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 
 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 fp = open(filename, 'rb') 267 data = fp.read() 268 fp.close() 269 if not is_gae and data[:4] != imp.get_magic(): 270 raise SystemError, 'compiled code is incompatible' 271 return marshal.loads(data[8:])
272 273
274 -def compile_views(folder):
275 """ 276 Compiles all the views in the application specified by `folder` 277 """ 278 279 path = os.path.join(folder, 'views') 280 for file in listdir(path, '^[\w/]+\.\w+$'): 281 data = parse_template(file, path) 282 filename = ('views/%s.py' % file).replace('/', '_').replace('\\', '_') 283 filename = os.path.join(folder, 'compiled', filename) 284 fp = open(filename, 'w') 285 fp.write(data) 286 fp.close() 287 save_pyc(filename) 288 os.unlink(filename)
289 290
291 -def compile_models(folder):
292 """ 293 Compiles all the models in the application specified by `folder` 294 """ 295 296 path = os.path.join(folder, 'models') 297 for file in listdir(path, '.+\.py$'): 298 fp = open(os.path.join(path, file), 'r') 299 data = fp.read() 300 fp.close() 301 filename = os.path.join(folder, 'compiled','models',file) 302 mktree(filename) 303 fp = open(filename, 'w') 304 fp.write(data) 305 fp.close() 306 save_pyc(filename) 307 os.unlink(filename)
308 309
310 -def compile_controllers(folder):
311 """ 312 Compiles all the controllers in the application specified by `folder` 313 """ 314 315 path = os.path.join(folder, 'controllers') 316 for file in listdir(path, '.+\.py$'): 317 ### why is this here? save_pyc(os.path.join(path, file)) 318 fp = open(os.path.join(path,file), 'r') 319 data = fp.read() 320 fp.close() 321 exposed = regex_expose.findall(data) 322 for function in exposed: 323 command = data + "\nresponse._vars=response._caller(%s)\n" % \ 324 function 325 filename = os.path.join(folder, 'compiled', ('controllers/' 326 + file[:-3]).replace('/', '_') 327 + '_' + function + '.py') 328 fp = open(filename, 'w') 329 fp.write(command) 330 fp.close() 331 save_pyc(filename) 332 os.unlink(filename)
333 334
335 -def run_models_in(environment):
336 """ 337 Runs all models (in the app specified by the current folder) 338 It tries pre-compiled models first before compiling them. 339 """ 340 341 folder = environment['request'].folder 342 c = environment['request'].controller 343 f = environment['request'].function 344 cpath = os.path.join(folder, 'compiled') 345 if os.path.exists(cpath): 346 for model in listdir(cpath, '^models_\w+\.pyc$', 0): 347 restricted(read_pyc(model), environment, layer=model) 348 path = os.path.join(cpath, 'models') 349 models = listdir(path, '^\w+\.pyc$',0,sort=False) 350 compiled=True 351 else: 352 path = os.path.join(folder, 'models') 353 models = listdir(path, '^\w+\.py$',0,sort=False) 354 compiled=False 355 paths = (path, os.path.join(path,c), os.path.join(path,c,f)) 356 for model in models: 357 if not os.path.split(model)[0] in paths: 358 continue 359 elif compiled: 360 code = read_pyc(model) 361 elif is_gae: 362 code = getcfs(model, model, 363 lambda: compile2(open(model, 'r').read(),model)) 364 else: 365 code = getcfs(model, model, None) 366 restricted(code, environment, layer=model)
367 368
369 -def run_controller_in(controller, function, environment):
370 """ 371 Runs the controller.function() (for the app specified by 372 the current folder). 373 It tries pre-compiled controller_function.pyc first before compiling it. 374 """ 375 376 # if compiled should run compiled! 377 378 folder = environment['request'].folder 379 path = os.path.join(folder, 'compiled') 380 badc = 'invalid controller (%s/%s)' % (controller, function) 381 badf = 'invalid function (%s/%s)' % (controller, function) 382 if os.path.exists(path): 383 filename = os.path.join(path, 'controllers_%s_%s.pyc' 384 % (controller, function)) 385 if not os.path.exists(filename): 386 raise HTTP(404, 387 rewrite.thread.routes.error_message % badf, 388 web2py_error=badf) 389 restricted(read_pyc(filename), environment, layer=filename) 390 elif function == '_TEST': 391 # TESTING: adjust the path to include site packages 392 from settings import global_settings 393 from admin import abspath, add_path_first 394 paths = (global_settings.gluon_parent, abspath('site-packages', gluon=True), abspath('gluon', gluon=True), '') 395 [add_path_first(path) for path in paths] 396 # TESTING END 397 398 filename = os.path.join(folder, 'controllers/%s.py' 399 % controller) 400 if not os.path.exists(filename): 401 raise HTTP(404, 402 rewrite.thread.routes.error_message % badc, 403 web2py_error=badc) 404 environment['__symbols__'] = environment.keys() 405 fp = open(filename, 'r') 406 code = fp.read() 407 fp.close() 408 code += TEST_CODE 409 restricted(code, environment, layer=filename) 410 else: 411 filename = os.path.join(folder, 'controllers/%s.py' 412 % controller) 413 if not os.path.exists(filename): 414 raise HTTP(404, 415 rewrite.thread.routes.error_message % badc, 416 web2py_error=badc) 417 fp = open(filename, 'r') 418 code = fp.read() 419 fp.close() 420 exposed = regex_expose.findall(code) 421 if not function in exposed: 422 raise HTTP(404, 423 rewrite.thread.routes.error_message % badf, 424 web2py_error=badf) 425 code = "%s\nresponse._vars=response._caller(%s)\n" % (code, function) 426 if is_gae: 427 layer = filename + ':' + function 428 code = getcfs(layer, filename, lambda: compile2(code,layer)) 429 restricted(code, environment, filename) 430 response = environment['response'] 431 vars=response._vars 432 if response.postprocessing: 433 for p in response.postprocessing: 434 vars = p(vars) 435 if isinstance(vars,unicode): 436 vars = vars.encode('utf8') 437 if hasattr(vars,'xml'): 438 vars = vars.xml() 439 return vars
440
441 -def run_view_in(environment):
442 """ 443 Executes the view for the requested action. 444 The view is the one specified in `response.view` or determined by the url 445 or `view/generic.extension` 446 It tries the pre-compiled views_controller_function.pyc before compiling it. 447 """ 448 449 request = environment['request'] 450 response = environment['response'] 451 folder = request.folder 452 path = os.path.join(folder, 'compiled') 453 badv = 'invalid view (%s)' % response.view 454 patterns = response.generic_patterns or [] 455 regex = re.compile('|'.join(fnmatch.translate(r) for r in patterns)) 456 allow_generic = regex.search('%s/%s.%s' % (request.controller, 457 request.function, 458 request.extension)) 459 if not isinstance(response.view, str): 460 ccode = parse_template(response.view, os.path.join(folder, 'views'), 461 context=environment) 462 restricted(ccode, environment, 'file stream') 463 elif os.path.exists(path): 464 x = response.view.replace('/', '_') 465 files = ['views_%s.pyc' % x] 466 if allow_generic: 467 files.append('views_generic.%s.pyc' % request.extension) 468 # for backward compatibility 469 if request.extension == 'html': 470 files.append('views_%s.pyc' % x[:-5]) 471 if allow_generic: 472 files.append('views_generic.pyc') 473 # end backward compatibility code 474 for f in files: 475 filename = os.path.join(path,f) 476 if os.path.exists(filename): 477 code = read_pyc(filename) 478 restricted(code, environment, layer=filename) 479 return 480 raise HTTP(404, 481 rewrite.thread.routes.error_message % badv, 482 web2py_error=badv) 483 else: 484 filename = os.path.join(folder, 'views', response.view) 485 if not os.path.exists(filename) and allow_generic: 486 response.view = 'generic.' + request.extension 487 filename = os.path.join(folder, 'views', response.view) 488 if not os.path.exists(filename): 489 raise HTTP(404, 490 rewrite.thread.routes.error_message % badv, 491 web2py_error=badv) 492 layer = filename 493 if is_gae: 494 ccode = getcfs(layer, filename, 495 lambda: compile2(parse_template(response.view, 496 os.path.join(folder, 'views'), 497 context=environment),layer)) 498 else: 499 ccode = parse_template(response.view, 500 os.path.join(folder, 'views'), 501 context=environment) 502 restricted(ccode, environment, layer)
503
504 -def remove_compiled_application(folder):
505 """ 506 Deletes the folder `compiled` containing the compiled application. 507 """ 508 try: 509 shutil.rmtree(os.path.join(folder, 'compiled')) 510 path = os.path.join(folder, 'controllers') 511 for file in listdir(path,'.*\.pyc$',drop=False): 512 os.unlink(file) 513 except OSError: 514 pass
515 516
517 -def compile_application(folder):
518 """ 519 Compiles all models, views, controller for the application in `folder`. 520 """ 521 remove_compiled_application(folder) 522 os.mkdir(os.path.join(folder, 'compiled')) 523 compile_models(folder) 524 compile_controllers(folder) 525 compile_views(folder)
526 527
528 -def test():
529 """ 530 Example:: 531 532 >>> import traceback, types 533 >>> environment={'x':1} 534 >>> open('a.py', 'w').write('print 1/x') 535 >>> save_pyc('a.py') 536 >>> os.unlink('a.py') 537 >>> if type(read_pyc('a.pyc'))==types.CodeType: print 'code' 538 code 539 >>> exec read_pyc('a.pyc') in environment 540 1 541 """ 542 543 return
544 545 546 if __name__ == '__main__': 547 import doctest 548 doctest.testmod() 549