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