~ubuntu-branches/ubuntu/oneiric/moin/oneiric-security

« back to all changes in this revision

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

  • Committer: Bazaar Package Importer
  • Author(s): Jamie Strandboge
  • Date: 2010-03-30 12:55:34 UTC
  • mfrom: (0.1.17 sid)
  • Revision ID: james.westby@ubuntu.com-20100330125534-4c2ufc1rok24447l
Tags: 1.9.2-2ubuntu1
* Merge from Debian testing (LP: #521834). Based on work by Stefan Ebner.
  Remaining changes:
 - Remove python-xml from Suggests field, the package isn't anymore in
   sys.path.
 - Demote fckeditor from Recommends to Suggests; the code was previously
   embedded in moin, but it was also disabled, so there's no reason for us
   to pull this in by default currently. Note: This isn't necessary anymore
   but needs a MIR for fckeditor, so postpone dropping this change until
   lucid+1
* debian/rules:
  - Replace hardcoded python2.5 with python* and hardcore python2.6 for ln
* debian/control.in: drop versioned depends on cdbs

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