Package web2py :: Package gluon :: Module compileapp
[hide private]
[frames] | no frames]

Source Code for Module web2py.gluon.compileapp

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  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   
95 -class mybuiltin(object):
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 #__builtins__
101 - def __getitem__(self, key):
102 try: 103 return getattr(__builtin__, key) 104 except AttributeError: 105 raise KeyError, key
106 - def __setitem__(self, key, value):
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) ### NASTY 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 ## some magic here because current are thread-locals 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
187 -class LoadFactory(object):
188 """ 189 Attention: this helper is new and experimental 190 """
191 - def __init__(self,environment):
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 ## some magic here because current are thread-locals 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
269 -def local_import_aux(name, reload_force=False, app='welcome'):
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
325 -def build_environment(request, response, session, store_current=True):
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: # jython hack 350 __builtins__ = mybuiltin() 351 elif is_pypy: # apply the same hack to pypy too 352 __builtins__ = mybuiltin() 353 else: 354 __builtins__['__import__'] = __builtin__.__import__ ### WHY? 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 # for backward compatibility 364 environment['SQLField'] = SQLField # for backward compatibility 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
376 -def save_pyc(filename):
377 """ 378 Bytecode compiles the file `filename` 379 """ 380 py_compile.compile(filename)
381 382
383 -def read_pyc(filename):
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
396 -def compile_views(folder):
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
411 -def compile_models(folder):
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
426 -def compile_controllers(folder):
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 ### why is this here? save_pyc(os.path.join(path, file)) 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
447 -def run_models_in(environment):
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
481 -def run_controller_in(controller, function, environment):
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 # if compiled should run compiled! 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 # TESTING: adjust the path to include site packages 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 # TESTING END 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
549 -def run_view_in(environment):
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 # for backward compatibility 576 if request.extension == 'html': 577 files.append('views_%s.pyc' % x[:-5]) 578 if allow_generic: 579 files.append('views_generic.pyc') 580 # end backward compatibility code 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
611 -def remove_compiled_application(folder):
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
624 -def compile_application(folder):
625 """ 626 Compiles all models, views, controller for the application in `folder`. 627 """ 628 remove_compiled_application(folder) 629 os.mkdir(os.path.join(folder, 'compiled')) 630 compile_models(folder) 631 compile_controllers(folder) 632 compile_views(folder)
633 634
635 -def test():
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