Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/websdk/werkzeug/script.py
blob: 2411cbd0df0e3ea5ff17e8912f45ff955ba8fc07 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# -*- coding: utf-8 -*-
r'''
    werkzeug.script
    ~~~~~~~~~~~~~~~

    .. admonition:: Deprecated Functionality

       ``werkzeug.script`` is deprecated without replacement functionality.
       Python's command line support improved greatly with :mod:`argparse`
       and a bunch of alternative modules.

    Most of the time you have recurring tasks while writing an application
    such as starting up an interactive python interpreter with some prefilled
    imports, starting the development server, initializing the database or
    something similar.

    For that purpose werkzeug provides the `werkzeug.script` module which
    helps you writing such scripts.


    Basic Usage
    -----------

    The following snippet is roughly the same in every werkzeug script::

        #!/usr/bin/env python
        # -*- coding: utf-8 -*-
        from werkzeug import script

        # actions go here

        if __name__ == '__main__':
            script.run()

    Starting this script now does nothing because no actions are defined.
    An action is a function in the same module starting with ``"action_"``
    which takes a number of arguments where every argument has a default.  The
    type of the default value specifies the type of the argument.

    Arguments can then be passed by position or using ``--name=value`` from
    the shell.

    Because a runserver and shell command is pretty common there are two
    factory functions that create such commands::

        def make_app():
            from yourapplication import YourApplication
            return YourApplication(...)

        action_runserver = script.make_runserver(make_app, use_reloader=True)
        action_shell = script.make_shell(lambda: {'app': make_app()})


    Using The Scripts
    -----------------

    The script from above can be used like this from the shell now:

    .. sourcecode:: text

        $ ./manage.py --help
        $ ./manage.py runserver localhost 8080 --debugger --no-reloader
        $ ./manage.py runserver -p 4000
        $ ./manage.py shell

    As you can see it's possible to pass parameters as positional arguments
    or as named parameters, pretty much like Python function calls.


    :copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
    :license: BSD, see LICENSE for more details.
'''
import sys
import inspect
import getopt
from os.path import basename


argument_types = {
    bool:       'boolean',
    str:        'string',
    int:        'integer',
    float:      'float'
}


converters = {
    'boolean':  lambda x: x.lower() in ('1', 'true', 'yes', 'on'),
    'string':   str,
    'integer':  int,
    'float':    float
}


def run(namespace=None, action_prefix='action_', args=None):
    """Run the script.  Participating actions are looked up in the caller's
    namespace if no namespace is given, otherwise in the dict provided.
    Only items that start with action_prefix are processed as actions.  If
    you want to use all items in the namespace provided as actions set
    action_prefix to an empty string.

    :param namespace: An optional dict where the functions are looked up in.
                      By default the local namespace of the caller is used.
    :param action_prefix: The prefix for the functions.  Everything else
                          is ignored.
    :param args: the arguments for the function.  If not specified
                 :data:`sys.argv` without the first argument is used.
    """
    if namespace is None:
        namespace = sys._getframe(1).f_locals
    actions = find_actions(namespace, action_prefix)

    if args is None:
        args = sys.argv[1:]
    if not args or args[0] in ('-h', '--help'):
        return print_usage(actions)
    elif args[0] not in actions:
        fail('Unknown action \'%s\'' % args[0])

    arguments = {}
    types = {}
    key_to_arg = {}
    long_options = []
    formatstring = ''
    func, doc, arg_def = actions[args.pop(0)]
    for idx, (arg, shortcut, default, option_type) in enumerate(arg_def):
        real_arg = arg.replace('-', '_')
        if shortcut:
            formatstring += shortcut
            if not isinstance(default, bool):
                formatstring += ':'
            key_to_arg['-' + shortcut] = real_arg
        long_options.append(isinstance(default, bool) and arg or arg + '=')
        key_to_arg['--' + arg] = real_arg
        key_to_arg[idx] = real_arg
        types[real_arg] = option_type
        arguments[real_arg] = default

    try:
        optlist, posargs = getopt.gnu_getopt(args, formatstring, long_options)
    except getopt.GetoptError, e:
        fail(str(e))

    specified_arguments = set()
    for key, value in enumerate(posargs):
        try:
            arg = key_to_arg[key]
        except IndexError:
            fail('Too many parameters')
        specified_arguments.add(arg)
        try:
            arguments[arg] = converters[types[arg]](value)
        except ValueError:
            fail('Invalid value for argument %s (%s): %s' % (key, arg, value))

    for key, value in optlist:
        arg = key_to_arg[key]
        if arg in specified_arguments:
            fail('Argument \'%s\' is specified twice' % arg)
        if types[arg] == 'boolean':
            if arg.startswith('no_'):
                value = 'no'
            else:
                value = 'yes'
        try:
            arguments[arg] = converters[types[arg]](value)
        except ValueError:
            fail('Invalid value for \'%s\': %s' % (key, value))

    newargs = {}
    for k, v in arguments.iteritems():
        newargs[k.startswith('no_') and k[3:] or k] = v
    arguments = newargs
    return func(**arguments)


def fail(message, code=-1):
    """Fail with an error."""
    print >> sys.stderr, 'Error:', message
    sys.exit(code)


def find_actions(namespace, action_prefix):
    """Find all the actions in the namespace."""
    actions = {}
    for key, value in namespace.iteritems():
        if key.startswith(action_prefix):
            actions[key[len(action_prefix):]] = analyse_action(value)
    return actions


def print_usage(actions):
    """Print the usage information.  (Help screen)"""
    actions = actions.items()
    actions.sort()
    print 'usage: %s <action> [<options>]' % basename(sys.argv[0])
    print '       %s --help' % basename(sys.argv[0])
    print
    print 'actions:'
    for name, (func, doc, arguments) in actions:
        print '  %s:' % name
        for line in doc.splitlines():
            print '    %s' % line
        if arguments:
            print
        for arg, shortcut, default, argtype in arguments:
            if isinstance(default, bool):
                print '    %s' % (
                    (shortcut and '-%s, ' % shortcut or '') + '--' + arg
                )
            else:
                print '    %-30s%-10s%s' % (
                    (shortcut and '-%s, ' % shortcut or '') + '--' + arg,
                    argtype, default
                )
        print


def analyse_action(func):
    """Analyse a function."""
    description = inspect.getdoc(func) or 'undocumented action'
    arguments = []
    args, varargs, kwargs, defaults = inspect.getargspec(func)
    if varargs or kwargs:
        raise TypeError('variable length arguments for action not allowed.')
    if len(args) != len(defaults or ()):
        raise TypeError('not all arguments have proper definitions')

    for idx, (arg, definition) in enumerate(zip(args, defaults or ())):
        if arg.startswith('_'):
            raise TypeError('arguments may not start with an underscore')
        if not isinstance(definition, tuple):
            shortcut = None
            default = definition
        else:
            shortcut, default = definition
        argument_type = argument_types[type(default)]
        if isinstance(default, bool) and default is True:
            arg = 'no-' + arg
        arguments.append((arg.replace('_', '-'), shortcut,
                          default, argument_type))
    return func, description, arguments


def make_shell(init_func=None, banner=None, use_ipython=True):
    """Returns an action callback that spawns a new interactive
    python shell.

    :param init_func: an optional initialization function that is
                      called before the shell is started.  The return
                      value of this function is the initial namespace.
    :param banner: the banner that is displayed before the shell.  If
                   not specified a generic banner is used instead.
    :param use_ipython: if set to `True` ipython is used if available.
    """
    if banner is None:
        banner = 'Interactive Werkzeug Shell'
    if init_func is None:
        init_func = dict
    def action(ipython=use_ipython):
        """Start a new interactive python session."""
        namespace = init_func()
        if ipython:
            try:
                try:
                    from IPython.frontend.terminal.embed import InteractiveShellEmbed
                    sh = InteractiveShellEmbed(banner1=banner)
                except ImportError:
                    from IPython.Shell import IPShellEmbed
                    sh = IPShellEmbed(banner=banner)
            except ImportError:
                pass
            else:
                sh(global_ns={}, local_ns=namespace)
                return
        from code import interact
        interact(banner, local=namespace)
    return action


def make_runserver(app_factory, hostname='localhost', port=5000,
                   use_reloader=False, use_debugger=False, use_evalex=True,
                   threaded=False, processes=1, static_files=None,
                   extra_files=None, ssl_context=None):
    """Returns an action callback that spawns a new development server.

    .. versionadded:: 0.5
       `static_files` and `extra_files` was added.

    ..versionadded:: 0.6.1
       `ssl_context` was added.

    :param app_factory: a function that returns a new WSGI application.
    :param hostname: the default hostname the server should listen on.
    :param port: the default port of the server.
    :param use_reloader: the default setting for the reloader.
    :param use_evalex: the default setting for the evalex flag of the debugger.
    :param threaded: the default threading setting.
    :param processes: the default number of processes to start.
    :param static_files: optional dict of static files.
    :param extra_files: optional list of extra files to track for reloading.
    :param ssl_context: optional SSL context for running server in HTTPS mode.
    """
    def action(hostname=('h', hostname), port=('p', port),
               reloader=use_reloader, debugger=use_debugger,
               evalex=use_evalex, threaded=threaded, processes=processes):
        """Start a new development server."""
        from werkzeug.serving import run_simple
        app = app_factory()
        run_simple(hostname, port, app, reloader, debugger, evalex,
                   extra_files, 1, threaded, processes,
                   static_files=static_files, ssl_context=ssl_context)
    return action