Package web2py :: Package gluon :: Module shell

[hide private]
[frames] | no frames]

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