From aa801c431ad1f979bce3ea1afbd88e7796ebfd7e Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 15 Jun 2022 14:07:00 -0700 Subject: [PATCH] FlaskGroup can be nested --- CHANGES.rst | 3 +++ src/flask/cli.py | 30 ++++++++++++++++++++---------- tests/test_cli.py | 13 +++++++++++++ 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ce85e673..84ffbf06 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -25,6 +25,9 @@ Unreleased - Added the ``View.init_every_request`` class attribute. If a view subclass sets this to ``False``, the view will not create a new instance on every request. :issue:`2520`. +- A ``flask.cli.FlaskGroup`` Click group can be nested as a + sub-command in a custom CLI. :issue:`3263` + Version 2.1.3 ------------- diff --git a/src/flask/cli.py b/src/flask/cli.py index 6e0359d9..a4e366d7 100644 --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -5,6 +5,7 @@ import platform import re import sys import traceback +import typing as t from functools import update_wrapper from operator import attrgetter from threading import Lock @@ -478,7 +479,13 @@ class FlaskGroup(AppGroup): if add_version_option: params.append(version_option) - AppGroup.__init__(self, params=params, **extra) + if "context_settings" not in extra: + extra["context_settings"] = {} + + extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK") + + super().__init__(params=params, **extra) + self.create_app = create_app self.load_dotenv = load_dotenv self.set_debug_flag = set_debug_flag @@ -546,20 +553,22 @@ class FlaskGroup(AppGroup): return sorted(rv) - def main(self, *args, **kwargs): + def make_context( + self, + info_name: t.Optional[str], + args: t.List[str], + parent: t.Optional[click.Context] = None, + **extra: t.Any, + ) -> click.Context: if get_load_dotenv(self.load_dotenv): load_dotenv() - obj = kwargs.get("obj") - - if obj is None: - obj = ScriptInfo( + if "obj" not in extra and "obj" not in self.context_settings: + extra["obj"] = ScriptInfo( create_app=self.create_app, set_debug_flag=self.set_debug_flag ) - kwargs["obj"] = obj - kwargs.setdefault("auto_envvar_prefix", "FLASK") - return super().main(*args, **kwargs) + return super().make_context(info_name, args, parent=parent, **extra) def _path_is_ancestor(path, other): @@ -958,6 +967,7 @@ def routes_command(sort: str, all_methods: bool) -> None: cli = FlaskGroup( + name="flask", help="""\ A general utility script for Flask applications. @@ -973,7 +983,7 @@ debug mode. """.format( cmd="export" if os.name == "posix" else "set", prefix="$ " if os.name == "posix" else "> ", - ) + ), ) diff --git a/tests/test_cli.py b/tests/test_cli.py index c9dd5ade..7a8e9af9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -388,6 +388,19 @@ def test_flaskgroup_debug(runner, set_debug_flag): assert result.output == f"{not set_debug_flag}\n" +def test_flaskgroup_nested(app, runner): + cli = click.Group("cli") + flask_group = FlaskGroup(name="flask", create_app=lambda: app) + cli.add_command(flask_group) + + @flask_group.command() + def show(): + click.echo(current_app.name) + + result = runner.invoke(cli, ["flask", "show"]) + assert result.output == "flask_test\n" + + def test_no_command_echo_loading_error(): from flask.cli import cli