From c7a384118290a0080bfe54d76cabbcc695316488 Mon Sep 17 00:00:00 2001 From: Antonio Ossa Date: Tue, 28 Mar 2017 02:37:37 -0300 Subject: [PATCH 1/6] Drop werkzeug.script from main code This commit deletes werkzeug/script.py, suggest using pallets/click instead of werkzeug.script for bigger applications and deletes script module from being loaded in werkzeug/__init__.py Signed-off-by: Antonio Ossa --- werkzeug/__init__.py | 2 +- werkzeug/script.py | 332 ------------------------------------------- werkzeug/serving.py | 2 +- 3 files changed, 2 insertions(+), 334 deletions(-) delete mode 100644 werkzeug/script.py diff --git a/werkzeug/__init__.py b/werkzeug/__init__.py index 8e5a47e85..7e720dd6a 100644 --- a/werkzeug/__init__.py +++ b/werkzeug/__init__.py @@ -98,7 +98,7 @@ } # modules that should be imported when accessed as attributes of werkzeug -attribute_modules = frozenset(['exceptions', 'routing', 'script']) +attribute_modules = frozenset(['exceptions', 'routing']) object_origins = {} diff --git a/werkzeug/script.py b/werkzeug/script.py deleted file mode 100644 index 46876f4c8..000000000 --- a/werkzeug/script.py +++ /dev/null @@ -1,332 +0,0 @@ -# -*- 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) 2014 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -''' -from __future__ import print_function - -import sys -import inspect -import getopt -from warnings import warn -from os.path import basename -from werkzeug._compat import iteritems - - -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 _deprecated(): - warn(DeprecationWarning('werkzeug.script is deprecated and ' - 'will be removed soon'), stacklevel=2) - - -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. - """ - _deprecated() - 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 as 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 iteritems(arguments): - newargs[k.startswith('no_') and k[3:] or k] = v - arguments = newargs - return func(**arguments) - - -def fail(message, code=-1): - """Fail with an error.""" - _deprecated() - print('Error: %s' % message, file=sys.stderr) - sys.exit(code) - - -def find_actions(namespace, action_prefix): - """Find all the actions in the namespace.""" - _deprecated() - actions = {} - for key, value in iteritems(namespace): - 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)""" - _deprecated() - actions = sorted(iteritems(actions)) - print('usage: %s []' % 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.""" - _deprecated() - 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. - """ - _deprecated() - 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.instance(banner1=banner) - except ImportError: - from IPython.Shell import IPShellEmbed - sh = IPShellEmbed(banner=banner) - except ImportError: - pass - else: - sh(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. - """ - _deprecated() - - 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, - use_reloader=reloader, use_debugger=debugger, - use_evalex=evalex, extra_files=extra_files, - reloader_interval=1, threaded=threaded, processes=processes, - static_files=static_files, ssl_context=ssl_context) - return action diff --git a/werkzeug/serving.py b/werkzeug/serving.py index 510e27153..8a0efdfed 100644 --- a/werkzeug/serving.py +++ b/werkzeug/serving.py @@ -28,7 +28,7 @@ You can also pass it a `extra_files` keyword argument with a list of additional files (like configuration files) you want to observe. - For bigger applications you should consider using `werkzeug.script` + For bigger applications you should consider using `pallets/click` instead of a simple start file. From e609041e1e3db7e38a90d009815435a8719ecd6f Mon Sep 17 00:00:00 2001 From: Antonio Ossa Date: Tue, 28 Mar 2017 02:53:31 -0300 Subject: [PATCH 2/6] Suggest pallets/click to replace werkzeug.script As suggested in the corresponding issue, click is the recommended replacement https://github.com/pallets/werkzeug/issues/1079#issuecomment-289602432 Signed-off-by: Antonio Ossa --- docs/transition.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/transition.rst b/docs/transition.rst index a1257e170..7b9981975 100644 --- a/docs/transition.rst +++ b/docs/transition.rst @@ -63,7 +63,7 @@ suggesting alternatives even if they will stick around for a longer time. Do not use: - `werkzeug.script`, replace it with custom scripts written with - `argparse` or something similar. + `argparse`, pallets/click or something similar. - `werkzeug.template`, replace with a proper template engine. - `werkzeug.contrib.jsrouting`, stop using URL generation for JavaScript, it does not scale well with many public routing. From cfc58eee21872d853bfcb4fd06bac637fb4b2ef4 Mon Sep 17 00:00:00 2001 From: Antonio Ossa Date: Tue, 28 Mar 2017 03:05:10 -0300 Subject: [PATCH 3/6] Add deprecation note to CHANGES Signed-off-by: Antonio Ossa --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 03459210f..2372b02a3 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ Version 0.13 yet to be released - Raise `TypeError` when port is not an integer. +- Fully deprecate `werkzeug.script`. Use pallets/click instead. Version 0.12.1 -------------- From 8098d03e445353d972ac4dd7f55470b45a09291d Mon Sep 17 00:00:00 2001 From: Antonio Ossa Date: Tue, 28 Mar 2017 21:59:17 -0300 Subject: [PATCH 4/6] Update examples Every manage-.py file used werkzeug.script a deprecated (and now definitely removed) module. The new cli are powered by click [1] [1]: https://github.com/pallets/click Signed-off-by: Antonio Ossa --- examples/README | 3 +- examples/manage-coolmagic.py | 53 +++++++++++++++++++++++++++--- examples/manage-couchy.py | 62 ++++++++++++++++++++++++++++++++--- examples/manage-cupoftee.py | 29 ++++++++++++++-- examples/manage-i18nurls.py | 53 +++++++++++++++++++++++++++--- examples/manage-plnt.py | 60 ++++++++++++++++++++++++++++----- examples/manage-shorty.py | 61 +++++++++++++++++++++++++++++++--- examples/manage-simplewiki.py | 60 +++++++++++++++++++++++++++------ examples/manage-webpylike.py | 51 +++++++++++++++++++++++++--- 9 files changed, 386 insertions(+), 46 deletions(-) diff --git a/examples/README b/examples/README index e99dd0fdd..8f74b4f09 100644 --- a/examples/README +++ b/examples/README @@ -7,7 +7,8 @@ Werkzeug powered applications. Beside the proof of concept applications and code snippets in the partial folder they all have external depencencies for template engines or database -adapters (SQLAlchemy only so far). +adapters (SQLAlchemy only so far). Also, every application has click as +external dependency, used to create the command line interface. Full Example Applications diff --git a/examples/manage-coolmagic.py b/examples/manage-coolmagic.py index b61eaa99c..28bcb5d03 100755 --- a/examples/manage-coolmagic.py +++ b/examples/manage-coolmagic.py @@ -9,12 +9,55 @@ :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ -import os +import click from coolmagic import make_app -from werkzeug import script +from werkzeug.serving import run_simple -action_runserver = script.make_runserver(make_app, use_reloader=True) -action_shell = script.make_shell(lambda: {}) + +@click.group() +def cli(): + pass + + +@cli.command() +@click.option('-h', '--hostname', type=str, default='localhost', help="localhost") +@click.option('-p', '--port', type=int, default=5000, help="5000") +@click.option('--no-reloader', is_flag=True, default=False) +@click.option('--debugger', is_flag=True) +@click.option('--no-evalex', is_flag=True, default=False) +@click.option('--threaded', is_flag=True) +@click.option('--processes', type=int, default=1, help="1") +def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): + """Start a new development server.""" + app = make_app() + reloader = not no_reloader + evalex = not no_evalex + run_simple(hostname, port, app, + use_reloader=reloader, use_debugger=debugger, + use_evalex=evalex, threaded=threaded, processes=processes) + + +@cli.command() +@click.option('--no-ipython', is_flag=True, default=False) +def shell(no_ipython): + """Start a new interactive python session.""" + banner = 'Interactive Werkzeug Shell' + namespace = dict() + if not no_ipython: + try: + try: + from IPython.frontend.terminal.embed import InteractiveShellEmbed + sh = InteractiveShellEmbed.instance(banner1=banner) + except ImportError: + from IPython.Shell import IPShellEmbed + sh = IPShellEmbed(banner=banner) + except ImportError: + pass + else: + sh(local_ns=namespace) + return + from code import interact + interact(banner, local=namespace) if __name__ == '__main__': - script.run() + cli() diff --git a/examples/manage-couchy.py b/examples/manage-couchy.py index 4b582dbcf..4f90f1c43 100755 --- a/examples/manage-couchy.py +++ b/examples/manage-couchy.py @@ -1,17 +1,69 @@ #!/usr/bin/env python -from werkzeug import script +import click +from werkzeug.serving import run_simple + def make_app(): from couchy.application import Couchy return Couchy('http://localhost:5984') + def make_shell(): from couchy import models, utils application = make_app() return locals() -action_runserver = script.make_runserver(make_app, use_reloader=True) -action_shell = script.make_shell(make_shell) -action_initdb = lambda: make_app().init_database() -script.run() +@click.group() +def cli(): + pass + + +@cli.command() +def initdb(): + from couchy.application import Couchy + Couchy('http://localhost:5984').init_database() + + +@cli.command() +@click.option('-h', '--hostname', type=str, default='localhost', help="localhost") +@click.option('-p', '--port', type=int, default=5000, help="5000") +@click.option('--no-reloader', is_flag=True, default=False) +@click.option('--debugger', is_flag=True) +@click.option('--no-evalex', is_flag=True, default=False) +@click.option('--threaded', is_flag=True) +@click.option('--processes', type=int, default=1, help="1") +def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): + """Start a new development server.""" + app = make_app() + reloader = not no_reloader + evalex = not no_evalex + run_simple(hostname, port, app, + use_reloader=reloader, use_debugger=debugger, + use_evalex=evalex, threaded=threaded, processes=processes) + + +@cli.command() +@click.option('--no-ipython', is_flag=True, default=False) +def shell(no_ipython): + """Start a new interactive python session.""" + banner = 'Interactive Werkzeug Shell' + namespace = make_shell() + if not no_ipython: + try: + try: + from IPython.frontend.terminal.embed import InteractiveShellEmbed + sh = InteractiveShellEmbed.instance(banner1=banner) + except ImportError: + from IPython.Shell import IPShellEmbed + sh = IPShellEmbed(banner=banner) + except ImportError: + pass + else: + sh(local_ns=namespace) + return + from code import interact + interact(banner, local=namespace) + +if __name__ == '__main__': + cli() diff --git a/examples/manage-cupoftee.py b/examples/manage-cupoftee.py index dd5e0a6ce..589a4172e 100755 --- a/examples/manage-cupoftee.py +++ b/examples/manage-cupoftee.py @@ -8,12 +8,35 @@ :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ -from werkzeug import script +import click +from werkzeug.serving import run_simple def make_app(): from cupoftee import make_app return make_app('/tmp/cupoftee.db') -action_runserver = script.make_runserver(make_app) -script.run() + +@click.group() +def cli(): + pass + + +@cli.command() +@click.option('-h', '--hostname', type=str, default='localhost', help="localhost") +@click.option('-p', '--port', type=int, default=5000, help="5000") +@click.option('--reloader', is_flag=True, default=False) +@click.option('--debugger', is_flag=True) +@click.option('--evalex', is_flag=True, default=False) +@click.option('--threaded', is_flag=True) +@click.option('--processes', type=int, default=1, help="1") +def runserver(hostname, port, reloader, debugger, evalex, threaded, processes): + """Start a new development server.""" + app = make_app() + run_simple(hostname, port, app, + use_reloader=reloader, use_debugger=debugger, + use_evalex=evalex, threaded=threaded, processes=processes) + + +if __name__ == '__main__': + cli() diff --git a/examples/manage-i18nurls.py b/examples/manage-i18nurls.py index ba0ac4d93..537d08cae 100755 --- a/examples/manage-i18nurls.py +++ b/examples/manage-i18nurls.py @@ -9,12 +9,55 @@ :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ -import os +import click from i18nurls import make_app -from werkzeug import script +from werkzeug.serving import run_simple -action_runserver = script.make_runserver(make_app) -action_shell = script.make_shell(lambda: {}) + +@click.group() +def cli(): + pass + + +@cli.command() +@click.option('-h', '--hostname', type=str, default='localhost', help="localhost") +@click.option('-p', '--port', type=int, default=5000, help="5000") +@click.option('--no-reloader', is_flag=True, default=False) +@click.option('--debugger', is_flag=True) +@click.option('--no-evalex', is_flag=True, default=False) +@click.option('--threaded', is_flag=True) +@click.option('--processes', type=int, default=1, help="1") +def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): + """Start a new development server.""" + app = make_app() + reloader = not no_reloader + evalex = not no_evalex + run_simple(hostname, port, app, + use_reloader=reloader, use_debugger=debugger, + use_evalex=evalex, threaded=threaded, processes=processes) + + +@cli.command() +@click.option('--no-ipython', is_flag=True, default=False) +def shell(no_ipython): + """Start a new interactive python session.""" + banner = 'Interactive Werkzeug Shell' + namespace = dict() + if not no_ipython: + try: + try: + from IPython.frontend.terminal.embed import InteractiveShellEmbed + sh = InteractiveShellEmbed.instance(banner1=banner) + except ImportError: + from IPython.Shell import IPShellEmbed + sh = IPShellEmbed(banner=banner) + except ImportError: + pass + else: + sh(local_ns=namespace) + return + from code import interact + interact(banner, local=namespace) if __name__ == '__main__': - script.run() + cli() diff --git a/examples/manage-plnt.py b/examples/manage-plnt.py index e8853d19c..f37faa1af 100755 --- a/examples/manage-plnt.py +++ b/examples/manage-plnt.py @@ -9,8 +9,9 @@ :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ +import click import os -from werkzeug import script +from werkzeug.serving import run_simple def make_app(): @@ -22,11 +23,13 @@ def make_app(): return app -action_runserver = script.make_runserver(make_app, use_reloader=True) -action_shell = script.make_shell(lambda: {'app': make_app()}) +@click.group() +def cli(): + pass -def action_initdb(): +@cli.command() +def initdb(): """Initialize the database""" from plnt.database import Blog, session make_app().init_database() @@ -51,15 +54,56 @@ def action_initdb(): for blog in blogs: session.add(blog) session.commit() - print 'Initialized database, now run manage-plnt.py sync to get the posts' + click.echo('Initialized database, now run manage-plnt.py sync to get the posts') -def action_sync(): +@cli.command() +@click.option('-h', '--hostname', type=str, default='localhost', help="localhost") +@click.option('-p', '--port', type=int, default=5000, help="5000") +@click.option('--no-reloader', is_flag=True, default=False) +@click.option('--debugger', is_flag=True) +@click.option('--no-evalex', is_flag=True, default=False) +@click.option('--threaded', is_flag=True) +@click.option('--processes', type=int, default=1, help="1") +def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): + """Start a new development server.""" + app = make_app() + reloader = not no_reloader + evalex = not no_evalex + run_simple(hostname, port, app, + use_reloader=reloader, use_debugger=debugger, + use_evalex=evalex, threaded=threaded, processes=processes) + + +@cli.command() +@click.option('--no-ipython', is_flag=True, default=False) +def shell(no_ipython): + """Start a new interactive python session.""" + banner = 'Interactive Werkzeug Shell' + namespace = {'app': make_app()} + if not no_ipython: + try: + try: + from IPython.frontend.terminal.embed import InteractiveShellEmbed + sh = InteractiveShellEmbed.instance(banner1=banner) + except ImportError: + from IPython.Shell import IPShellEmbed + sh = IPShellEmbed(banner=banner) + except ImportError: + pass + else: + sh(local_ns=namespace) + return + from code import interact + interact(banner, local=namespace) + + +@cli.command() +def sync(): """Sync the blogs in the planet. Call this from a cronjob.""" from plnt.sync import sync make_app().bind_to_context() sync() - if __name__ == '__main__': - script.run() + cli() diff --git a/examples/manage-shorty.py b/examples/manage-shorty.py index 71ea3b27f..57d10a176 100755 --- a/examples/manage-shorty.py +++ b/examples/manage-shorty.py @@ -1,20 +1,71 @@ #!/usr/bin/env python +import click import os import tempfile -from werkzeug import script +from werkzeug.serving import run_simple + def make_app(): from shorty.application import Shorty filename = os.path.join(tempfile.gettempdir(), "shorty.db") return Shorty('sqlite:///{0}'.format(filename)) + def make_shell(): from shorty import models, utils application = make_app() return locals() -action_runserver = script.make_runserver(make_app, use_reloader=True) -action_shell = script.make_shell(make_shell) -action_initdb = lambda: make_app().init_database() -script.run() +@click.group() +def cli(): + pass + + +@cli.command() +def initdb(): + make_app().init_database() + + +@cli.command() +@click.option('-h', '--hostname', type=str, default='localhost', help="localhost") +@click.option('-p', '--port', type=int, default=5000, help="5000") +@click.option('--no-reloader', is_flag=True, default=False) +@click.option('--debugger', is_flag=True) +@click.option('--no-evalex', is_flag=True, default=False) +@click.option('--threaded', is_flag=True) +@click.option('--processes', type=int, default=1, help="1") +def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): + """Start a new development server.""" + app = make_app() + reloader = not no_reloader + evalex = not no_evalex + run_simple(hostname, port, app, + use_reloader=reloader, use_debugger=debugger, + use_evalex=evalex, threaded=threaded, processes=processes) + + +@cli.command() +@click.option('--no-ipython', is_flag=True, default=False) +def shell(no_ipython): + """Start a new interactive python session.""" + banner = 'Interactive Werkzeug Shell' + namespace = make_shell() + if not no_ipython: + try: + try: + from IPython.frontend.terminal.embed import InteractiveShellEmbed + sh = InteractiveShellEmbed.instance(banner1=banner) + except ImportError: + from IPython.Shell import IPShellEmbed + sh = IPShellEmbed(banner=banner) + except ImportError: + pass + else: + sh(local_ns=namespace) + return + from code import interact + interact(banner, local=namespace) + +if __name__ == '__main__': + cli() diff --git a/examples/manage-simplewiki.py b/examples/manage-simplewiki.py index 2960b0f3f..742652c98 100755 --- a/examples/manage-simplewiki.py +++ b/examples/manage-simplewiki.py @@ -9,8 +9,10 @@ :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ +import click import os -from werkzeug import script +import tempfile +from werkzeug.serving import run_simple def make_wiki(): @@ -20,10 +22,7 @@ def make_wiki(): return SimpleWiki(database_uri or 'sqlite:////tmp/simplewiki.db') -def shell_init_func(): - """ - Called on shell initialization. Adds useful stuff to the namespace. - """ +def make_shell(): from simplewiki import database wiki = make_wiki() wiki.bind_to_context() @@ -33,14 +32,55 @@ def shell_init_func(): } -action_runserver = script.make_runserver(make_wiki, use_reloader=True) -action_shell = script.make_shell(shell_init_func) +@click.group() +def cli(): + pass -def action_initdb(): - """Initialize the database""" +@cli.command() +def initdb(): make_wiki().init_database() +@cli.command() +@click.option('-h', '--hostname', type=str, default='localhost', help="localhost") +@click.option('-p', '--port', type=int, default=5000, help="5000") +@click.option('--no-reloader', is_flag=True, default=False) +@click.option('--debugger', is_flag=True) +@click.option('--no-evalex', is_flag=True, default=False) +@click.option('--threaded', is_flag=True) +@click.option('--processes', type=int, default=1, help="1") +def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): + """Start a new development server.""" + app = make_wiki() + reloader = not no_reloader + evalex = not no_evalex + run_simple(hostname, port, app, + use_reloader=reloader, use_debugger=debugger, + use_evalex=evalex, threaded=threaded, processes=processes) + + +@cli.command() +@click.option('--no-ipython', is_flag=True, default=False) +def shell(no_ipython): + """Start a new interactive python session.""" + banner = 'Interactive Werkzeug Shell' + namespace = make_shell() + if not no_ipython: + try: + try: + from IPython.frontend.terminal.embed import InteractiveShellEmbed + sh = InteractiveShellEmbed.instance(banner1=banner) + except ImportError: + from IPython.Shell import IPShellEmbed + sh = IPShellEmbed(banner=banner) + except ImportError: + pass + else: + sh(local_ns=namespace) + return + from code import interact + interact(banner, local=namespace) + if __name__ == '__main__': - script.run() + cli() diff --git a/examples/manage-webpylike.py b/examples/manage-webpylike.py index 6a9ca4abf..e9262c62b 100755 --- a/examples/manage-webpylike.py +++ b/examples/manage-webpylike.py @@ -13,14 +13,57 @@ :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ +import click import os import sys sys.path.append(os.path.join(os.path.dirname(__file__), 'webpylike')) from example import app -from werkzeug import script +from werkzeug.serving import run_simple -action_runserver = script.make_runserver(lambda: app) -action_shell = script.make_shell(lambda: {}) + +@click.group() +def cli(): + pass + + +@cli.command() +@click.option('-h', '--hostname', type=str, default='localhost', help="localhost") +@click.option('-p', '--port', type=int, default=5000, help="5000") +@click.option('--no-reloader', is_flag=True, default=False) +@click.option('--debugger', is_flag=True) +@click.option('--no-evalex', is_flag=True, default=False) +@click.option('--threaded', is_flag=True) +@click.option('--processes', type=int, default=1, help="1") +def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): + """Start a new development server.""" + reloader = not no_reloader + evalex = not no_evalex + run_simple(hostname, port, app, + use_reloader=reloader, use_debugger=debugger, + use_evalex=evalex, threaded=threaded, processes=processes) + + +@cli.command() +@click.option('--no-ipython', is_flag=True, default=False) +def shell(no_ipython): + """Start a new interactive python session.""" + banner = 'Interactive Werkzeug Shell' + namespace = dict() + if not no_ipython: + try: + try: + from IPython.frontend.terminal.embed import InteractiveShellEmbed + sh = InteractiveShellEmbed.instance(banner1=banner) + except ImportError: + from IPython.Shell import IPShellEmbed + sh = IPShellEmbed(banner=banner) + except ImportError: + pass + else: + sh(local_ns=namespace) + return + from code import interact + interact(banner, local=namespace) if __name__ == '__main__': - script.run() + cli() From 629b6a4c3668405a072d2daa5fc68fba4f69df99 Mon Sep 17 00:00:00 2001 From: Antonio Ossa Date: Tue, 28 Mar 2017 22:37:40 -0300 Subject: [PATCH 5/6] Recommend `click` to replace `werkzeug.script` This commit updates docs and comments to recommend `click` and display the `click` website ([1]) when possible [1]: http://click.pocoo.org Signed-off-by: Antonio Ossa --- CHANGES | 3 ++- docs/transition.rst | 2 +- werkzeug/serving.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 2372b02a3..591df094a 100644 --- a/CHANGES +++ b/CHANGES @@ -7,7 +7,8 @@ Version 0.13 yet to be released - Raise `TypeError` when port is not an integer. -- Fully deprecate `werkzeug.script`. Use pallets/click instead. +- Fully deprecate `werkzeug.script`. Use `click` + (http://click.pocoo.org) instead. Version 0.12.1 -------------- diff --git a/docs/transition.rst b/docs/transition.rst index 7b9981975..54cf2a0ea 100644 --- a/docs/transition.rst +++ b/docs/transition.rst @@ -63,7 +63,7 @@ suggesting alternatives even if they will stick around for a longer time. Do not use: - `werkzeug.script`, replace it with custom scripts written with - `argparse`, pallets/click or something similar. + `argparse`, `click` or something similar. - `werkzeug.template`, replace with a proper template engine. - `werkzeug.contrib.jsrouting`, stop using URL generation for JavaScript, it does not scale well with many public routing. diff --git a/werkzeug/serving.py b/werkzeug/serving.py index 8a0efdfed..b423c9de3 100644 --- a/werkzeug/serving.py +++ b/werkzeug/serving.py @@ -28,8 +28,8 @@ You can also pass it a `extra_files` keyword argument with a list of additional files (like configuration files) you want to observe. - For bigger applications you should consider using `pallets/click` - instead of a simple start file. + For bigger applications you should consider using `click` + (http://click.pocoo.org) instead of a simple start file. :copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details. From 46bbf0a53821f3d3fff5546f6e4a9c00cc074983 Mon Sep 17 00:00:00 2001 From: Antonio Ossa Date: Wed, 29 Mar 2017 20:49:00 -0300 Subject: [PATCH 6/6] Update examples code Some changes were made in `couchdb` and `werkzeug`. It's time to update our examples. Now they should work on Python 2. Signed-off-by: Antonio Ossa --- examples/coolmagic/application.py | 2 +- examples/couchy/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/coolmagic/application.py b/examples/coolmagic/application.py index 83c8e04d2..eb010b9fd 100644 --- a/examples/coolmagic/application.py +++ b/examples/coolmagic/application.py @@ -67,7 +67,7 @@ def make_app(config=None): app = CoolMagicApplication(config) # static stuff - from werkzeug.utils import SharedDataMiddleware + from werkzeug.wsgi import SharedDataMiddleware app = SharedDataMiddleware(app, { '/public': path.join(path.dirname(__file__), 'public') }) diff --git a/examples/couchy/models.py b/examples/couchy/models.py index dd7aea385..be06d512a 100644 --- a/examples/couchy/models.py +++ b/examples/couchy/models.py @@ -1,5 +1,5 @@ from datetime import datetime -from couchdb.schema import Document, TextField, BooleanField, DateTimeField +from couchdb.mapping import Document, TextField, BooleanField, DateTimeField from couchy.utils import url_for, get_random_uid