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