Back to index

moin  1.9.0~rc2
script.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 r'''
00003     werkzeug.script
00004     ~~~~~~~~~~~~~~~
00005 
00006     Most of the time you have recurring tasks while writing an application
00007     such as starting up an interactive python interpreter with some prefilled
00008     imports, starting the development server, initializing the database or
00009     something similar.
00010 
00011     For that purpose werkzeug provides the `werkzeug.script` module which
00012     helps you writing such scripts.
00013 
00014 
00015     Basic Usage
00016     -----------
00017 
00018     The following snippet is roughly the same in every werkzeug script::
00019 
00020         #!/usr/bin/env python
00021         # -*- coding: utf-8 -*-
00022         from werkzeug import script
00023 
00024         # actions go here
00025 
00026         if __name__ == '__main__':
00027             script.run()
00028 
00029     Starting this script now does nothing because no actions are defined.
00030     An action is a function in the same module starting with ``"action_"``
00031     which takes a number of arguments where every argument has a default.  The
00032     type of the default value specifies the type of the argument.
00033 
00034     Arguments can then be passed by position or using ``--name=value`` from
00035     the shell.
00036 
00037     Because a runserver and shell command is pretty common there are two
00038     factory functions that create such commands::
00039 
00040         def make_app():
00041             from yourapplication import YourApplication
00042             return YourApplication(...)
00043 
00044         action_runserver = script.make_runserver(make_app, use_reloader=True)
00045         action_shell = script.make_shell(lambda: {'app': make_app()})
00046 
00047 
00048     Using The Scripts
00049     -----------------
00050 
00051     The script from above can be used like this from the shell now:
00052 
00053     .. sourcecode:: text
00054 
00055         $ ./manage.py --help
00056         $ ./manage.py runserver localhost 8080 --debugger --no-reloader
00057         $ ./manage.py runserver -p 4000
00058         $ ./manage.py shell
00059 
00060     As you can see it's possible to pass parameters as positional arguments
00061     or as named parameters, pretty much like Python function calls.
00062 
00063 
00064     :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
00065     :license: BSD, see LICENSE for more details.
00066 '''
00067 import sys
00068 import inspect
00069 import getopt
00070 from os.path import basename
00071 
00072 
00073 argument_types = {
00074     bool:       'boolean',
00075     str:        'string',
00076     int:        'integer',
00077     float:      'float'
00078 }
00079 
00080 
00081 converters = {
00082     'boolean':  lambda x: x.lower() in ('1', 'true', 'yes', 'on'),
00083     'string':   str,
00084     'integer':  int,
00085     'float':    float
00086 }
00087 
00088 
00089 def run(namespace=None, action_prefix='action_', args=None):
00090     """Run the script.  Participating actions are looked up in the callers
00091     namespace if no namespace is given, otherwise in the dict provided.
00092     Only items that start with action_prefix are processed as actions.  If
00093     you want to use all items in the namespace provided as actions set
00094     action_prefix to an empty string.
00095 
00096     :param namespace: An optional dict where the functions are looked up in.
00097                       By default the local namespace of the caller is used.
00098     :param action_prefix: The prefix for the functions.  Everything else
00099                           is ignored.
00100     :param args: the arguments for the function.  If not specified
00101                  :data:`sys.argv` without the first argument is used.
00102     """
00103     if namespace is None:
00104         namespace = sys._getframe(1).f_locals
00105     actions = find_actions(namespace, action_prefix)
00106 
00107     if args is None:
00108         args = sys.argv[1:]
00109     if not args or args[0] in ('-h', '--help'):
00110         return print_usage(actions)
00111     elif args[0] not in actions:
00112         fail('Unknown action \'%s\'' % args[0])
00113 
00114     arguments = {}
00115     conv = {}
00116     key_to_arg = {}
00117     long_options = []
00118     formatstring = ''
00119     func, doc, arg_def = actions[args.pop(0)]
00120     for idx, (arg, shortcut, default, option_type) in enumerate(arg_def):
00121         real_arg = arg.replace('-', '_')
00122         converter = converters[option_type]
00123         if shortcut:
00124             formatstring += shortcut
00125             if not isinstance(default, bool):
00126                 formatstring += ':'
00127             key_to_arg['-' + shortcut] = real_arg
00128         long_options.append(isinstance(default, bool) and arg or arg + '=')
00129         key_to_arg['--' + arg] = real_arg
00130         key_to_arg[idx] = real_arg
00131         conv[real_arg] = converter
00132         arguments[real_arg] = default
00133 
00134     try:
00135         optlist, posargs = getopt.gnu_getopt(args, formatstring, long_options)
00136     except getopt.GetoptError, e:
00137         fail(str(e))
00138 
00139     specified_arguments = set()
00140     for key, value in enumerate(posargs):
00141         try:
00142             arg = key_to_arg[key]
00143         except IndexError:
00144             fail('Too many parameters')
00145         specified_arguments.add(arg)
00146         try:
00147             arguments[arg] = conv[arg](value)
00148         except ValueError:
00149             fail('Invalid value for argument %s (%s): %s' % (key, arg, value))
00150 
00151     for key, value in optlist:
00152         arg = key_to_arg[key]
00153         if arg in specified_arguments:
00154             fail('Argument \'%s\' is specified twice' % arg)
00155         if arg.startswith('no_'):
00156             value = 'no'
00157         elif not value:
00158             value = 'yes'
00159         try:
00160             arguments[arg] = conv[arg](value)
00161         except ValueError:
00162             fail('Invalid value for \'%s\': %s' % (key, value))
00163 
00164     newargs = {}
00165     for k, v in arguments.iteritems():
00166         newargs[k.startswith('no_') and k[3:] or k] = v
00167     arguments = newargs
00168     return func(**arguments)
00169 
00170 
00171 def fail(message, code=-1):
00172     """Fail with an error."""
00173     print >> sys.stderr, 'Error:', message
00174     sys.exit(code)
00175 
00176 
00177 def find_actions(namespace, action_prefix):
00178     """Find all the actions in the namespace."""
00179     actions = {}
00180     for key, value in namespace.iteritems():
00181         if key.startswith(action_prefix):
00182             actions[key[len(action_prefix):]] = analyse_action(value)
00183     return actions
00184 
00185 
00186 def print_usage(actions):
00187     """Print the usage information.  (Help screen)"""
00188     actions = actions.items()
00189     actions.sort()
00190     print 'usage: %s <action> [<options>]' % basename(sys.argv[0])
00191     print '       %s --help' % basename(sys.argv[0])
00192     print
00193     print 'actions:'
00194     for name, (func, doc, arguments) in actions:
00195         print '  %s:' % name
00196         for line in doc.splitlines():
00197             print '    %s' % line
00198         if arguments:
00199             print
00200         for arg, shortcut, default, argtype in arguments:
00201             if isinstance(default, bool):
00202                 print '    %s' % (
00203                     (shortcut and '-%s, ' % shortcut or '') + '--' + arg
00204                 )
00205             else:
00206                 print '    %-30s%-10s%s' % (
00207                     (shortcut and '-%s, ' % shortcut or '') + '--' + arg,
00208                     argtype, default
00209                 )
00210         print
00211 
00212 
00213 def analyse_action(func):
00214     """Analyse a function."""
00215     description = inspect.getdoc(func) or 'undocumented action'
00216     arguments = []
00217     args, varargs, kwargs, defaults = inspect.getargspec(func)
00218     if varargs or kwargs:
00219         raise TypeError('variable length arguments for action not allowed.')
00220     if len(args) != len(defaults or ()):
00221         raise TypeError('not all arguments have proper definitions')
00222 
00223     for idx, (arg, definition) in enumerate(zip(args, defaults or ())):
00224         if arg.startswith('_'):
00225             raise TypeError('arguments may not start with an underscore')
00226         if not isinstance(definition, tuple):
00227             shortcut = None
00228             default = definition
00229         else:
00230             shortcut, default = definition
00231         argument_type = argument_types[type(default)]
00232         if isinstance(default, bool) and default is True:
00233             arg = 'no-' + arg
00234         arguments.append((arg.replace('_', '-'), shortcut,
00235                           default, argument_type))
00236     return func, description, arguments
00237 
00238 
00239 def make_shell(init_func=None, banner=None, use_ipython=True):
00240     """Returns an action callback that spawns a new interactive
00241     python shell.
00242 
00243     :param init_func: an optional initialization function that is
00244                       called before the shell is started.  The return
00245                       value of this function is the initial namespace.
00246     :param banner: the banner that is displayed before the shell.  If
00247                    not specified a generic banner is used instead.
00248     :param use_ipython: if set to `True` ipython is used if available.
00249     """
00250     if banner is None:
00251         banner = 'Interactive Werkzeug Shell'
00252     if init_func is None:
00253         init_func = dict
00254     def action(ipython=use_ipython):
00255         """Start a new interactive python session."""
00256         namespace = init_func()
00257         if ipython:
00258             try:
00259                 import IPython
00260             except ImportError:
00261                 pass
00262             else:
00263                 sh = IPython.Shell.IPShellEmbed(banner=banner)
00264                 sh(global_ns={}, local_ns=namespace)
00265                 return
00266         from code import interact
00267         interact(banner, local=namespace)
00268     return action
00269 
00270 
00271 def make_runserver(app_factory, hostname='localhost', port=5000,
00272                    use_reloader=False, use_debugger=False, use_evalex=True,
00273                    threaded=False, processes=1, static_files=None,
00274                    extra_files=None):
00275     """Returns an action callback that spawns a new development server.
00276 
00277     .. versionadded:: 0.5
00278        `static_files` and `extra_files` was added.
00279 
00280     :param app_factory: a function that returns a new WSGI application.
00281     :param hostname: the default hostname the server should listen on.
00282     :param port: the default port of the server.
00283     :param use_reloader: the default setting for the reloader.
00284     :param use_evalex: the default setting for the evalex flag of the debugger.
00285     :param threaded: the default threading setting.
00286     :param processes: the default number of processes to start.
00287     :param static_files: optionally a dict of static files.
00288     :param extra_files: optionally a list of extra files to track for reloading.
00289     """
00290     def action(hostname=('h', hostname), port=('p', port),
00291                reloader=use_reloader, debugger=use_debugger,
00292                evalex=use_evalex, threaded=threaded, processes=processes):
00293         """Start a new development server."""
00294         from werkzeug.serving import run_simple
00295         app = app_factory()
00296         run_simple(hostname, port, app, reloader, debugger, evalex,
00297                    extra_files, 1, threaded, processes,
00298                    static_files=static_files)
00299     return action