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, timeout=None, times=1,
112 content='loading...',**attr):
113 """ LOAD a component into the action's document
114
115 Timing options:
116 -times: An integer or string ("infinity"/"continuous")
117 specifies how many times the component is requested
118 -timeout (milliseconds): specifies the time to wait before
119 starting the request or the frequency if times is greater than
120 1 or "infinity".
121 Timing options default to the normal behavior. The component
122 is added on page loading without delay.
123 """
124 from html import TAG, DIV, URL, SCRIPT, XML
125 if args is None: args = []
126 vars = Storage(vars or {})
127 target = target or 'c'+str(random.random())[2:]
128 attr['_id']=target
129 request = current.request
130 if '.' in f:
131 f, extension = f.split('.',1)
132 if url or ajax:
133 url = url or URL(request.application, c, f, r=request,
134 args=args, vars=vars, extension=extension,
135 user_signature=user_signature)
136
137 if isinstance(times, basestring):
138 if times.upper() in ("INFINITY", "CONTINUOUS"):
139 times = "Infinity"
140 else:
141 raise TypeError("Unsupported times argument %s" % times)
142 elif isinstance(times, int):
143 if times <= 0:
144 raise ValueError("Times argument must be greater than zero, 'Infinity' or None")
145 else:
146 raise TypeError("Unsupported times argument type %s" % type(times))
147 if timeout is not None:
148 if not isinstance(timeout, (int, long)):
149 raise ValueError("Timeout argument must be an integer or None")
150 elif timeout <= 0:
151 raise ValueError("Timeout argument must be greater than zero or None")
152 statement = "web2py_component('%s','%s', %s, %s);" \
153 % (url, target, timeout, times)
154 else:
155 statement = "web2py_component('%s','%s');" % (url, target)
156 script = SCRIPT(statement, _type="text/javascript")
157 if not content is None:
158 return TAG[''](script, DIV(content,**attr))
159 else:
160 return TAG[''](script)
161
162 else:
163 if not isinstance(args,(list,tuple)):
164 args = [args]
165 c = c or request.controller
166 other_request = Storage()
167 for key, value in request.items():
168 other_request[key] = value
169 other_request['env'] = Storage()
170 for key, value in request.env.items():
171 other_request.env['key'] = value
172 other_request.controller = c
173 other_request.function = f
174 other_request.extension = extension or request.extension
175 other_request.args = List(args)
176 other_request.vars = vars
177 other_request.get_vars = vars
178 other_request.post_vars = Storage()
179 other_response = Response()
180 other_request.env.path_info = '/' + \
181 '/'.join([request.application,c,f] + \
182 map(str, other_request.args))
183 other_request.env.query_string = \
184 vars and URL(vars=vars).split('?')[1] or ''
185 other_request.env.http_web2py_component_location = \
186 request.env.path_info
187 other_request.cid = target
188 other_request.env.http_web2py_component_element = target
189 other_response.view = '%s/%s.%s' % (c,f, other_request.extension)
190
191 other_environment = copy.copy(current.globalenv)
192
193 other_response._view_environment = other_environment
194 other_response.generic_patterns = \
195 copy.copy(current.response.generic_patterns)
196 other_environment['request'] = other_request
197 other_environment['response'] = other_response
198
199
200
201 original_request, current.request = current.request, other_request
202 original_response, current.response = current.response, other_response
203 page = run_controller_in(c, f, other_environment)
204 if isinstance(page, dict):
205 other_response._vars = page
206 for key in page:
207 other_response._view_environment[key] = page[key]
208 run_view_in(other_response._view_environment)
209 page = other_response.body.getvalue()
210 current.request, current.response = original_request, original_response
211 js = None
212 if ajax_trap:
213 link = URL(request.application, c, f, r=request,
214 args=args, vars=vars, extension=extension,
215 user_signature=user_signature)
216 js = "web2py_trap_form('%s','%s');" % (link, target)
217 script = js and SCRIPT(js,_type="text/javascript") or ''
218 return TAG[''](DIV(XML(page),**attr),script)
219
220
221
223 """
224 Attention: this helper is new and experimental
225 """
227 self.environment = environment
228 - def __call__(self, c=None, f='index', args=None, vars=None,
229 extension=None, target=None,ajax=False,ajax_trap=False,
230 url=None,user_signature=False, content='loading...',**attr):
231 if args is None: args = []
232 vars = Storage(vars or {})
233 import globals
234 target = target or 'c'+str(random.random())[2:]
235 attr['_id']=target
236 request = self.environment['request']
237 if '.' in f:
238 f, extension = f.split('.',1)
239 if url or ajax:
240 url = url or html.URL(request.application, c, f, r=request,
241 args=args, vars=vars, extension=extension,
242 user_signature=user_signature)
243 script = html.SCRIPT('web2py_component("%s","%s")' % (url, target),
244 _type="text/javascript")
245 return html.TAG[''](script, html.DIV(content,**attr))
246 else:
247 if not isinstance(args,(list,tuple)):
248 args = [args]
249 c = c or request.controller
250
251 other_request = Storage()
252 for key, value in request.items():
253 other_request[key] = value
254 other_request['env'] = Storage()
255 for key, value in request.env.items():
256 other_request.env['key'] = value
257 other_request.controller = c
258 other_request.function = f
259 other_request.extension = extension or request.extension
260 other_request.args = List(args)
261 other_request.vars = vars
262 other_request.get_vars = vars
263 other_request.post_vars = Storage()
264 other_response = globals.Response()
265 other_request.env.path_info = '/' + \
266 '/'.join([request.application,c,f] + \
267 map(str, other_request.args))
268 other_request.env.query_string = \
269 vars and html.URL(vars=vars).split('?')[1] or ''
270 other_request.env.http_web2py_component_location = \
271 request.env.path_info
272 other_request.cid = target
273 other_request.env.http_web2py_component_element = target
274 other_response.view = '%s/%s.%s' % (c,f, other_request.extension)
275 other_environment = copy.copy(self.environment)
276 other_response._view_environment = other_environment
277 other_response.generic_patterns = \
278 copy.copy(current.response.generic_patterns)
279 other_environment['request'] = other_request
280 other_environment['response'] = other_response
281
282
283
284 original_request, current.request = current.request, other_request
285 original_response, current.response = current.response, other_response
286 page = run_controller_in(c, f, other_environment)
287 if isinstance(page, dict):
288 other_response._vars = page
289 for key in page:
290 other_response._view_environment[key] = page[key]
291 run_view_in(other_response._view_environment)
292 page = other_response.body.getvalue()
293 current.request, current.response = original_request, original_response
294 js = None
295 if ajax_trap:
296 link = html.URL(request.application, c, f, r=request,
297 args=args, vars=vars, extension=extension,
298 user_signature=user_signature)
299 js = "web2py_trap_form('%s','%s');" % (link, target)
300 script = js and html.SCRIPT(js,_type="text/javascript") or ''
301 return html.TAG[''](html.DIV(html.XML(page),**attr),script)
302
303
305 """
306 In apps, instead of importing a local module
307 (in applications/app/modules) with::
308
309 import a.b.c as d
310
311 you should do::
312
313 d = local_import('a.b.c')
314
315 or (to force a reload):
316
317 d = local_import('a.b.c', reload=True)
318
319 This prevents conflict between applications and un-necessary execs.
320 It can be used to import any module, including regular Python modules.
321 """
322 items = name.replace('/','.')
323 name = "applications.%s.modules.%s" % (app, items)
324 module = __import__(name)
325 for item in name.split(".")[1:]:
326 module = getattr(module, item)
327 if reload_force:
328 reload(module)
329 return module
330
331
332 """
333 OLD IMPLEMENTATION:
334 items = name.replace('/','.').split('.')
335 filename, modulepath = items[-1], os.path.join(apath,'modules',*items[:-1])
336 imp.acquire_lock()
337 try:
338 file=None
339 (file,path,desc) = imp.find_module(filename,[modulepath]+sys.path)
340 if not path in sys.modules or reload:
341 if is_gae:
342 module={}
343 execfile(path,{},module)
344 module=Storage(module)
345 else:
346 module = imp.load_module(path,file,path,desc)
347 sys.modules[path] = module
348 else:
349 module = sys.modules[path]
350 except Exception, e:
351 module = None
352 if file:
353 file.close()
354 imp.release_lock()
355 if not module:
356 raise ImportError, "cannot find module %s in %s" % (filename, modulepath)
357 return module
358 """
359
409
410
412 """
413 Bytecode compiles the file `filename`
414 """
415 py_compile.compile(filename)
416
417
419 """
420 Read the code inside a bytecode compiled file if the MAGIC number is
421 compatible
422
423 :returns: a code object
424 """
425 data = read_file(filename, 'rb')
426 if not is_gae and data[:4] != imp.get_magic():
427 raise SystemError, 'compiled code is incompatible'
428 return marshal.loads(data[8:])
429
430
432 """
433 Compiles all the views in the application specified by `folder`
434 """
435
436 path = os.path.join(folder, 'views')
437 for file in listdir(path, '^[\w/\-]+(\.\w+)+$'):
438 data = parse_template(file, path)
439 filename = ('views/%s.py' % file).replace('/', '_').replace('\\', '_')
440 filename = os.path.join(folder, 'compiled', filename)
441 write_file(filename, data)
442 save_pyc(filename)
443 os.unlink(filename)
444
445
447 """
448 Compiles all the models in the application specified by `folder`
449 """
450
451 path = os.path.join(folder, 'models')
452 for file in listdir(path, '.+\.py$'):
453 data = read_file(os.path.join(path, file))
454 filename = os.path.join(folder, 'compiled','models',file)
455 mktree(filename)
456 write_file(filename, data)
457 save_pyc(filename)
458 os.unlink(filename)
459
460
462 """
463 Compiles all the controllers in the application specified by `folder`
464 """
465
466 path = os.path.join(folder, 'controllers')
467 for file in listdir(path, '.+\.py$'):
468
469 data = read_file(os.path.join(path,file))
470 exposed = regex_expose.findall(data)
471 for function in exposed:
472 command = data + "\nresponse._vars=response._caller(%s)\n" % \
473 function
474 filename = os.path.join(folder, 'compiled', ('controllers/'
475 + file[:-3]).replace('/', '_')
476 + '_' + function + '.py')
477 write_file(filename, command)
478 save_pyc(filename)
479 os.unlink(filename)
480
481
483 """
484 Runs all models (in the app specified by the current folder)
485 It tries pre-compiled models first before compiling them.
486 """
487
488 folder = environment['request'].folder
489 c = environment['request'].controller
490 f = environment['request'].function
491 cpath = os.path.join(folder, 'compiled')
492 if os.path.exists(cpath):
493 for model in listdir(cpath, '^models_\w+\.pyc$', 0):
494 restricted(read_pyc(model), environment, layer=model)
495 path = os.path.join(cpath, 'models')
496 models = listdir(path, '^\w+\.pyc$',0,sort=False)
497 compiled=True
498 else:
499 path = os.path.join(folder, 'models')
500 models = listdir(path, '^\w+\.py$',0,sort=False)
501 compiled=False
502 paths = (path, os.path.join(path,c), os.path.join(path,c,f))
503 for model in models:
504 if not os.path.split(model)[0] in paths and c!='appadmin':
505 continue
506 elif compiled:
507 code = read_pyc(model)
508 elif is_gae:
509 code = getcfs(model, model,
510 lambda: compile2(read_file(model), model))
511 else:
512 code = getcfs(model, model, None)
513 restricted(code, environment, layer=model)
514
515
517 """
518 Runs the controller.function() (for the app specified by
519 the current folder).
520 It tries pre-compiled controller_function.pyc first before compiling it.
521 """
522
523
524
525 folder = environment['request'].folder
526 path = os.path.join(folder, 'compiled')
527 badc = 'invalid controller (%s/%s)' % (controller, function)
528 badf = 'invalid function (%s/%s)' % (controller, function)
529 if os.path.exists(path):
530 filename = os.path.join(path, 'controllers_%s_%s.pyc'
531 % (controller, function))
532 if not os.path.exists(filename):
533 raise HTTP(404,
534 rewrite.thread.routes.error_message % badf,
535 web2py_error=badf)
536 restricted(read_pyc(filename), environment, layer=filename)
537 elif function == '_TEST':
538
539 from settings import global_settings
540 from admin import abspath, add_path_first
541 paths = (global_settings.gluon_parent, abspath('site-packages', gluon=True), abspath('gluon', gluon=True), '')
542 [add_path_first(path) for path in paths]
543
544
545 filename = os.path.join(folder, 'controllers/%s.py'
546 % controller)
547 if not os.path.exists(filename):
548 raise HTTP(404,
549 rewrite.thread.routes.error_message % badc,
550 web2py_error=badc)
551 environment['__symbols__'] = environment.keys()
552 code = read_file(filename)
553 code += TEST_CODE
554 restricted(code, environment, layer=filename)
555 else:
556 filename = os.path.join(folder, 'controllers/%s.py'
557 % controller)
558 if not os.path.exists(filename):
559 raise HTTP(404,
560 rewrite.thread.routes.error_message % badc,
561 web2py_error=badc)
562 code = read_file(filename)
563 exposed = regex_expose.findall(code)
564 if not function in exposed:
565 raise HTTP(404,
566 rewrite.thread.routes.error_message % badf,
567 web2py_error=badf)
568 code = "%s\nresponse._vars=response._caller(%s)\n" % (code, function)
569 if is_gae:
570 layer = filename + ':' + function
571 code = getcfs(layer, filename, lambda: compile2(code,layer))
572 restricted(code, environment, filename)
573 response = environment['response']
574 vars=response._vars
575 if response.postprocessing:
576 for p in response.postprocessing:
577 vars = p(vars)
578 if isinstance(vars,unicode):
579 vars = vars.encode('utf8')
580 if hasattr(vars,'xml'):
581 vars = vars.xml()
582 return vars
583
585 """
586 Executes the view for the requested action.
587 The view is the one specified in `response.view` or determined by the url
588 or `view/generic.extension`
589 It tries the pre-compiled views_controller_function.pyc before compiling it.
590 """
591
592 request = environment['request']
593 response = environment['response']
594 folder = request.folder
595 path = os.path.join(folder, 'compiled')
596 badv = 'invalid view (%s)' % response.view
597 patterns = response.generic_patterns or []
598 regex = re.compile('|'.join(fnmatch.translate(r) for r in patterns))
599 short_action = '%(controller)s/%(function)s.%(extension)s' % request
600 allow_generic = patterns and regex.search(short_action)
601 if not isinstance(response.view, str):
602 ccode = parse_template(response.view, os.path.join(folder, 'views'),
603 context=environment)
604 restricted(ccode, environment, 'file stream')
605 elif os.path.exists(path):
606 x = response.view.replace('/', '_')
607 files = ['views_%s.pyc' % x]
608 if allow_generic:
609 files.append('views_generic.%s.pyc' % request.extension)
610
611 if request.extension == 'html':
612 files.append('views_%s.pyc' % x[:-5])
613 if allow_generic:
614 files.append('views_generic.pyc')
615
616 for f in files:
617 filename = os.path.join(path,f)
618 if os.path.exists(filename):
619 code = read_pyc(filename)
620 restricted(code, environment, layer=filename)
621 return
622 raise HTTP(404,
623 rewrite.thread.routes.error_message % badv,
624 web2py_error=badv)
625 else:
626 filename = os.path.join(folder, 'views', response.view)
627 if not os.path.exists(filename) and allow_generic:
628 response.view = 'generic.' + request.extension
629 filename = os.path.join(folder, 'views', response.view)
630 if not os.path.exists(filename):
631 raise HTTP(404,
632 rewrite.thread.routes.error_message % badv,
633 web2py_error=badv)
634 layer = filename
635 if is_gae:
636 ccode = getcfs(layer, filename,
637 lambda: compile2(parse_template(response.view,
638 os.path.join(folder, 'views'),
639 context=environment),layer))
640 else:
641 ccode = parse_template(response.view,
642 os.path.join(folder, 'views'),
643 context=environment)
644 restricted(ccode, environment, layer)
645
647 """
648 Deletes the folder `compiled` containing the compiled application.
649 """
650 try:
651 shutil.rmtree(os.path.join(folder, 'compiled'))
652 path = os.path.join(folder, 'controllers')
653 for file in listdir(path,'.*\.pyc$',drop=False):
654 os.unlink(file)
655 except OSError:
656 pass
657
658
668
669
671 """
672 Example::
673
674 >>> import traceback, types
675 >>> environment={'x':1}
676 >>> open('a.py', 'w').write('print 1/x')
677 >>> save_pyc('a.py')
678 >>> os.unlink('a.py')
679 >>> if type(read_pyc('a.pyc'))==types.CodeType: print 'code'
680 code
681 >>> exec read_pyc('a.pyc') in environment
682 1
683 """
684
685 return
686
687
688 if __name__ == '__main__':
689 import doctest
690 doctest.testmod()
691