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