Browse Source

fall back to built-in command when loading fails

print traceback when loading fails
don't print traceback multiple times
modify test to set command on app instead of group
add docs and changelog
pull/2053/head
David Lord 8 years ago
parent
commit
87fe58c239
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
  1. 3
      CHANGES
  2. 38
      docs/cli.rst
  3. 54
      flask/cli.py
  4. 15
      tests/test_cli.py

3
CHANGES

@ -34,11 +34,14 @@ Major release, unreleased
type is invalid. (`#2256`_)
- Add ``routes`` CLI command to output routes registered on the application.
(`#2259`_)
- Allow overriding built-in commands using ``app.cli.command`` instead of
creating a separate ``FlaskGroup``. (`#2217`_)
.. _#1489: https://github.com/pallets/flask/pull/1489
.. _#1898: https://github.com/pallets/flask/pull/1898
.. _#1936: https://github.com/pallets/flask/pull/1936
.. _#2017: https://github.com/pallets/flask/pull/2017
.. _#2217: https://github.com/pallets/flask/pull/2217
.. _#2223: https://github.com/pallets/flask/pull/2223
.. _#2254: https://github.com/pallets/flask/pull/2254
.. _#2256: https://github.com/pallets/flask/pull/2256

38
docs/cli.rst

@ -159,6 +159,44 @@ pick it up::
From this point onwards :command:`flask` will find your application.
Overriding Built-In Commands
----------------------------
Flask comes with some built in commands which can be overridden. Use
``app.cli.command(name)`` with the name of the built-in command being
overridden. Use :meth:`~click.Context.forward` to call the built-in command
from the custom command.
For example, to override the built-in ``run`` command and add a ``--ssl``
option for enabling SSL on the dev server::
import click
from flask import Flask
from flask.cli import run_command as builtin_run_command
app = Flask(__name__)
@app.cli.command(
'run',
# copy the built-in command's help text
help=builtin_run_command.help,
short_help=builtin_run_command.short_help
)
@click.option('--ssl', 'ssl_context', flag_value='adhoc')
@click.pass_context
def run_command(ctx, *args, ssl_context=None, **kwargs):
if ssl_context == 'adhoc':
click.echo(' * Enabling adhoc SSL')
ctx.forward(builtin_run_command)
# copy the built-in command's options
run_command.params[:0] = builtin_run_command.params
Copying the built-in function's code to your own version is also viable, and
may be more useful if the behavior you want to change can't be done before the
command is forwarded to.
.. _custom-scripts:
Custom Scripts

54
flask/cli.py

@ -341,43 +341,40 @@ class FlaskGroup(AppGroup):
def get_command(self, ctx, name):
self._load_plugin_commands()
# We load built-in commands first as these should always be the
# same no matter what the app does. If the app does want to
# override this it needs to make a custom instance of this group
# and not attach the default commands.
#
# This also means that the script stays functional in case the
# application completely fails.
builtin_rv = AppGroup.get_command(self, ctx, name)
info = ctx.ensure_object(ScriptInfo)
try:
rv = info.load_app().cli.get_command(ctx, name)
if rv is not None:
return rv
elif builtin_rv is not None:
return builtin_rv
except NoAppException:
pass
except Exception:
# Intentionally swallow all exceptions and try to get the built-in
# version of the command. If this didn't fail in list_commands
# first, print the traceback.
if not info.data.get('_list_commands_failed', False):
traceback.print_exc()
# If the app couldn't be loaded, or didn't have the given command, fall
# back to built-in commands.
return super(FlaskGroup, self).get_command(ctx, name)
def list_commands(self, ctx):
self._load_plugin_commands()
# The commands available is the list of both the application (if
# available) plus the builtin commands.
rv = set(click.Group.list_commands(self, ctx))
rv = set(super(FlaskGroup, self).list_commands(ctx))
info = ctx.ensure_object(ScriptInfo)
try:
rv.update(info.load_app().cli.list_commands(ctx))
except Exception:
# Here we intentionally swallow all exceptions as we don't
# want the help page to break if the app does not exist.
# If someone attempts to use the command we try to create
# the app again and this will give us the error.
# However, we will not do so silently because that would confuse
# users.
# Intentionally swallow all exceptions so the help page doesn't
# break if the app does not exist. Set a flag so that the error is
# only printed once while printing each command's help.
info.data['_list_commands_failed'] = True
traceback.print_exc()
return sorted(rv)
def main(self, *args, **kwargs):
@ -406,8 +403,9 @@ class FlaskGroup(AppGroup):
@click.option('--with-threads/--without-threads', default=False,
help='Enable or disable multithreading.')
@pass_script_info
def run_command(info, host, port, reload, debugger, eager_loading,
with_threads):
def run_command(
info, host, port, reload, debugger, eager_loading, with_threads, **kwargs
):
"""Runs a local development server for the Flask application.
This local server is recommended for development purposes only but it
@ -450,8 +448,12 @@ def run_command(info, host, port, reload, debugger, eager_loading,
if debug is not None:
print(' * Forcing debug mode %s' % (debug and 'on' or 'off'))
run_simple(host, port, app, use_reloader=reload,
use_debugger=debugger, threaded=with_threads)
run_simple(
host, port, app,
use_reloader=reload, use_debugger=debugger,
threaded=with_threads,
**kwargs
)
@click.command('shell', short_help='Runs a shell in the app context.')

15
tests/test_cli.py

@ -271,13 +271,16 @@ class TestRoutes:
def test_override_builtin_cli():
cli = FlaskGroup(create_app=lambda info: Flask('override_builtin'))
def create_app(info):
app = Flask(__name__)
@cli.command('run', help="foo")
def run():
click.echo(current_app.name)
@app.cli.command('routes')
def routes_command():
click.echo('test')
return app
runner = CliRunner()
result = runner.invoke(cli, ['run'])
result = runner.invoke(FlaskGroup(create_app=create_app), ['routes'])
assert result.exit_code == 0
assert result.output == 'override_builtin\n'
assert result.output == 'test\n'

Loading…
Cancel
Save