Skip to content

create_app factory with variable length keyword arguments raises an error #4170

@kochb

Description

@kochb

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions