kochb · GitHub

Description

My create_app factory accepts optional variable length keyword arguments:

def create_app(**kwargs):
    pass

We use these to optionally inject pre-initialized service objects into the application, usually for testing purposes. This worked through Flask 1.1.4, but breaks due to changes regarding the script_info parameter made in 2.0.0.

Error: Detected factory 'create_app' in module 'service.main', but could not call it without arguments. Use "FLASK_APP='service.main:create_app(args)'" to specify arguments.

The buggy logic is in call_factory; inspect considers the default value for a **kwargs argument to be empty. This causes the flask code to inject script_info as a positional argument, despite the function signature not accepting any positional args. A check needs to be added to exclude variable length kwargs, for example:

if (
    not args
    and len(sig.parameters) == 1
    and next(iter(sig.parameters.values())).default is inspect.Parameter.empty
    and next(iter(sig.parameters.values())).kind is not inspect.Parameter.VAR_KEYWORD
):

Test Case

Here's a test case that reproduces the bug:

class Module:
    @staticmethod
    def create_app(**kwargs):
        return Flask("appname")
app = find_best_app(script_info, Module)
assert isinstance(app, Flask)
assert app.name == "appname"

I was hoping to submit a simple patch for this issue, but the change I described above breaks tests for auto_reload and debug in ways that I don't quite understand.

src/flask/cli.py:75: NoAppException
___ test_flaskgroup_debug[True] ___
runner = <click.testing.CliRunner object at 0x10589f580>, set_debug_flag = True
    @pytest.mark.parametrize("set_debug_flag", (True, False))
    def test_flaskgroup_debug(runner, set_debug_flag):
        def create_app():
            app = Flask("flaskgroup")
            app.debug = True
            return app
        @click.group(cls=FlaskGroup, create_app=create_app, set_debug_flag=set_debug_flag)
        def cli(**params):
            pass
        @cli.command()
        def test():
            click.echo(str(current_app.debug))
        result = runner.invoke(cli, ["test"])
        assert result.exit_code == 0
>       assert result.output == f"{not set_debug_flag}\n"
E       AssertionError: assert 'True\n' == 'False\n'
E         - False
E         + True
tests/test_cli.py:406: AssertionError
___ test_templates_auto_reload_debug_run ___
app = <Flask 'flask_test'>, monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x1057ad9d0>
    def test_templates_auto_reload_debug_run(app, monkeypatch):
        def run_simple_mock(*args, **kwargs):
            pass
        monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock)
        app.run()
>       assert not app.templates_auto_reload
E       AssertionError: assert not True
E        +  where True = <Flask 'flask_test'>.templates_auto_reload

Environment

  • Python version: 3.8.6
  • Flask version: 2.0.1

Read the original on github.com ↗