diff --git a/CHANGES.rst b/CHANGES.rst index 6e092adc..2ef2f0e5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,11 @@ Version 2.1.0 Unreleased -- Update Click dependency to >= 8.0. +- Drop support for Python 3.6. :pr:`4335` +- Update Click dependency to >= 8.0. :pr:`4008` +- Remove previously deprecated code. :pr:`4337` + + - The CLI does not pass ``script_info`` to app factory functions. Version 2.0.2 diff --git a/src/flask/cli.py b/src/flask/cli.py index 7ab4fa1c..64e103fc 100644 --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -5,7 +5,6 @@ import platform import re import sys import traceback -import warnings from functools import update_wrapper from operator import attrgetter from threading import Lock @@ -34,7 +33,7 @@ class NoAppException(click.UsageError): """Raised if an application cannot be found or loaded.""" -def find_best_app(script_info, module): +def find_best_app(module): """Given a module instance this tries to find the best possible application in the module or raises an exception. """ @@ -65,7 +64,7 @@ def find_best_app(script_info, module): if inspect.isfunction(app_factory): try: - app = call_factory(script_info, app_factory) + app = app_factory() if isinstance(app, Flask): return app @@ -87,42 +86,6 @@ def find_best_app(script_info, module): ) -def call_factory(script_info, app_factory, args=None, kwargs=None): - """Takes an app factory, a ``script_info` object and optionally a tuple - of arguments. Checks for the existence of a script_info argument and calls - the app_factory depending on that and the arguments provided. - """ - sig = inspect.signature(app_factory) - args = [] if args is None else args - kwargs = {} if kwargs is None else kwargs - - if "script_info" in sig.parameters: - warnings.warn( - "The 'script_info' argument is deprecated and will not be" - " passed to the app factory function in Flask 2.1.", - DeprecationWarning, - ) - kwargs["script_info"] = script_info - - if not args and len(sig.parameters) == 1: - first_parameter = next(iter(sig.parameters.values())) - - if ( - first_parameter.default is inspect.Parameter.empty - # **kwargs is reported as an empty default, ignore it - and first_parameter.kind is not inspect.Parameter.VAR_KEYWORD - ): - warnings.warn( - "Script info is deprecated and will not be passed as the" - " single argument to the app factory function in Flask" - " 2.1.", - DeprecationWarning, - ) - args.append(script_info) - - return app_factory(*args, **kwargs) - - def _called_with_wrong_args(f): """Check whether calling a function raised a ``TypeError`` because the call failed or because something in the factory raised the @@ -149,7 +112,7 @@ def _called_with_wrong_args(f): del tb -def find_app_by_string(script_info, module, app_name): +def find_app_by_string(module, app_name): """Check if the given string is a variable name or a function. Call a function to get the app instance, or return the variable directly. """ @@ -166,7 +129,8 @@ def find_app_by_string(script_info, module, app_name): if isinstance(expr, ast.Name): name = expr.id - args = kwargs = None + args = [] + kwargs = {} elif isinstance(expr, ast.Call): # Ensure the function name is an attribute name only. if not isinstance(expr.func, ast.Name): @@ -202,7 +166,7 @@ def find_app_by_string(script_info, module, app_name): # to get the real application. if inspect.isfunction(attr): try: - app = call_factory(script_info, attr, args, kwargs) + app = attr(*args, **kwargs) except TypeError as e: if not _called_with_wrong_args(attr): raise @@ -253,7 +217,7 @@ def prepare_import(path): return ".".join(module_name[::-1]) -def locate_app(script_info, module_name, app_name, raise_if_not_found=True): +def locate_app(module_name, app_name, raise_if_not_found=True): __traceback_hide__ = True # noqa: F841 try: @@ -273,9 +237,9 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True): module = sys.modules[module_name] if app_name is None: - return find_best_app(script_info, module) + return find_best_app(module) else: - return find_app_by_string(script_info, module, app_name) + return find_app_by_string(module, app_name) def get_version(ctx, param, value): @@ -396,18 +360,18 @@ class ScriptInfo: return self._loaded_app if self.create_app is not None: - app = call_factory(self, self.create_app) + app = self.create_app() else: if self.app_import_path: path, name = ( re.split(r":(?![\\/])", self.app_import_path, 1) + [None] )[:2] import_name = prepare_import(path) - app = locate_app(self, import_name, name) + app = locate_app(import_name, name) else: for path in ("wsgi.py", "app.py"): import_name = prepare_import(path) - app = locate_app(self, import_name, None, raise_if_not_found=False) + app = locate_app(import_name, None, raise_if_not_found=False) if app: break @@ -983,15 +947,7 @@ debug mode. def main() -> None: - if int(click.__version__[0]) < 8: - warnings.warn( - "Using the `flask` cli with Click 7 is deprecated and" - " will not be supported starting with Flask 2.1." - " Please upgrade to Click 8 as soon as possible.", - DeprecationWarning, - ) - # TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed - cli.main(args=sys.argv[1:]) + cli.main() if __name__ == "__main__": diff --git a/tests/test_cli.py b/tests/test_cli.py index 08ba4800..a2fcc5a1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -49,29 +49,27 @@ def test_cli_name(test_apps): def test_find_best_app(test_apps): - script_info = ScriptInfo() - class Module: app = Flask("appname") - assert find_best_app(script_info, Module) == Module.app + assert find_best_app(Module) == Module.app class Module: application = Flask("appname") - assert find_best_app(script_info, Module) == Module.application + assert find_best_app(Module) == Module.application class Module: myapp = Flask("appname") - assert find_best_app(script_info, Module) == Module.myapp + assert find_best_app(Module) == Module.myapp class Module: @staticmethod def create_app(): return Flask("appname") - app = find_best_app(script_info, Module) + app = find_best_app(Module) assert isinstance(app, Flask) assert app.name == "appname" @@ -80,29 +78,7 @@ def test_find_best_app(test_apps): def create_app(**kwargs): return Flask("appname") - app = find_best_app(script_info, Module) - assert isinstance(app, Flask) - assert app.name == "appname" - - class Module: - @staticmethod - def create_app(foo): - return Flask("appname") - - with pytest.deprecated_call(match="Script info"): - app = find_best_app(script_info, Module) - - assert isinstance(app, Flask) - assert app.name == "appname" - - class Module: - @staticmethod - def create_app(foo=None, script_info=None): - return Flask("appname") - - with pytest.deprecated_call(match="script_info"): - app = find_best_app(script_info, Module) - + app = find_best_app(Module) assert isinstance(app, Flask) assert app.name == "appname" @@ -111,7 +87,7 @@ def test_find_best_app(test_apps): def make_app(): return Flask("appname") - app = find_best_app(script_info, Module) + app = find_best_app(Module) assert isinstance(app, Flask) assert app.name == "appname" @@ -122,7 +98,7 @@ def test_find_best_app(test_apps): def create_app(): return Flask("appname2") - assert find_best_app(script_info, Module) == Module.myapp + assert find_best_app(Module) == Module.myapp class Module: myapp = Flask("appname1") @@ -131,32 +107,32 @@ def test_find_best_app(test_apps): def create_app(): return Flask("appname2") - assert find_best_app(script_info, Module) == Module.myapp + assert find_best_app(Module) == Module.myapp class Module: pass - pytest.raises(NoAppException, find_best_app, script_info, Module) + pytest.raises(NoAppException, find_best_app, Module) class Module: myapp1 = Flask("appname1") myapp2 = Flask("appname2") - pytest.raises(NoAppException, find_best_app, script_info, Module) + pytest.raises(NoAppException, find_best_app, Module) class Module: @staticmethod def create_app(foo, bar): return Flask("appname2") - pytest.raises(NoAppException, find_best_app, script_info, Module) + pytest.raises(NoAppException, find_best_app, Module) class Module: @staticmethod def create_app(): raise TypeError("bad bad factory!") - pytest.raises(TypeError, find_best_app, script_info, Module) + pytest.raises(TypeError, find_best_app, Module) @pytest.mark.parametrize( @@ -220,8 +196,7 @@ def test_prepare_import(request, value, path, result): ), ) def test_locate_app(test_apps, iname, aname, result): - info = ScriptInfo() - assert locate_app(info, iname, aname).name == result + assert locate_app(iname, aname).name == result @pytest.mark.parametrize( @@ -243,20 +218,17 @@ def test_locate_app(test_apps, iname, aname, result): ), ) def test_locate_app_raises(test_apps, iname, aname): - info = ScriptInfo() - with pytest.raises(NoAppException): - locate_app(info, iname, aname) + locate_app(iname, aname) def test_locate_app_suppress_raise(test_apps): - info = ScriptInfo() - app = locate_app(info, "notanapp.py", None, raise_if_not_found=False) + app = locate_app("notanapp.py", None, raise_if_not_found=False) assert app is None # only direct import error is suppressed with pytest.raises(NoAppException): - locate_app(info, "cliapp.importerrorapp", None, raise_if_not_found=False) + locate_app("cliapp.importerrorapp", None, raise_if_not_found=False) def test_get_version(test_apps, capsys):