1
# -*- coding: utf-8 -*-
6
Most of the time you have recurring tasks while writing an application
7
such as starting up an interactive python interpreter with some prefilled
8
imports, starting the development server, initializing the database or
11
For that purpose werkzeug provides the `werkzeug.script` module which
12
helps you writing such scripts.
18
The following snippet is roughly the same in every werkzeug script::
21
# -*- coding: utf-8 -*-
22
from werkzeug import script
26
if __name__ == '__main__':
29
Starting this script now does nothing because no actions are defined.
30
An action is a function in the same module starting with ``"action_"``
31
which takes a number of arguments where every argument has a default. The
32
type of the default value specifies the type of the argument.
34
Arguments can then be passed by position or using ``--name=value`` from
37
Because a runserver and shell command is pretty common there are two
38
factory functions that create such commands::
41
from yourapplication import YourApplication
42
return YourApplication(...)
44
action_runserver = script.make_runserver(make_app, use_reloader=True)
45
action_shell = script.make_shell(lambda: {'app': make_app()})
51
The script from above can be used like this from the shell now:
56
$ ./manage.py runserver localhost 8080 --debugger --no-reloader
57
$ ./manage.py runserver -p 4000
60
As you can see it's possible to pass parameters as positional arguments
61
or as named parameters, pretty much like Python function calls.
64
:copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
65
:license: BSD, see LICENSE for more details.
70
from os.path import basename
82
'boolean': lambda x: x.lower() in ('1', 'true', 'yes', 'on'),
89
def run(namespace=None, action_prefix='action_', args=None):
90
"""Run the script. Participating actions are looked up in the callers
91
namespace if no namespace is given, otherwise in the dict provided.
92
Only items that start with action_prefix are processed as actions. If
93
you want to use all items in the namespace provided as actions set
94
action_prefix to an empty string.
96
:param namespace: An optional dict where the functions are looked up in.
97
By default the local namespace of the caller is used.
98
:param action_prefix: The prefix for the functions. Everything else
100
:param args: the arguments for the function. If not specified
101
:data:`sys.argv` without the first argument is used.
103
if namespace is None:
104
namespace = sys._getframe(1).f_locals
105
actions = find_actions(namespace, action_prefix)
109
if not args or args[0] in ('-h', '--help'):
110
return print_usage(actions)
111
elif args[0] not in actions:
112
fail('Unknown action \'%s\'' % args[0])
119
func, doc, arg_def = actions[args.pop(0)]
120
for idx, (arg, shortcut, default, option_type) in enumerate(arg_def):
121
real_arg = arg.replace('-', '_')
122
converter = converters[option_type]
124
formatstring += shortcut
125
if not isinstance(default, bool):
127
key_to_arg['-' + shortcut] = real_arg
128
long_options.append(isinstance(default, bool) and arg or arg + '=')
129
key_to_arg['--' + arg] = real_arg
130
key_to_arg[idx] = real_arg
131
conv[real_arg] = converter
132
arguments[real_arg] = default
135
optlist, posargs = getopt.gnu_getopt(args, formatstring, long_options)
136
except getopt.GetoptError, e:
139
specified_arguments = set()
140
for key, value in enumerate(posargs):
142
arg = key_to_arg[key]
144
fail('Too many parameters')
145
specified_arguments.add(arg)
147
arguments[arg] = conv[arg](value)
149
fail('Invalid value for argument %s (%s): %s' % (key, arg, value))
151
for key, value in optlist:
152
arg = key_to_arg[key]
153
if arg in specified_arguments:
154
fail('Argument \'%s\' is specified twice' % arg)
155
if arg.startswith('no_'):
160
arguments[arg] = conv[arg](value)
162
fail('Invalid value for \'%s\': %s' % (key, value))
165
for k, v in arguments.iteritems():
166
newargs[k.startswith('no_') and k[3:] or k] = v
168
return func(**arguments)
171
def fail(message, code=-1):
172
"""Fail with an error."""
173
print >> sys.stderr, 'Error:', message
177
def find_actions(namespace, action_prefix):
178
"""Find all the actions in the namespace."""
180
for key, value in namespace.iteritems():
181
if key.startswith(action_prefix):
182
actions[key[len(action_prefix):]] = analyse_action(value)
186
def print_usage(actions):
187
"""Print the usage information. (Help screen)"""
188
actions = actions.items()
190
print 'usage: %s <action> [<options>]' % basename(sys.argv[0])
191
print ' %s --help' % basename(sys.argv[0])
194
for name, (func, doc, arguments) in actions:
196
for line in doc.splitlines():
200
for arg, shortcut, default, argtype in arguments:
201
if isinstance(default, bool):
203
(shortcut and '-%s, ' % shortcut or '') + '--' + arg
206
print ' %-30s%-10s%s' % (
207
(shortcut and '-%s, ' % shortcut or '') + '--' + arg,
213
def analyse_action(func):
214
"""Analyse a function."""
215
description = inspect.getdoc(func) or 'undocumented action'
217
args, varargs, kwargs, defaults = inspect.getargspec(func)
218
if varargs or kwargs:
219
raise TypeError('variable length arguments for action not allowed.')
220
if len(args) != len(defaults or ()):
221
raise TypeError('not all arguments have proper definitions')
223
for idx, (arg, definition) in enumerate(zip(args, defaults or ())):
224
if arg.startswith('_'):
225
raise TypeError('arguments may not start with an underscore')
226
if not isinstance(definition, tuple):
230
shortcut, default = definition
231
argument_type = argument_types[type(default)]
232
if isinstance(default, bool) and default is True:
234
arguments.append((arg.replace('_', '-'), shortcut,
235
default, argument_type))
236
return func, description, arguments
239
def make_shell(init_func=None, banner=None, use_ipython=True):
240
"""Returns an action callback that spawns a new interactive
243
:param init_func: an optional initialization function that is
244
called before the shell is started. The return
245
value of this function is the initial namespace.
246
:param banner: the banner that is displayed before the shell. If
247
not specified a generic banner is used instead.
248
:param use_ipython: if set to `True` ipython is used if available.
251
banner = 'Interactive Werkzeug Shell'
252
if init_func is None:
254
def action(ipython=use_ipython):
255
"""Start a new interactive python session."""
256
namespace = init_func()
263
sh = IPython.Shell.IPShellEmbed(banner=banner)
264
sh(global_ns={}, local_ns=namespace)
266
from code import interact
267
interact(banner, local=namespace)
271
def make_runserver(app_factory, hostname='localhost', port=5000,
272
use_reloader=False, use_debugger=False, use_evalex=True,
273
threaded=False, processes=1, static_files=None,
275
"""Returns an action callback that spawns a new development server.
277
.. versionadded:: 0.5
278
`static_files` and `extra_files` was added.
280
:param app_factory: a function that returns a new WSGI application.
281
:param hostname: the default hostname the server should listen on.
282
:param port: the default port of the server.
283
:param use_reloader: the default setting for the reloader.
284
:param use_evalex: the default setting for the evalex flag of the debugger.
285
:param threaded: the default threading setting.
286
:param processes: the default number of processes to start.
287
:param static_files: optionally a dict of static files.
288
:param extra_files: optionally a list of extra files to track for reloading.
290
def action(hostname=('h', hostname), port=('p', port),
291
reloader=use_reloader, debugger=use_debugger,
292
evalex=use_evalex, threaded=threaded, processes=processes):
293
"""Start a new development server."""
294
from werkzeug.serving import run_simple
296
run_simple(hostname, port, app, reloader, debugger, evalex,
297
extra_files, 1, threaded, processes,
298
static_files=static_files)