Package gluon :: Module shell
[hide private]
[frames] | no frames]

Source Code for Module gluon.shell

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3   
  4  """ 
  5  This file is part of the web2py Web Framework 
  6  Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>, 
  7  limodou <limodou@gmail.com> and srackham <srackham@gmail.com>. 
  8  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
  9   
 10  """ 
 11   
 12  import os 
 13  import sys 
 14  import code 
 15  import logging 
 16  import types 
 17  import re 
 18  import optparse 
 19  import glob 
 20  import traceback 
 21  import gluon.fileutils as fileutils 
 22  from gluon.settings import global_settings 
 23  from gluon.utils import web2py_uuid 
 24  from gluon.compileapp import build_environment, read_pyc, run_models_in 
 25  from gluon.restricted import RestrictedError 
 26  from gluon.globals import Request, Response, Session 
 27  from gluon.storage import Storage, List 
 28  from gluon.admin import w2p_unpack 
 29  from gluon.dal import BaseAdapter 
 30   
 31  logger = logging.getLogger("web2py") 
 32   
33 -def enable_autocomplete_and_history(adir,env):
34 try: 35 import rlcompleter 36 import atexit 37 import readline 38 except ImportError: 39 pass 40 else: 41 readline.parse_and_bind("bind ^I rl_complete" 42 if sys.platform == 'darwin' 43 else "tab: complete") 44 history_file = os.path.join(adir,'.pythonhistory') 45 try: 46 readline.read_history_file(history_file) 47 except IOError: 48 open(history_file, 'a').close() 49 atexit.register(readline.write_history_file, history_file) 50 readline.set_completer(rlcompleter.Completer(env).complete)
51 52
53 -def exec_environment( 54 pyfile='', 55 request=None, 56 response=None, 57 session=None, 58 ):
59 """ 60 .. function:: gluon.shell.exec_environment([pyfile=''[, request=Request() 61 [, response=Response[, session=Session()]]]]) 62 63 Environment builder and module loader. 64 65 66 Builds a web2py environment and optionally executes a Python 67 file into the environment. 68 A Storage dictionary containing the resulting environment is returned. 69 The working directory must be web2py root -- this is the web2py default. 70 71 """ 72 73 if request is None: 74 request = Request({}) 75 if response is None: 76 response = Response() 77 if session is None: 78 session = Session() 79 80 if request.folder is None: 81 mo = re.match(r'(|.*/)applications/(?P<appname>[^/]+)', pyfile) 82 if mo: 83 appname = mo.group('appname') 84 request.folder = os.path.join('applications', appname) 85 else: 86 request.folder = '' 87 env = build_environment(request, response, session, store_current=False) 88 if pyfile: 89 pycfile = pyfile + 'c' 90 if os.path.isfile(pycfile): 91 exec read_pyc(pycfile) in env 92 else: 93 execfile(pyfile, env) 94 return Storage(env)
95 96
97 -def env( 98 a, 99 import_models=False, 100 c=None, 101 f=None, 102 dir='', 103 extra_request={}, 104 ):
105 """ 106 Return web2py execution environment for application (a), controller (c), 107 function (f). 108 If import_models is True the exec all application models into the 109 environment. 110 111 extra_request allows you to pass along any extra 112 variables to the request object before your models 113 get executed. This was mainly done to support 114 web2py_utils.test_runner, however you can use it 115 with any wrapper scripts that need access to the 116 web2py environment. 117 """ 118 119 request = Request({}) 120 response = Response() 121 session = Session() 122 request.application = a 123 124 # Populate the dummy environment with sensible defaults. 125 126 if not dir: 127 request.folder = os.path.join('applications', a) 128 else: 129 request.folder = dir 130 request.controller = c or 'default' 131 request.function = f or 'index' 132 response.view = '%s/%s.html' % (request.controller, 133 request.function) 134 request.env.http_host = '127.0.0.1:8000' 135 request.env.remote_addr = '127.0.0.1' 136 request.env.web2py_runtime_gae = global_settings.web2py_runtime_gae 137 138 for k, v in extra_request.items(): 139 request[k] = v 140 141 path_info = '/%s/%s/%s' % (a, c, f) 142 if request.args: 143 path_info = '%s/%s' % (path_info, '/'.join(request.args)) 144 if request.vars: 145 vars = ['%s=%s' % (k,v) if v else '%s' % k 146 for (k,v) in request.vars.iteritems()] 147 path_info = '%s?%s' % (path_info, '&'.join(vars)) 148 request.env.path_info = path_info 149 150 # Monkey patch so credentials checks pass. 151 152 def check_credentials(request, other_application='admin'): 153 return True
154 155 fileutils.check_credentials = check_credentials 156 157 environment = build_environment(request, response, session) 158 159 if import_models: 160 try: 161 run_models_in(environment) 162 except RestrictedError, e: 163 sys.stderr.write(e.traceback + '\n') 164 sys.exit(1) 165 166 environment['__name__'] = '__main__' 167 return environment 168 169
170 -def exec_pythonrc():
171 pythonrc = os.environ.get('PYTHONSTARTUP') 172 if pythonrc and os.path.isfile(pythonrc): 173 def execfile_getlocals(file): 174 execfile(file) 175 return locals()
176 try: 177 return execfile_getlocals(pythonrc) 178 except NameError: 179 pass 180 return dict() 181 182
183 -def run( 184 appname, 185 plain=False, 186 import_models=False, 187 startfile=None, 188 bpython=False, 189 python_code=False, 190 cronjob=False):
191 """ 192 Start interactive shell or run Python script (startfile) in web2py 193 controller environment. appname is formatted like: 194 195 a web2py application name 196 a/c exec the controller c into the application environment 197 """ 198 199 (a, c, f, args, vars) = parse_path_info(appname, av=True) 200 errmsg = 'invalid application name: %s' % appname 201 if not a: 202 die(errmsg) 203 adir = os.path.join('applications', a) 204 205 if not os.path.exists(adir): 206 if sys.stdin and not sys.stdin.name == '/dev/null': 207 confirm = raw_input( 208 'application %s does not exist, create (y/n)?' % a) 209 else: 210 logging.warn('application does not exist and will not be created') 211 return 212 if confirm.lower() in ['y', 'yes']: 213 214 os.mkdir(adir) 215 w2p_unpack('welcome.w2p', adir) 216 for subfolder in ['models', 'views', 'controllers', 'databases', 217 'modules', 'cron', 'errors', 'sessions', 218 'languages', 'static', 'private', 'uploads']: 219 subpath = os.path.join(adir, subfolder) 220 if not os.path.exists(subpath): 221 os.mkdir(subpath) 222 db = os.path.join(adir, 'models/db.py') 223 if os.path.exists(db): 224 data = fileutils.read_file(db) 225 data = data.replace( 226 '<your secret key>', 'sha512:' + web2py_uuid()) 227 fileutils.write_file(db, data) 228 229 if c: 230 import_models = True 231 extra_request = {} 232 if args: 233 extra_request['args'] = args 234 if vars: 235 extra_request['vars'] = vars 236 _env = env(a, c=c, f=f, import_models=import_models, extra_request=extra_request) 237 if c: 238 pyfile = os.path.join('applications', a, 'controllers', c + '.py') 239 pycfile = os.path.join('applications', a, 'compiled', 240 "controllers_%s_%s.pyc" % (c, f)) 241 if ((cronjob and os.path.isfile(pycfile)) 242 or not os.path.isfile(pyfile)): 243 exec read_pyc(pycfile) in _env 244 elif os.path.isfile(pyfile): 245 execfile(pyfile, _env) 246 else: 247 die(errmsg) 248 249 if f: 250 exec ('print %s()' % f, _env) 251 return 252 253 _env.update(exec_pythonrc()) 254 if startfile: 255 try: 256 ccode = None 257 if startfile.endswith('.pyc'): 258 ccode = read_pyc(startfile) 259 exec ccode in _env 260 else: 261 execfile(startfile, _env) 262 263 if import_models: 264 BaseAdapter.close_all_instances('commit') 265 except Exception, e: 266 print traceback.format_exc() 267 if import_models: 268 BaseAdapter.close_all_instances('rollback') 269 elif python_code: 270 try: 271 exec(python_code, _env) 272 if import_models: 273 BaseAdapter.close_all_instances('commit') 274 except Exception, e: 275 print traceback.format_exc() 276 if import_models: 277 BaseAdapter.close_all_instances('rollback') 278 else: 279 if not plain: 280 if bpython: 281 try: 282 import bpython 283 bpython.embed(locals_=_env) 284 return 285 except: 286 logger.warning( 287 'import bpython error; trying ipython...') 288 else: 289 try: 290 import IPython 291 if IPython.__version__ > '1.0.0': 292 IPython.start_ipython(user_ns=_env) 293 return 294 elif IPython.__version__ == '1.0.0': 295 from IPython.terminal.embed import InteractiveShellEmbed 296 shell = InteractiveShellEmbed(user_ns=_env) 297 shell() 298 return 299 elif IPython.__version__ >= '0.11': 300 from IPython.frontend.terminal.embed import InteractiveShellEmbed 301 shell = InteractiveShellEmbed(user_ns=_env) 302 shell() 303 return 304 else: 305 # following 2 lines fix a problem with 306 # IPython; thanks Michael Toomim 307 if '__builtins__' in _env: 308 del _env['__builtins__'] 309 shell = IPython.Shell.IPShell(argv=[], user_ns=_env) 310 shell.mainloop() 311 return 312 except: 313 logger.warning( 314 'import IPython error; use default python shell') 315 enable_autocomplete_and_history(adir,_env) 316 code.interact(local=_env)
317 318
319 -def parse_path_info(path_info, av=False):
320 """ 321 Parse path info formatted like a/c/f where c and f are optional 322 and a leading / accepted. 323 Return tuple (a, c, f). If invalid path_info a is set to None. 324 If c or f are omitted they are set to None. 325 If av=True, parse args and vars 326 """ 327 if av: 328 vars = None 329 if '?' in path_info: 330 path_info, query = path_info.split('?', 2) 331 vars = Storage() 332 for var in query.split('&'): 333 (var, val) = var.split('=', 2) if '=' in var else (var, None) 334 vars[var] = val 335 items = List(path_info.split('/')) 336 args = List(items[3:]) if len(items) > 3 else None 337 return (items(0), items(1), items(2), args, vars) 338 339 mo = re.match(r'^/?(?P<a>\w+)(/(?P<c>\w+)(/(?P<f>\w+))?)?$', 340 path_info) 341 if mo: 342 return (mo.group('a'), mo.group('c'), mo.group('f')) 343 else: 344 return (None, None, None)
345 346
347 -def die(msg):
348 print >> sys.stderr, msg 349 sys.exit(1)
350 351
352 -def test(testpath, import_models=True, verbose=False):
353 """ 354 Run doctests in web2py environment. testpath is formatted like: 355 356 a tests all controllers in application a 357 a/c tests controller c in application a 358 a/c/f test function f in controller c, application a 359 360 Where a, c and f are application, controller and function names 361 respectively. If the testpath is a file name the file is tested. 362 If a controller is specified models are executed by default. 363 """ 364 365 import doctest 366 if os.path.isfile(testpath): 367 mo = re.match(r'(|.*/)applications/(?P<a>[^/]+)', testpath) 368 if not mo: 369 die('test file is not in application directory: %s' 370 % testpath) 371 a = mo.group('a') 372 c = f = None 373 files = [testpath] 374 else: 375 (a, c, f) = parse_path_info(testpath) 376 errmsg = 'invalid test path: %s' % testpath 377 if not a: 378 die(errmsg) 379 cdir = os.path.join('applications', a, 'controllers') 380 if not os.path.isdir(cdir): 381 die(errmsg) 382 if c: 383 cfile = os.path.join(cdir, c + '.py') 384 if not os.path.isfile(cfile): 385 die(errmsg) 386 files = [cfile] 387 else: 388 files = glob.glob(os.path.join(cdir, '*.py')) 389 for testfile in files: 390 globs = env(a, import_models) 391 ignores = globs.keys() 392 execfile(testfile, globs) 393 394 def doctest_object(name, obj): 395 """doctest obj and enclosed methods and classes.""" 396 397 if type(obj) in (types.FunctionType, types.TypeType, 398 types.ClassType, types.MethodType, 399 types.UnboundMethodType): 400 401 # Reload environment before each test. 402 403 globs = env(a, c=c, f=f, import_models=import_models) 404 execfile(testfile, globs) 405 doctest.run_docstring_examples( 406 obj, globs=globs, 407 name='%s: %s' % (os.path.basename(testfile), 408 name), verbose=verbose) 409 if type(obj) in (types.TypeType, types.ClassType): 410 for attr_name in dir(obj): 411 412 # Execute . operator so decorators are executed. 413 414 o = eval('%s.%s' % (name, attr_name), globs) 415 doctest_object(attr_name, o)
416 417 for (name, obj) in globs.items(): 418 if name not in ignores and (f is None or f == name): 419 doctest_object(name, obj) 420 421
422 -def get_usage():
423 usage = """ 424 %prog [options] pythonfile 425 """ 426 return usage
427 428
429 -def execute_from_command_line(argv=None):
430 if argv is None: 431 argv = sys.argv 432 433 parser = optparse.OptionParser(usage=get_usage()) 434 435 parser.add_option('-S', '--shell', dest='shell', metavar='APPNAME', 436 help='run web2py in interactive shell ' + 437 'or IPython(if installed) with specified appname') 438 msg = 'run web2py in interactive shell or bpython (if installed) with' 439 msg += ' specified appname (if app does not exist it will be created).' 440 msg += '\n Use combined with --shell' 441 parser.add_option( 442 '-B', 443 '--bpython', 444 action='store_true', 445 default=False, 446 dest='bpython', 447 help=msg, 448 ) 449 parser.add_option( 450 '-P', 451 '--plain', 452 action='store_true', 453 default=False, 454 dest='plain', 455 help='only use plain python shell, should be used with --shell option', 456 ) 457 parser.add_option( 458 '-M', 459 '--import_models', 460 action='store_true', 461 default=False, 462 dest='import_models', 463 help='auto import model files, default is False, ' + 464 ' should be used with --shell option', 465 ) 466 parser.add_option( 467 '-R', 468 '--run', 469 dest='run', 470 metavar='PYTHON_FILE', 471 default='', 472 help='run PYTHON_FILE in web2py environment, ' + 473 'should be used with --shell option', 474 ) 475 476 (options, args) = parser.parse_args(argv[1:]) 477 478 if len(sys.argv) == 1: 479 parser.print_help() 480 sys.exit(0) 481 482 if len(args) > 0: 483 startfile = args[0] 484 else: 485 startfile = '' 486 run(options.shell, options.plain, startfile=startfile, 487 bpython=options.bpython)
488 489 490 if __name__ == '__main__': 491 execute_from_command_line() 492