1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8
9 Functions required to execute app components
10 ============================================
11
12 FOR INTERNAL USE ONLY
13 """
14
15 import re
16 import fnmatch
17 import os
18 import copy
19 import random
20 from storage import Storage, List
21 from template import parse_template
22 from restricted import restricted, compile2
23 from fileutils import mktree, listdir
24 from myregex import regex_expose
25 from languages import translator
26 from dal import BaseAdapter, SQLDB, SQLField, DAL, Field
27 from sqlhtml import SQLFORM, SQLTABLE
28 from cache import Cache
29 from globals import current
30 import settings
31 from cfs import getcfs
32 import html
33 import validators
34 from http import HTTP, redirect
35 import marshal
36 import shutil
37 import imp
38 import logging
39 logger = logging.getLogger("web2py")
40 import rewrite
41
42 try:
43 import py_compile
44 except:
45 logger.warning('unable to import py_compile')
46
47 is_gae = settings.global_settings.web2py_runtime_gae
48
49 TEST_CODE = \
50 r"""
51 def _TEST():
52 import doctest, sys, cStringIO, types, cgi, gluon.fileutils
53 if not gluon.fileutils.check_credentials(request):
54 raise HTTP(401, web2py_error='invalid credentials')
55 stdout = sys.stdout
56 html = '<h2>Testing controller "%s.py" ... done.</h2><br/>\n' \
57 % request.controller
58 for key in sorted([key for key in globals() if not key in __symbols__+['_TEST']]):
59 eval_key = eval(key)
60 if type(eval_key) == types.FunctionType:
61 number_doctests = sum([len(ds.examples) for ds in doctest.DocTestFinder().find(eval_key)])
62 if number_doctests>0:
63 sys.stdout = cStringIO.StringIO()
64 name = '%s/controllers/%s.py in %s.__doc__' \
65 % (request.folder, request.controller, key)
66 doctest.run_docstring_examples(eval_key,
67 globals(), False, name=name)
68 report = sys.stdout.getvalue().strip()
69 if report:
70 pf = 'failed'
71 else:
72 pf = 'passed'
73 html += '<h3 class="%s">Function %s [%s]</h3>\n' \
74 % (pf, key, pf)
75 if report:
76 html += CODE(report, language='web2py', \
77 link='/examples/global/vars/').xml()
78 html += '<br/>\n'
79 else:
80 html += \
81 '<h3 class="nodoctests">Function %s [no doctests]</h3><br/>\n' \
82 % (key)
83 response._vars = html
84 sys.stdout = stdout
85 _TEST()
86 """
87
89 """
90 Attention: this helper is new and experimental
91 """
93 self.environment = environment
94 - def __call__(self, c=None, f='index', args=[], vars={},
95 extension=None, target=None,ajax=False,ajax_trap=False,
96 url=None,user_signature=False, content='loading...',**attr):
97 import globals
98 target = target or 'c'+str(random.random())[2:]
99 attr['_id']=target
100 request = self.environment['request']
101 if '.' in f:
102 f, extension = f.split('.',1)
103 if url or ajax:
104 url = url or html.URL(request.application, c, f, r=request,
105 args=args, vars=vars, extension=extension,
106 user_signature=user_signature)
107 script = html.SCRIPT('web2py_component("%s","%s")' % (url, target),
108 _type="text/javascript")
109 return html.TAG[''](script, html.DIV(content,**attr))
110 else:
111 if not isinstance(args,(list,tuple)):
112 args = [args]
113 c = c or request.controller
114 other_request = globals.Request()
115 other_request.application = request.application
116 other_request.controller = c
117 other_request.function = f
118 other_request.extension = extension or request.extension
119 other_request.args = List(args)
120 other_request.folder = request.folder
121 other_request.env = request.env
122 other_request.vars = request.vars
123 other_request.get_vars = request.get_vars
124 other_request.post_vars = request.post_vars
125 other_response = globals.Response()
126 other_request.env.http_web2py_component_location = \
127 request.env.path_info
128 other_request.env.http_web2py_component_element = target
129 other_response.view = '%s/%s.%s' % (c,f, other_request.extension)
130
131 other_environment = copy.copy(self.environment)
132 other_response._view_environment = other_environment
133 other_environment['request'] = other_request
134 other_environment['response'] = other_response
135 page = run_controller_in(c, f, other_environment)
136
137 if isinstance(page, dict):
138 other_response._vars = page
139 for key in page:
140 other_response._view_environment[key] = page[key]
141 run_view_in(other_response._view_environment)
142 page = other_response.body.getvalue()
143 js = None
144 if ajax_trap:
145 link = html.URL(request.application, c, f, r=request,
146 args=args, vars=vars, extension=extension,
147 user_signature=user_signature),
148 js = "web2py_trap_form('%s','%s');" % (link, target)
149
150
151
152
153 script = js and html.SCRIPT(js,_type="text/javascript") or ''
154 return html.TAG[''](html.DIV(html.XML(page),**attr),script)
155
156
158 """
159 In apps, instead of importing a local module
160 (in applications/app/modules) with::
161
162 import a.b.c as d
163
164 you should do::
165
166 d = local_import('a.b.c')
167
168 or (to force a reload):
169
170 d = local_import('a.b.c', reload=True)
171
172 This prevents conflict between applications and un-necessary execs.
173 It can be used to import any module, including regular Python modules.
174 """
175 items = name.replace('/','.')
176 name = "applications.%s.modules.%s" % (app, items)
177 module = __import__(name)
178 for item in name.split(".")[1:]:
179 module = getattr(module, item)
180 if force:
181 reload(module)
182 return module
183
184
185 """
186 OLD IMPLEMENTATION:
187 items = name.replace('/','.').split('.')
188 filename, modulepath = items[-1], os.path.join(apath,'modules',*items[:-1])
189 imp.acquire_lock()
190 try:
191 file=None
192 (file,path,desc) = imp.find_module(filename,[modulepath]+sys.path)
193 if not path in sys.modules or reload:
194 if is_gae:
195 module={}
196 execfile(path,{},module)
197 module=Storage(module)
198 else:
199 module = imp.load_module(path,file,path,desc)
200 sys.modules[path] = module
201 else:
202 module = sys.modules[path]
203 except Exception, e:
204 module = None
205 if file:
206 file.close()
207 imp.release_lock()
208 if not module:
209 raise ImportError, "cannot find module %s in %s" % (filename, modulepath)
210 return module
211 """
212
214 """
215 Build the environment dictionary into which web2py files are executed.
216 """
217
218 environment = {}
219 for key in html.__all__:
220 environment[key] = getattr(html, key)
221 for key in validators.__all__:
222 environment[key] = getattr(validators, key)
223 if not request.env:
224 request.env = Storage()
225
226 current.request = request
227 current.response = response
228 current.session = session
229 current.T = environment['T'] = translator(request)
230 current.cache = environment['cache'] = Cache(request)
231
232 environment['HTTP'] = HTTP
233 environment['redirect'] = redirect
234 environment['request'] = request
235 environment['response'] = response
236 environment['session'] = session
237 environment['DAL'] = DAL
238 environment['Field'] = Field
239 environment['SQLDB'] = SQLDB
240 environment['SQLField'] = SQLField
241 environment['SQLFORM'] = SQLFORM
242 environment['SQLTABLE'] = SQLTABLE
243 environment['LOAD'] = LoadFactory(environment)
244 environment['local_import'] = \
245 lambda name, reload=False, app=request.application:\
246 local_import_aux(name,reload,app)
247 BaseAdapter.set_folder(os.path.join(request.folder, 'databases'))
248 response._view_environment = copy.copy(environment)
249 return environment
250
251
253 """
254 Bytecode compiles the file `filename`
255 """
256 py_compile.compile(filename)
257
258
260 """
261 Read the code inside a bytecode compiled file if the MAGIC number is
262 compatible
263
264 :returns: a code object
265 """
266 fp = open(filename, 'rb')
267 data = fp.read()
268 fp.close()
269 if not is_gae and data[:4] != imp.get_magic():
270 raise SystemError, 'compiled code is incompatible'
271 return marshal.loads(data[8:])
272
273
275 """
276 Compiles all the views in the application specified by `folder`
277 """
278
279 path = os.path.join(folder, 'views')
280 for file in listdir(path, '^[\w/]+\.\w+$'):
281 data = parse_template(file, path)
282 filename = ('views/%s.py' % file).replace('/', '_').replace('\\', '_')
283 filename = os.path.join(folder, 'compiled', filename)
284 fp = open(filename, 'w')
285 fp.write(data)
286 fp.close()
287 save_pyc(filename)
288 os.unlink(filename)
289
290
292 """
293 Compiles all the models in the application specified by `folder`
294 """
295
296 path = os.path.join(folder, 'models')
297 for file in listdir(path, '.+\.py$'):
298 fp = open(os.path.join(path, file), 'r')
299 data = fp.read()
300 fp.close()
301 filename = os.path.join(folder, 'compiled','models',file)
302 mktree(filename)
303 fp = open(filename, 'w')
304 fp.write(data)
305 fp.close()
306 save_pyc(filename)
307 os.unlink(filename)
308
309
311 """
312 Compiles all the controllers in the application specified by `folder`
313 """
314
315 path = os.path.join(folder, 'controllers')
316 for file in listdir(path, '.+\.py$'):
317
318 fp = open(os.path.join(path,file), 'r')
319 data = fp.read()
320 fp.close()
321 exposed = regex_expose.findall(data)
322 for function in exposed:
323 command = data + "\nresponse._vars=response._caller(%s)\n" % \
324 function
325 filename = os.path.join(folder, 'compiled', ('controllers/'
326 + file[:-3]).replace('/', '_')
327 + '_' + function + '.py')
328 fp = open(filename, 'w')
329 fp.write(command)
330 fp.close()
331 save_pyc(filename)
332 os.unlink(filename)
333
334
336 """
337 Runs all models (in the app specified by the current folder)
338 It tries pre-compiled models first before compiling them.
339 """
340
341 folder = environment['request'].folder
342 c = environment['request'].controller
343 f = environment['request'].function
344 cpath = os.path.join(folder, 'compiled')
345 if os.path.exists(cpath):
346 for model in listdir(cpath, '^models_\w+\.pyc$', 0):
347 restricted(read_pyc(model), environment, layer=model)
348 path = os.path.join(cpath, 'models')
349 models = listdir(path, '^\w+\.pyc$',0,sort=False)
350 compiled=True
351 else:
352 path = os.path.join(folder, 'models')
353 models = listdir(path, '^\w+\.py$',0,sort=False)
354 compiled=False
355 paths = (path, os.path.join(path,c), os.path.join(path,c,f))
356 for model in models:
357 if not os.path.split(model)[0] in paths:
358 continue
359 elif compiled:
360 code = read_pyc(model)
361 elif is_gae:
362 code = getcfs(model, model,
363 lambda: compile2(open(model, 'r').read(),model))
364 else:
365 code = getcfs(model, model, None)
366 restricted(code, environment, layer=model)
367
368
370 """
371 Runs the controller.function() (for the app specified by
372 the current folder).
373 It tries pre-compiled controller_function.pyc first before compiling it.
374 """
375
376
377
378 folder = environment['request'].folder
379 path = os.path.join(folder, 'compiled')
380 badc = 'invalid controller (%s/%s)' % (controller, function)
381 badf = 'invalid function (%s/%s)' % (controller, function)
382 if os.path.exists(path):
383 filename = os.path.join(path, 'controllers_%s_%s.pyc'
384 % (controller, function))
385 if not os.path.exists(filename):
386 raise HTTP(404,
387 rewrite.thread.routes.error_message % badf,
388 web2py_error=badf)
389 restricted(read_pyc(filename), environment, layer=filename)
390 elif function == '_TEST':
391
392 from settings import global_settings
393 from admin import abspath, add_path_first
394 paths = (global_settings.gluon_parent, abspath('site-packages', gluon=True), abspath('gluon', gluon=True), '')
395 [add_path_first(path) for path in paths]
396
397
398 filename = os.path.join(folder, 'controllers/%s.py'
399 % controller)
400 if not os.path.exists(filename):
401 raise HTTP(404,
402 rewrite.thread.routes.error_message % badc,
403 web2py_error=badc)
404 environment['__symbols__'] = environment.keys()
405 fp = open(filename, 'r')
406 code = fp.read()
407 fp.close()
408 code += TEST_CODE
409 restricted(code, environment, layer=filename)
410 else:
411 filename = os.path.join(folder, 'controllers/%s.py'
412 % controller)
413 if not os.path.exists(filename):
414 raise HTTP(404,
415 rewrite.thread.routes.error_message % badc,
416 web2py_error=badc)
417 fp = open(filename, 'r')
418 code = fp.read()
419 fp.close()
420 exposed = regex_expose.findall(code)
421 if not function in exposed:
422 raise HTTP(404,
423 rewrite.thread.routes.error_message % badf,
424 web2py_error=badf)
425 code = "%s\nresponse._vars=response._caller(%s)\n" % (code, function)
426 if is_gae:
427 layer = filename + ':' + function
428 code = getcfs(layer, filename, lambda: compile2(code,layer))
429 restricted(code, environment, filename)
430 response = environment['response']
431 vars=response._vars
432 if response.postprocessing:
433 for p in response.postprocessing:
434 vars = p(vars)
435 if isinstance(vars,unicode):
436 vars = vars.encode('utf8')
437 if hasattr(vars,'xml'):
438 vars = vars.xml()
439 return vars
440
442 """
443 Executes the view for the requested action.
444 The view is the one specified in `response.view` or determined by the url
445 or `view/generic.extension`
446 It tries the pre-compiled views_controller_function.pyc before compiling it.
447 """
448
449 request = environment['request']
450 response = environment['response']
451 folder = request.folder
452 path = os.path.join(folder, 'compiled')
453 badv = 'invalid view (%s)' % response.view
454 patterns = response.generic_patterns or []
455 regex = re.compile('|'.join(fnmatch.translate(r) for r in patterns))
456 allow_generic = regex.search('%s/%s.%s' % (request.controller,
457 request.function,
458 request.extension))
459 if not isinstance(response.view, str):
460 ccode = parse_template(response.view, os.path.join(folder, 'views'),
461 context=environment)
462 restricted(ccode, environment, 'file stream')
463 elif os.path.exists(path):
464 x = response.view.replace('/', '_')
465 files = ['views_%s.pyc' % x]
466 if allow_generic:
467 files.append('views_generic.%s.pyc' % request.extension)
468
469 if request.extension == 'html':
470 files.append('views_%s.pyc' % x[:-5])
471 if allow_generic:
472 files.append('views_generic.pyc')
473
474 for f in files:
475 filename = os.path.join(path,f)
476 if os.path.exists(filename):
477 code = read_pyc(filename)
478 restricted(code, environment, layer=filename)
479 return
480 raise HTTP(404,
481 rewrite.thread.routes.error_message % badv,
482 web2py_error=badv)
483 else:
484 filename = os.path.join(folder, 'views', response.view)
485 if not os.path.exists(filename) and allow_generic:
486 response.view = 'generic.' + request.extension
487 filename = os.path.join(folder, 'views', response.view)
488 if not os.path.exists(filename):
489 raise HTTP(404,
490 rewrite.thread.routes.error_message % badv,
491 web2py_error=badv)
492 layer = filename
493 if is_gae:
494 ccode = getcfs(layer, filename,
495 lambda: compile2(parse_template(response.view,
496 os.path.join(folder, 'views'),
497 context=environment),layer))
498 else:
499 ccode = parse_template(response.view,
500 os.path.join(folder, 'views'),
501 context=environment)
502 restricted(ccode, environment, layer)
503
505 """
506 Deletes the folder `compiled` containing the compiled application.
507 """
508 try:
509 shutil.rmtree(os.path.join(folder, 'compiled'))
510 path = os.path.join(folder, 'controllers')
511 for file in listdir(path,'.*\.pyc$',drop=False):
512 os.unlink(file)
513 except OSError:
514 pass
515
516
526
527
529 """
530 Example::
531
532 >>> import traceback, types
533 >>> environment={'x':1}
534 >>> open('a.py', 'w').write('print 1/x')
535 >>> save_pyc('a.py')
536 >>> os.unlink('a.py')
537 >>> if type(read_pyc('a.pyc'))==types.CodeType: print 'code'
538 code
539 >>> exec read_pyc('a.pyc') in environment
540 1
541 """
542
543 return
544
545
546 if __name__ == '__main__':
547 import doctest
548 doctest.testmod()
549