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 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   
89 -class LoadFactory(object):
90 """ 91 Attention: this helper is new and experimental 92 """
93 - def __init__(self,environment):
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 ## some magic here because current are thread-locals 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
160 -def local_import_aux(name, force=False, app='welcome'):
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
216 -def build_environment(request, response, session):
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 # for backward compatibility 245 environment['SQLField'] = SQLField # for backward compatibility 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
257 -def save_pyc(filename):
258 """ 259 Bytecode compiles the file `filename` 260 """ 261 py_compile.compile(filename)
262 263
264 -def read_pyc(filename):
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
277 -def compile_views(folder):
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
292 -def compile_models(folder):
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
307 -def compile_controllers(folder):
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 ### why is this here? save_pyc(os.path.join(path, file)) 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
328 -def run_models_in(environment):
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
362 -def run_controller_in(controller, function, environment):
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 # if compiled should run compiled! 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 # TESTING: adjust the path to include site packages 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 # TESTING END 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
430 -def run_view_in(environment):
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 # for backward compatibility 457 if request.extension == 'html': 458 files.append('views_%s.pyc' % x[:-5]) 459 if allow_generic: 460 files.append('views_generic.pyc') 461 # end backward compatibility code 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
492 -def remove_compiled_application(folder):
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
505 -def compile_application(folder):
506 """ 507 Compiles all models, views, controller for the application in `folder`. 508 """ 509 remove_compiled_application(folder) 510 os.mkdir(os.path.join(folder, 'compiled')) 511 compile_models(folder) 512 compile_controllers(folder) 513 compile_views(folder)
514 515
516 -def test():
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