mirror of https://github.com/pallets/flask.git
Add Blueprint level cli command registration
Implements #1357. Adds ability to register click cli commands onto blueprint.
This commit is contained in:
parent
855d59b68b
commit
ec1ccd7530
|
@ -56,6 +56,9 @@ Unreleased
|
|||
returning a string will produce a ``text/html`` response, returning
|
||||
a dict will call ``jsonify`` to produce a ``application/json``
|
||||
response. :pr:`3111`
|
||||
- Blueprints have a ``cli`` Click group like ``app.cli``. CLI commands
|
||||
registered with a blueprint will be available as a group under the
|
||||
``flask`` command. :issue:`1357`.
|
||||
|
||||
.. _#2935: https://github.com/pallets/flask/issues/2935
|
||||
.. _#2957: https://github.com/pallets/flask/issues/2957
|
||||
|
|
56
docs/cli.rst
56
docs/cli.rst
|
@ -310,10 +310,66 @@ group. This is useful if you want to organize multiple related commands. ::
|
|||
|
||||
$ flask user create demo
|
||||
|
||||
|
||||
See :ref:`testing-cli` for an overview of how to test your custom
|
||||
commands.
|
||||
|
||||
|
||||
Registering Commands with Blueprints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If your application uses blueprints, you can optionally register CLI
|
||||
commands directly onto them. When your blueprint is registered onto your
|
||||
application, the associated commands will be available to the ``flask``
|
||||
command. By default, those commands will be nested in a group matching
|
||||
the name of the blueprint.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Blueprint
|
||||
|
||||
bp = Blueprint('students', __name__)
|
||||
|
||||
@bp.cli.command('create')
|
||||
@click.argument('name')
|
||||
def create(name):
|
||||
...
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask students create alice
|
||||
|
||||
You can alter the group name by specifying the ``cli_group`` parameter
|
||||
when creating the :class:`Blueprint` object, or later with
|
||||
:meth:`app.register_blueprint(bp, cli_group='...') <Flask.register_blueprint>`.
|
||||
The following are equivalent:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bp = Blueprint('students', __name__, cli_group='other')
|
||||
# or
|
||||
app.register_blueprint(bp, cli_group='other')
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask other create alice
|
||||
|
||||
Specifying ``cli_group=None`` will remove the nesting and merge the
|
||||
commands directly to the application's level:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bp = Blueprint('students', __name__, cli_group=None)
|
||||
# or
|
||||
app.register_blueprint(bp, cli_group=None)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask create alice
|
||||
|
||||
|
||||
Application Context
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
10
flask/app.py
10
flask/app.py
|
@ -600,13 +600,9 @@ class Flask(_PackageBoundObject):
|
|||
view_func=self.send_static_file,
|
||||
)
|
||||
|
||||
#: The click command line context for this application. Commands
|
||||
#: registered here show up in the :command:`flask` command once the
|
||||
#: application has been discovered. The default commands are
|
||||
#: provided by Flask itself and can be overridden.
|
||||
#:
|
||||
#: This is an instance of a :class:`click.Group` object.
|
||||
self.cli = cli.AppGroup(self.name)
|
||||
# Set the name of the Click group in case someone wants to add
|
||||
# the app's commands to another CLI tool.
|
||||
self.cli.name = self.name
|
||||
|
||||
@locked_cached_property
|
||||
def name(self):
|
||||
|
|
|
@ -13,6 +13,9 @@ from functools import update_wrapper
|
|||
|
||||
from .helpers import _PackageBoundObject, _endpoint_from_view_func
|
||||
|
||||
# a singleton sentinel value for parameter defaults
|
||||
_sentinel = object()
|
||||
|
||||
|
||||
class BlueprintSetupState(object):
|
||||
"""Temporary holder object for registering a blueprint with the
|
||||
|
@ -90,6 +93,11 @@ class Blueprint(_PackageBoundObject):
|
|||
or other things on the main application. See :ref:`blueprints` for more
|
||||
information.
|
||||
|
||||
.. versionchanged:: 1.1.0
|
||||
Blueprints have a ``cli`` group to register nested CLI commands.
|
||||
The ``cli_group`` parameter controls the name of the group under
|
||||
the ``flask`` command.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
|
||||
|
@ -129,6 +137,7 @@ class Blueprint(_PackageBoundObject):
|
|||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_group=_sentinel,
|
||||
):
|
||||
_PackageBoundObject.__init__(
|
||||
self, import_name, template_folder, root_path=root_path
|
||||
|
@ -142,6 +151,7 @@ class Blueprint(_PackageBoundObject):
|
|||
if url_defaults is None:
|
||||
url_defaults = {}
|
||||
self.url_values_defaults = url_defaults
|
||||
self.cli_group = cli_group
|
||||
|
||||
def record(self, func):
|
||||
"""Registers a function that is called when the blueprint is
|
||||
|
@ -206,6 +216,17 @@ class Blueprint(_PackageBoundObject):
|
|||
for deferred in self.deferred_functions:
|
||||
deferred(state)
|
||||
|
||||
cli_resolved_group = options.get("cli_group", self.cli_group)
|
||||
|
||||
if cli_resolved_group is None:
|
||||
app.cli.commands.update(self.cli.commands)
|
||||
elif cli_resolved_group is _sentinel:
|
||||
self.cli.name = self.name
|
||||
app.cli.add_command(self.cli)
|
||||
else:
|
||||
self.cli.name = cli_resolved_group
|
||||
app.cli.add_command(self.cli)
|
||||
|
||||
def route(self, rule, **options):
|
||||
"""Like :meth:`Flask.route` but for a blueprint. The endpoint for the
|
||||
:func:`url_for` function is prefixed with the name of the blueprint.
|
||||
|
|
|
@ -942,6 +942,15 @@ class _PackageBoundObject(object):
|
|||
self._static_folder = None
|
||||
self._static_url_path = None
|
||||
|
||||
# circular import
|
||||
from .cli import AppGroup
|
||||
|
||||
#: The Click command group for registration of CLI commands
|
||||
#: on the application and associated blueprints. These commands
|
||||
#: are accessible via the :command:`flask` command once the
|
||||
#: application has been discovered and blueprints registered.
|
||||
self.cli = AppGroup()
|
||||
|
||||
def _get_static_folder(self):
|
||||
if self._static_folder is not None:
|
||||
return os.path.join(self.root_path, self._static_folder)
|
||||
|
|
|
@ -23,7 +23,7 @@ import pytest
|
|||
from _pytest.monkeypatch import notset
|
||||
from click.testing import CliRunner
|
||||
|
||||
from flask import Flask, current_app
|
||||
from flask import Flask, current_app, Blueprint
|
||||
from flask.cli import (
|
||||
AppGroup,
|
||||
FlaskGroup,
|
||||
|
@ -609,3 +609,46 @@ def test_run_cert_import(monkeypatch):
|
|||
# no --key with SSLContext
|
||||
with pytest.raises(click.BadParameter):
|
||||
run_command.make_context("run", ["--cert", "ssl_context", "--key", __file__])
|
||||
|
||||
|
||||
def test_cli_blueprints(app):
|
||||
"""Test blueprint commands register correctly to the application"""
|
||||
custom = Blueprint("custom", __name__, cli_group="customized")
|
||||
nested = Blueprint("nested", __name__)
|
||||
merged = Blueprint("merged", __name__, cli_group=None)
|
||||
late = Blueprint("late", __name__)
|
||||
|
||||
@custom.cli.command("custom")
|
||||
def custom_command():
|
||||
click.echo("custom_result")
|
||||
|
||||
@nested.cli.command("nested")
|
||||
def nested_command():
|
||||
click.echo("nested_result")
|
||||
|
||||
@merged.cli.command("merged")
|
||||
def merged_command():
|
||||
click.echo("merged_result")
|
||||
|
||||
@late.cli.command("late")
|
||||
def late_command():
|
||||
click.echo("late_result")
|
||||
|
||||
app.register_blueprint(custom)
|
||||
app.register_blueprint(nested)
|
||||
app.register_blueprint(merged)
|
||||
app.register_blueprint(late, cli_group="late_registration")
|
||||
|
||||
app_runner = app.test_cli_runner()
|
||||
|
||||
result = app_runner.invoke(args=["customized", "custom"])
|
||||
assert "custom_result" in result.output
|
||||
|
||||
result = app_runner.invoke(args=["nested", "nested"])
|
||||
assert "nested_result" in result.output
|
||||
|
||||
result = app_runner.invoke(args=["merged"])
|
||||
assert "merged_result" in result.output
|
||||
|
||||
result = app_runner.invoke(args=["late_registration", "late"])
|
||||
assert "late_result" in result.output
|
||||
|
|
Loading…
Reference in New Issue