~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/support/werkzeug/script.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mto: This revision was merged to the branch mainline in revision 18.
  • Revision ID: james.westby@ubuntu.com-20080622211713-inlv5k4eifxckelr
ImportĀ upstreamĀ versionĀ 1.7.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
r'''
3
 
    werkzeug.script
4
 
    ~~~~~~~~~~~~~~~
5
 
 
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
9
 
    something similar.
10
 
 
11
 
    For that purpose werkzeug provides the `werkzeug.script` module which
12
 
    helps you writing such scripts.
13
 
 
14
 
 
15
 
    Basic Usage
16
 
    -----------
17
 
 
18
 
    The following snippet is roughly the same in every werkzeug script::
19
 
 
20
 
        #!/usr/bin/env python
21
 
        # -*- coding: utf-8 -*-
22
 
        from werkzeug import script
23
 
 
24
 
        # actions go here
25
 
 
26
 
        if __name__ == '__main__':
27
 
            script.run()
28
 
 
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.
33
 
 
34
 
    Arguments can then be passed by position or using ``--name=value`` from
35
 
    the shell.
36
 
 
37
 
    Because a runserver and shell command is pretty common there are two
38
 
    factory functions that create such commands::
39
 
 
40
 
        def make_app():
41
 
            from yourapplication import YourApplication
42
 
            return YourApplication(...)
43
 
 
44
 
        action_runserver = script.make_runserver(make_app, use_reloader=True)
45
 
        action_shell = script.make_shell(lambda: {'app': make_app()})
46
 
 
47
 
 
48
 
    Using The Scripts
49
 
    -----------------
50
 
 
51
 
    The script from above can be used like this from the shell now:
52
 
 
53
 
    .. sourcecode:: text
54
 
 
55
 
        $ ./manage.py --help
56
 
        $ ./manage.py runserver localhost 8080 --debugger --no-reloader
57
 
        $ ./manage.py runserver -p 4000
58
 
        $ ./manage.py shell
59
 
 
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.
62
 
 
63
 
 
64
 
    :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
65
 
    :license: BSD, see LICENSE for more details.
66
 
'''
67
 
import sys
68
 
import inspect
69
 
import getopt
70
 
from os.path import basename
71
 
 
72
 
 
73
 
argument_types = {
74
 
    bool:       'boolean',
75
 
    str:        'string',
76
 
    int:        'integer',
77
 
    float:      'float'
78
 
}
79
 
 
80
 
 
81
 
converters = {
82
 
    'boolean':  lambda x: x.lower() in ('1', 'true', 'yes', 'on'),
83
 
    'string':   str,
84
 
    'integer':  int,
85
 
    'float':    float
86
 
}
87
 
 
88
 
 
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.
95
 
 
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
99
 
                          is ignored.
100
 
    :param args: the arguments for the function.  If not specified
101
 
                 :data:`sys.argv` without the first argument is used.
102
 
    """
103
 
    if namespace is None:
104
 
        namespace = sys._getframe(1).f_locals
105
 
    actions = find_actions(namespace, action_prefix)
106
 
 
107
 
    if args is None:
108
 
        args = sys.argv[1:]
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])
113
 
 
114
 
    arguments = {}
115
 
    conv = {}
116
 
    key_to_arg = {}
117
 
    long_options = []
118
 
    formatstring = ''
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]
123
 
        if shortcut:
124
 
            formatstring += shortcut
125
 
            if not isinstance(default, bool):
126
 
                formatstring += ':'
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
133
 
 
134
 
    try:
135
 
        optlist, posargs = getopt.gnu_getopt(args, formatstring, long_options)
136
 
    except getopt.GetoptError, e:
137
 
        fail(str(e))
138
 
 
139
 
    specified_arguments = set()
140
 
    for key, value in enumerate(posargs):
141
 
        try:
142
 
            arg = key_to_arg[key]
143
 
        except IndexError:
144
 
            fail('Too many parameters')
145
 
        specified_arguments.add(arg)
146
 
        try:
147
 
            arguments[arg] = conv[arg](value)
148
 
        except ValueError:
149
 
            fail('Invalid value for argument %s (%s): %s' % (key, arg, value))
150
 
 
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_'):
156
 
            value = 'no'
157
 
        elif not value:
158
 
            value = 'yes'
159
 
        try:
160
 
            arguments[arg] = conv[arg](value)
161
 
        except ValueError:
162
 
            fail('Invalid value for \'%s\': %s' % (key, value))
163
 
 
164
 
    newargs = {}
165
 
    for k, v in arguments.iteritems():
166
 
        newargs[k.startswith('no_') and k[3:] or k] = v
167
 
    arguments = newargs
168
 
    return func(**arguments)
169
 
 
170
 
 
171
 
def fail(message, code=-1):
172
 
    """Fail with an error."""
173
 
    print >> sys.stderr, 'Error:', message
174
 
    sys.exit(code)
175
 
 
176
 
 
177
 
def find_actions(namespace, action_prefix):
178
 
    """Find all the actions in the namespace."""
179
 
    actions = {}
180
 
    for key, value in namespace.iteritems():
181
 
        if key.startswith(action_prefix):
182
 
            actions[key[len(action_prefix):]] = analyse_action(value)
183
 
    return actions
184
 
 
185
 
 
186
 
def print_usage(actions):
187
 
    """Print the usage information.  (Help screen)"""
188
 
    actions = actions.items()
189
 
    actions.sort()
190
 
    print 'usage: %s <action> [<options>]' % basename(sys.argv[0])
191
 
    print '       %s --help' % basename(sys.argv[0])
192
 
    print
193
 
    print 'actions:'
194
 
    for name, (func, doc, arguments) in actions:
195
 
        print '  %s:' % name
196
 
        for line in doc.splitlines():
197
 
            print '    %s' % line
198
 
        if arguments:
199
 
            print
200
 
        for arg, shortcut, default, argtype in arguments:
201
 
            if isinstance(default, bool):
202
 
                print '    %s' % (
203
 
                    (shortcut and '-%s, ' % shortcut or '') + '--' + arg
204
 
                )
205
 
            else:
206
 
                print '    %-30s%-10s%s' % (
207
 
                    (shortcut and '-%s, ' % shortcut or '') + '--' + arg,
208
 
                    argtype, default
209
 
                )
210
 
        print
211
 
 
212
 
 
213
 
def analyse_action(func):
214
 
    """Analyse a function."""
215
 
    description = inspect.getdoc(func) or 'undocumented action'
216
 
    arguments = []
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')
222
 
 
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):
227
 
            shortcut = None
228
 
            default = definition
229
 
        else:
230
 
            shortcut, default = definition
231
 
        argument_type = argument_types[type(default)]
232
 
        if isinstance(default, bool) and default is True:
233
 
            arg = 'no-' + arg
234
 
        arguments.append((arg.replace('_', '-'), shortcut,
235
 
                          default, argument_type))
236
 
    return func, description, arguments
237
 
 
238
 
 
239
 
def make_shell(init_func=None, banner=None, use_ipython=True):
240
 
    """Returns an action callback that spawns a new interactive
241
 
    python shell.
242
 
 
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.
249
 
    """
250
 
    if banner is None:
251
 
        banner = 'Interactive Werkzeug Shell'
252
 
    if init_func is None:
253
 
        init_func = dict
254
 
    def action(ipython=use_ipython):
255
 
        """Start a new interactive python session."""
256
 
        namespace = init_func()
257
 
        if ipython:
258
 
            try:
259
 
                import IPython
260
 
            except ImportError:
261
 
                pass
262
 
            else:
263
 
                sh = IPython.Shell.IPShellEmbed(banner=banner)
264
 
                sh(global_ns={}, local_ns=namespace)
265
 
                return
266
 
        from code import interact
267
 
        interact(banner, local=namespace)
268
 
    return action
269
 
 
270
 
 
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,
274
 
                   extra_files=None):
275
 
    """Returns an action callback that spawns a new development server.
276
 
 
277
 
    .. versionadded:: 0.5
278
 
       `static_files` and `extra_files` was added.
279
 
 
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.
289
 
    """
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
295
 
        app = app_factory()
296
 
        run_simple(hostname, port, app, reloader, debugger, evalex,
297
 
                   extra_files, 1, threaded, processes,
298
 
                   static_files=static_files)
299
 
    return action