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 if global_settings.cmd_options: 135 ip = global_settings.cmd_options.ip 136 port = global_settings.cmd_options.port 137 else: 138 ip, port = '127.0.0.1', '8000' 139 request.env.http_host = '%s:%s' % (ip,port) 140 request.env.remote_addr = '127.0.0.1' 141 request.env.web2py_runtime_gae = global_settings.web2py_runtime_gae 142 143 for k, v in extra_request.items(): 144 request[k] = v 145 146 path_info = '/%s/%s/%s' % (a, c, f) 147 if request.args: 148 path_info = '%s/%s' % (path_info, '/'.join(request.args)) 149 if request.vars: 150 vars = ['%s=%s' % (k,v) if v else '%s' % k 151 for (k,v) in request.vars.iteritems()] 152 path_info = '%s?%s' % (path_info, '&'.join(vars)) 153 request.env.path_info = path_info 154 155 # Monkey patch so credentials checks pass. 156 157 def check_credentials(request, other_application='admin'): 158 return True
159 160 fileutils.check_credentials = check_credentials 161 162 environment = build_environment(request, response, session) 163 164 if import_models: 165 try: 166 run_models_in(environment) 167 except RestrictedError, e: 168 sys.stderr.write(e.traceback + '\n') 169 sys.exit(1) 170 171 environment['__name__'] = '__main__' 172 return environment 173 174
175 -def exec_pythonrc():
176 pythonrc = os.environ.get('PYTHONSTARTUP') 177 if pythonrc and os.path.isfile(pythonrc): 178 def execfile_getlocals(file): 179 execfile(file) 180 return locals()
181 try: 182 return execfile_getlocals(pythonrc) 183 except NameError: 184 pass 185 return dict() 186 187
188 -def run( 189 appname, 190 plain=False, 191 import_models=False, 192 startfile=None, 193 bpython=False, 194 python_code=False, 195 cronjob=False):
196 """ 197 Start interactive shell or run Python script (startfile) in web2py 198 controller environment. appname is formatted like: 199 200 a web2py application name 201 a/c exec the controller c into the application environment 202 """ 203 204 (a, c, f, args, vars) = parse_path_info(appname, av=True) 205 errmsg = 'invalid application name: %s' % appname 206 if not a: 207 die(errmsg) 208 adir = os.path.join('applications', a) 209 210 if not os.path.exists(adir): 211 if sys.stdin and not sys.stdin.name == '/dev/null': 212 confirm = raw_input( 213 'application %s does not exist, create (y/n)?' % a) 214 else: 215 logging.warn('application does not exist and will not be created') 216 return 217 if confirm.lower() in ['y', 'yes']: 218 219 os.mkdir(adir) 220 w2p_unpack('welcome.w2p', adir) 221 for subfolder in ['models', 'views', 'controllers', 'databases', 222 'modules', 'cron', 'errors', 'sessions', 223 'languages', 'static', 'private', 'uploads']: 224 subpath = os.path.join(adir, subfolder) 225 if not os.path.exists(subpath): 226 os.mkdir(subpath) 227 db = os.path.join(adir, 'models/db.py') 228 if os.path.exists(db): 229 data = fileutils.read_file(db) 230 data = data.replace( 231 '<your secret key>', 'sha512:' + web2py_uuid()) 232 fileutils.write_file(db, data) 233 234 if c: 235 import_models = True 236 extra_request = {} 237 if args: 238 extra_request['args'] = args 239 if vars: 240 extra_request['vars'] = vars 241 _env = env(a, c=c, f=f, import_models=import_models, extra_request=extra_request) 242 if c: 243 pyfile = os.path.join('applications', a, 'controllers', c + '.py') 244 pycfile = os.path.join('applications', a, 'compiled', 245 "controllers_%s_%s.pyc" % (c, f)) 246 if ((cronjob and os.path.isfile(pycfile)) 247 or not os.path.isfile(pyfile)): 248 exec read_pyc(pycfile) in _env 249 elif os.path.isfile(pyfile): 250 execfile(pyfile, _env) 251 else: 252 die(errmsg) 253 254 if f: 255 exec ('print %s()' % f, _env) 256 return 257 258 _env.update(exec_pythonrc()) 259 if startfile: 260 try: 261 ccode = None 262 if startfile.endswith('.pyc'): 263 ccode = read_pyc(startfile) 264 exec ccode in _env 265 else: 266 execfile(startfile, _env) 267 268 if import_models: 269 BaseAdapter.close_all_instances('commit') 270 except Exception, e: 271 print traceback.format_exc() 272 if import_models: 273 BaseAdapter.close_all_instances('rollback') 274 elif python_code: 275 try: 276 exec(python_code, _env) 277 if import_models: 278 BaseAdapter.close_all_instances('commit') 279 except Exception, e: 280 print traceback.format_exc() 281 if import_models: 282 BaseAdapter.close_all_instances('rollback') 283 else: 284 if not plain: 285 if bpython: 286 try: 287 import bpython 288 bpython.embed(locals_=_env) 289 return 290 except: 291 logger.warning( 292 'import bpython error; trying ipython...') 293 else: 294 try: 295 import IPython 296 if IPython.__version__ > '1.0.0': 297 IPython.start_ipython(user_ns=_env) 298 return 299 elif IPython.__version__ == '1.0.0': 300 from IPython.terminal.embed import InteractiveShellEmbed 301 shell = InteractiveShellEmbed(user_ns=_env) 302 shell() 303 return 304 elif IPython.__version__ >= '0.11': 305 from IPython.frontend.terminal.embed import InteractiveShellEmbed 306 shell = InteractiveShellEmbed(user_ns=_env) 307 shell() 308 return 309 else: 310 # following 2 lines fix a problem with 311 # IPython; thanks Michael Toomim 312 if '__builtins__' in _env: 313 del _env['__builtins__'] 314 shell = IPython.Shell.IPShell(argv=[], user_ns=_env) 315 shell.mainloop() 316 return 317 except: 318 logger.warning( 319 'import IPython error; use default python shell') 320 enable_autocomplete_and_history(adir,_env) 321 code.interact(local=_env)
322 323
324 -def parse_path_info(path_info, av=False):
325 """ 326 Parse path info formatted like a/c/f where c and f are optional 327 and a leading / accepted. 328 Return tuple (a, c, f). If invalid path_info a is set to None. 329 If c or f are omitted they are set to None. 330 If av=True, parse args and vars 331 """ 332 if av: 333 vars = None 334 if '?' in path_info: 335 path_info, query = path_info.split('?', 2) 336 vars = Storage() 337 for var in query.split('&'): 338 (var, val) = var.split('=', 2) if '=' in var else (var, None) 339 vars[var] = val 340 items = List(path_info.split('/')) 341 args = List(items[3:]) if len(items) > 3 else None 342 return (items(0), items(1), items(2), args, vars) 343 344 mo = re.match(r'^/?(?P<a>\w+)(/(?P<c>\w+)(/(?P<f>\w+))?)?$', 345 path_info) 346 if mo: 347 return (mo.group('a'), mo.group('c'), mo.group('f')) 348 else: 349 return (None, None, None)
350 351
352 -def die(msg):
353 print >> sys.stderr, msg 354 sys.exit(1)
355 356
357 -def test(testpath, import_models=True, verbose=False):
358 """ 359 Run doctests in web2py environment. testpath is formatted like: 360 361 a tests all controllers in application a 362 a/c tests controller c in application a 363 a/c/f test function f in controller c, application a 364 365 Where a, c and f are application, controller and function names 366 respectively. If the testpath is a file name the file is tested. 367 If a controller is specified models are executed by default. 368 """ 369 370 import doctest 371 if os.path.isfile(testpath): 372 mo = re.match(r'(|.*/)applications/(?P<a>[^/]+)', testpath) 373 if not mo: 374 die('test file is not in application directory: %s' 375 % testpath) 376 a = mo.group('a') 377 c = f = None 378 files = [testpath] 379 else: 380 (a, c, f) = parse_path_info(testpath) 381 errmsg = 'invalid test path: %s' % testpath 382 if not a: 383 die(errmsg) 384 cdir = os.path.join('applications', a, 'controllers') 385 if not os.path.isdir(cdir): 386 die(errmsg) 387 if c: 388 cfile = os.path.join(cdir, c + '.py') 389 if not os.path.isfile(cfile): 390 die(errmsg) 391 files = [cfile] 392 else: 393 files = glob.glob(os.path.join(cdir, '*.py')) 394 for testfile in files: 395 globs = env(a, import_models) 396 ignores = globs.keys() 397 execfile(testfile, globs) 398 399 def doctest_object(name, obj): 400 """doctest obj and enclosed methods and classes.""" 401 402 if type(obj) in (types.FunctionType, types.TypeType, 403 types.ClassType, types.MethodType, 404 types.UnboundMethodType): 405 406 # Reload environment before each test. 407 408 globs = env(a, c=c, f=f, import_models=import_models) 409 execfile(testfile, globs) 410 doctest.run_docstring_examples( 411 obj, globs=globs, 412 name='%s: %s' % (os.path.basename(testfile), 413 name), verbose=verbose) 414 if type(obj) in (types.TypeType, types.ClassType): 415 for attr_name in dir(obj): 416 417 # Execute . operator so decorators are executed. 418 419 o = eval('%s.%s' % (name, attr_name), globs) 420 doctest_object(attr_name, o)
421 422 for (name, obj) in globs.items(): 423 if name not in ignores and (f is None or f == name): 424 doctest_object(name, obj) 425 426
427 -def get_usage():
428 usage = """ 429 %prog [options] pythonfile 430 """ 431 return usage
432 433
434 -def execute_from_command_line(argv=None):
435 if argv is None: 436 argv = sys.argv 437 438 parser = optparse.OptionParser(usage=get_usage()) 439 440 parser.add_option('-S', '--shell', dest='shell', metavar='APPNAME', 441 help='run web2py in interactive shell ' + 442 'or IPython(if installed) with specified appname') 443 msg = 'run web2py in interactive shell or bpython (if installed) with' 444 msg += ' specified appname (if app does not exist it will be created).' 445 msg += '\n Use combined with --shell' 446 parser.add_option( 447 '-B', 448 '--bpython', 449 action='store_true', 450 default=False, 451 dest='bpython', 452 help=msg, 453 ) 454 parser.add_option( 455 '-P', 456 '--plain', 457 action='store_true', 458 default=False, 459 dest='plain', 460 help='only use plain python shell, should be used with --shell option', 461 ) 462 parser.add_option( 463 '-M', 464 '--import_models', 465 action='store_true', 466 default=False, 467 dest='import_models', 468 help='auto import model files, default is False, ' + 469 ' should be used with --shell option', 470 ) 471 parser.add_option( 472 '-R', 473 '--run', 474 dest='run', 475 metavar='PYTHON_FILE', 476 default='', 477 help='run PYTHON_FILE in web2py environment, ' + 478 'should be used with --shell option', 479 ) 480 481 (options, args) = parser.parse_args(argv[1:]) 482 483 if len(sys.argv) == 1: 484 parser.print_help() 485 sys.exit(0) 486 487 if len(args) > 0: 488 startfile = args[0] 489 else: 490 startfile = '' 491 run(options.shell, options.plain, startfile=startfile, 492 bpython=options.bpython)
493 494 495 if __name__ == '__main__': 496 execute_from_command_line() 497