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