with_appcontext lasts for the lifetime of the click context
This commit is contained in:
parent
ae547270e9
commit
c9e000b9ce
6 changed files with 65 additions and 26 deletions
|
|
@ -33,6 +33,9 @@ Unreleased
|
||||||
- Add ``--env-file`` option to the ``flask`` CLI. This allows
|
- Add ``--env-file`` option to the ``flask`` CLI. This allows
|
||||||
specifying a dotenv file to load in addition to ``.env`` and
|
specifying a dotenv file to load in addition to ``.env`` and
|
||||||
``.flaskenv``. :issue:`3108`
|
``.flaskenv``. :issue:`3108`
|
||||||
|
- It is no longer required to decorate custom CLI commands on
|
||||||
|
``app.cli`` or ``blueprint.cli`` with ``@with_appcontext``, an app
|
||||||
|
context will already be active at that point. :issue:`2410`
|
||||||
|
|
||||||
|
|
||||||
Version 2.1.3
|
Version 2.1.3
|
||||||
|
|
|
||||||
20
docs/cli.rst
20
docs/cli.rst
|
|
@ -437,12 +437,14 @@ commands directly to the application's level:
|
||||||
Application Context
|
Application Context
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Commands added using the Flask app's :attr:`~Flask.cli`
|
Commands added using the Flask app's :attr:`~Flask.cli` or
|
||||||
:meth:`~cli.AppGroup.command` decorator will be executed with an application
|
:class:`~flask.cli.FlaskGroup` :meth:`~cli.AppGroup.command` decorator
|
||||||
context pushed, so your command and extensions have access to the app and its
|
will be executed with an application context pushed, so your custom
|
||||||
configuration. If you create a command using the Click :func:`~click.command`
|
commands and parameters have access to the app and its configuration. The
|
||||||
decorator instead of the Flask decorator, you can use
|
:func:`~cli.with_appcontext` decorator can be used to get the same
|
||||||
:func:`~cli.with_appcontext` to get the same behavior. ::
|
behavior, but is not needed in most cases.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
|
|
@ -454,12 +456,6 @@ decorator instead of the Flask decorator, you can use
|
||||||
|
|
||||||
app.cli.add_command(do_work)
|
app.cli.add_command(do_work)
|
||||||
|
|
||||||
If you're sure a command doesn't need the context, you can disable it::
|
|
||||||
|
|
||||||
@app.cli.command(with_appcontext=False)
|
|
||||||
def do_work():
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
Plugins
|
Plugins
|
||||||
-------
|
-------
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ response is sent.
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from flask import current_app, g
|
from flask import current_app, g
|
||||||
from flask.cli import with_appcontext
|
|
||||||
|
|
||||||
|
|
||||||
def get_db():
|
def get_db():
|
||||||
|
|
@ -128,7 +127,6 @@ Add the Python functions that will run these SQL commands to the
|
||||||
|
|
||||||
|
|
||||||
@click.command('init-db')
|
@click.command('init-db')
|
||||||
@with_appcontext
|
|
||||||
def init_db_command():
|
def init_db_command():
|
||||||
"""Clear the existing data and create new tables."""
|
"""Clear the existing data and create new tables."""
|
||||||
init_db()
|
init_db()
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import sqlite3
|
||||||
import click
|
import click
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import g
|
from flask import g
|
||||||
from flask.cli import with_appcontext
|
|
||||||
|
|
||||||
|
|
||||||
def get_db():
|
def get_db():
|
||||||
|
|
@ -39,7 +38,6 @@ def init_db():
|
||||||
|
|
||||||
|
|
||||||
@click.command("init-db")
|
@click.command("init-db")
|
||||||
@with_appcontext
|
|
||||||
def init_db_command():
|
def init_db_command():
|
||||||
"""Clear existing data and create new tables."""
|
"""Clear existing data and create new tables."""
|
||||||
init_db()
|
init_db()
|
||||||
|
|
|
||||||
|
|
@ -410,15 +410,25 @@ pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
|
||||||
|
|
||||||
def with_appcontext(f):
|
def with_appcontext(f):
|
||||||
"""Wraps a callback so that it's guaranteed to be executed with the
|
"""Wraps a callback so that it's guaranteed to be executed with the
|
||||||
script's application context. If callbacks are registered directly
|
script's application context.
|
||||||
to the ``app.cli`` object then they are wrapped with this function
|
|
||||||
by default unless it's disabled.
|
Custom commands (and their options) registered under ``app.cli`` or
|
||||||
|
``blueprint.cli`` will always have an app context available, this
|
||||||
|
decorator is not required in that case.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.2
|
||||||
|
The app context is active for subcommands as well as the
|
||||||
|
decorated callback. The app context is always available to
|
||||||
|
``app.cli`` command and parameter callbacks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def decorator(__ctx, *args, **kwargs):
|
def decorator(__ctx, *args, **kwargs):
|
||||||
with __ctx.ensure_object(ScriptInfo).load_app().app_context():
|
if not current_app:
|
||||||
return __ctx.invoke(f, *args, **kwargs)
|
app = __ctx.ensure_object(ScriptInfo).load_app()
|
||||||
|
__ctx.with_resource(app.app_context())
|
||||||
|
|
||||||
|
return __ctx.invoke(f, *args, **kwargs)
|
||||||
|
|
||||||
return update_wrapper(decorator, f)
|
return update_wrapper(decorator, f)
|
||||||
|
|
||||||
|
|
@ -587,6 +597,10 @@ class FlaskGroup(AppGroup):
|
||||||
Added the ``-A/--app``, ``-E/--env``, ``--debug/--no-debug``,
|
Added the ``-A/--app``, ``-E/--env``, ``--debug/--no-debug``,
|
||||||
and ``-e/--env-file`` options.
|
and ``-e/--env-file`` options.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.2
|
||||||
|
An app context is pushed when running ``app.cli`` commands, so
|
||||||
|
``@with_appcontext`` is no longer required for those commands.
|
||||||
|
|
||||||
.. versionchanged:: 1.0
|
.. versionchanged:: 1.0
|
||||||
If installed, python-dotenv will be used to load environment variables
|
If installed, python-dotenv will be used to load environment variables
|
||||||
from :file:`.env` and :file:`.flaskenv` files.
|
from :file:`.env` and :file:`.flaskenv` files.
|
||||||
|
|
@ -660,9 +674,18 @@ class FlaskGroup(AppGroup):
|
||||||
# Look up commands provided by the app, showing an error and
|
# Look up commands provided by the app, showing an error and
|
||||||
# continuing if the app couldn't be loaded.
|
# continuing if the app couldn't be loaded.
|
||||||
try:
|
try:
|
||||||
return info.load_app().cli.get_command(ctx, name)
|
app = info.load_app()
|
||||||
except NoAppException as e:
|
except NoAppException as e:
|
||||||
click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
|
click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Push an app context for the loaded app unless it is already
|
||||||
|
# active somehow. This makes the context available to parameter
|
||||||
|
# and command callbacks without needing @with_appcontext.
|
||||||
|
if not current_app or current_app._get_current_object() is not app:
|
||||||
|
ctx.with_resource(app.app_context())
|
||||||
|
|
||||||
|
return app.cli.get_command(ctx, name)
|
||||||
|
|
||||||
def list_commands(self, ctx):
|
def list_commands(self, ctx):
|
||||||
self._load_plugin_commands()
|
self._load_plugin_commands()
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import pytest
|
||||||
from _pytest.monkeypatch import notset
|
from _pytest.monkeypatch import notset
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from flask import _app_ctx_stack
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
@ -310,6 +311,26 @@ def test_lazy_load_error(monkeypatch):
|
||||||
lazy._flush_bg_loading_exception()
|
lazy._flush_bg_loading_exception()
|
||||||
|
|
||||||
|
|
||||||
|
def test_app_cli_has_app_context(app, runner):
|
||||||
|
def _param_cb(ctx, param, value):
|
||||||
|
# current_app should be available in parameter callbacks
|
||||||
|
return bool(current_app)
|
||||||
|
|
||||||
|
@app.cli.command()
|
||||||
|
@click.argument("value", callback=_param_cb)
|
||||||
|
def check(value):
|
||||||
|
app = click.get_current_context().obj.load_app()
|
||||||
|
# the loaded app should be the same as current_app
|
||||||
|
same_app = current_app._get_current_object() is app
|
||||||
|
# only one app context should be pushed
|
||||||
|
stack_size = len(_app_ctx_stack._local.stack)
|
||||||
|
return same_app, stack_size, value
|
||||||
|
|
||||||
|
cli = FlaskGroup(create_app=lambda: app)
|
||||||
|
result = runner.invoke(cli, ["check", "x"], standalone_mode=False)
|
||||||
|
assert result.return_value == (True, 1, True)
|
||||||
|
|
||||||
|
|
||||||
def test_with_appcontext(runner):
|
def test_with_appcontext(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
|
|
@ -323,12 +344,12 @@ def test_with_appcontext(runner):
|
||||||
assert result.output == "testapp\n"
|
assert result.output == "testapp\n"
|
||||||
|
|
||||||
|
|
||||||
def test_appgroup(runner):
|
def test_appgroup_app_context(runner):
|
||||||
@click.group(cls=AppGroup)
|
@click.group(cls=AppGroup)
|
||||||
def cli():
|
def cli():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@cli.command(with_appcontext=True)
|
@cli.command()
|
||||||
def test():
|
def test():
|
||||||
click.echo(current_app.name)
|
click.echo(current_app.name)
|
||||||
|
|
||||||
|
|
@ -336,7 +357,7 @@ def test_appgroup(runner):
|
||||||
def subgroup():
|
def subgroup():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@subgroup.command(with_appcontext=True)
|
@subgroup.command()
|
||||||
def test2():
|
def test2():
|
||||||
click.echo(current_app.name)
|
click.echo(current_app.name)
|
||||||
|
|
||||||
|
|
@ -351,7 +372,7 @@ def test_appgroup(runner):
|
||||||
assert result.output == "testappgroup\n"
|
assert result.output == "testappgroup\n"
|
||||||
|
|
||||||
|
|
||||||
def test_flaskgroup(runner):
|
def test_flaskgroup_app_context(runner):
|
||||||
def create_app():
|
def create_app():
|
||||||
return Flask("flaskgroup")
|
return Flask("flaskgroup")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue