diff --git a/CHANGES b/CHANGES index d243d66d5a..c37d3f7f30 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,10 @@ Major release, unreleased (`#2282`_) - Auto-detect zero-argument app factory called ``create_app`` or ``make_app`` from ``FLASK_APP``. (`#2297`_) +- Factory functions are not required to take a ``script_info`` parameter to + work with the ``flask`` command. If they take a single parameter or a + parameter named ``script_info``, the ``ScriptInfo`` object will be passed. + (`#2319`_) .. _#1489: https://github.com/pallets/flask/pull/1489 .. _#1898: https://github.com/pallets/flask/pull/1898 @@ -53,6 +57,7 @@ Major release, unreleased .. _#2259: https://github.com/pallets/flask/pull/2259 .. _#2282: https://github.com/pallets/flask/pull/2282 .. _#2297: https://github.com/pallets/flask/pull/2297 +.. _#2319: https://github.com/pallets/flask/pull/2319 Version 0.12.2 -------------- diff --git a/flask/_compat.py b/flask/_compat.py index 071628fc11..173b3689fd 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -25,6 +25,7 @@ itervalues = lambda d: iter(d.values()) iteritems = lambda d: iter(d.items()) + from inspect import getfullargspec as getargspec from io import StringIO def reraise(tp, value, tb=None): @@ -43,6 +44,7 @@ def reraise(tp, value, tb=None): itervalues = lambda d: d.itervalues() iteritems = lambda d: d.iteritems() + from inspect import getargspec from cStringIO import StringIO exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') diff --git a/flask/cli.py b/flask/cli.py index 6aa66f4f1a..e6e8f65f57 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -22,13 +22,14 @@ from ._compat import iteritems, reraise from .globals import current_app from .helpers import get_debug_flag +from ._compat import getargspec class NoAppException(click.UsageError): """Raised if an application cannot be found or loaded.""" -def find_best_app(module): +def find_best_app(script_info, module): """Given a module instance this tries to find the best possible application in the module or raises an exception. """ @@ -60,8 +61,7 @@ def find_best_app(module): if callable(app_factory): try: - app = app_factory() - + app = call_factory(app_factory, script_info) if isinstance(app, Flask): return app except TypeError: @@ -79,6 +79,20 @@ def find_best_app(module): ) +def call_factory(func, script_info): + """Checks if the given app factory function has an argument named + ``script_info`` or just a single argument and calls the function passing + ``script_info`` if so. Otherwise, calls the function without any arguments + and returns the result. + """ + arguments = getargspec(func).args + if 'script_info' in arguments: + return func(script_info=script_info) + elif len(arguments) == 1: + return func(script_info) + return func() + + def prepare_exec_for_file(filename): """Given a filename this will try to calculate the python path, add it to the search path and return the actual module name that is expected. @@ -108,7 +122,7 @@ def prepare_exec_for_file(filename): return '.'.join(module[::-1]) -def locate_app(app_id): +def locate_app(script_info, app_id): """Attempts to locate the application.""" __traceback_hide__ = True if ':' in app_id: @@ -134,7 +148,7 @@ def locate_app(app_id): mod = sys.modules[module] if app_obj is None: - app = find_best_app(mod) + app = find_best_app(script_info, mod) else: app = getattr(mod, app_obj, None) if app is None: @@ -259,7 +273,7 @@ def load_app(self): if self._loaded_app is not None: return self._loaded_app if self.create_app is not None: - rv = self.create_app(self) + rv = call_factory(self.create_app, self) else: if not self.app_import_path: raise NoAppException( @@ -267,7 +281,7 @@ def load_app(self): 'the FLASK_APP environment variable.\n\nFor more ' 'information see ' 'http://flask.pocoo.org/docs/latest/quickstart/') - rv = locate_app(self.app_import_path) + rv = locate_app(self, self.app_import_path) debug = get_debug_flag() if debug is not None: rv.debug = debug diff --git a/tests/test_cli.py b/tests/test_cli.py index bbb6fe5854..1c843d61cd 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -39,60 +39,75 @@ def test_cli_name(test_apps): def test_find_best_app(test_apps): """Test if `find_best_app` behaves as expected with different combinations of input.""" + script_info = ScriptInfo() class Module: app = Flask('appname') - assert find_best_app(Module) == Module.app + assert find_best_app(script_info, Module) == Module.app class Module: application = Flask('appname') - assert find_best_app(Module) == Module.application + assert find_best_app(script_info, Module) == Module.application class Module: myapp = Flask('appname') - assert find_best_app(Module) == Module.myapp + assert find_best_app(script_info, Module) == Module.myapp class Module: @staticmethod def create_app(): return Flask('appname') - assert isinstance(find_best_app(Module), Flask) - assert find_best_app(Module).name == 'appname' + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' + + class Module: + @staticmethod + def create_app(foo): + return Flask('appname') + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' + + class Module: + @staticmethod + def create_app(foo=None, script_info=None): + return Flask('appname') + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' class Module: @staticmethod def make_app(): return Flask('appname') - assert isinstance(find_best_app(Module), Flask) - assert find_best_app(Module).name == 'appname' + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' class Module: myapp = Flask('appname1') @staticmethod def create_app(): return Flask('appname2') - assert find_best_app(Module) == Module.myapp + assert find_best_app(script_info, Module) == Module.myapp class Module: myapp = Flask('appname1') @staticmethod - def create_app(foo): + def create_app(): return Flask('appname2') - assert find_best_app(Module) == Module.myapp + assert find_best_app(script_info, Module) == Module.myapp class Module: pass - pytest.raises(NoAppException, find_best_app, Module) + pytest.raises(NoAppException, find_best_app, script_info, Module) class Module: myapp1 = Flask('appname1') myapp2 = Flask('appname2') - pytest.raises(NoAppException, find_best_app, Module) + pytest.raises(NoAppException, find_best_app, script_info, Module) class Module: @staticmethod - def create_app(foo): + def create_app(foo, bar): return Flask('appname2') - pytest.raises(NoAppException, find_best_app, Module) + pytest.raises(NoAppException, find_best_app, script_info, Module) def test_prepare_exec_for_file(test_apps): @@ -117,13 +132,18 @@ def test_prepare_exec_for_file(test_apps): def test_locate_app(test_apps): """Test of locate_app.""" - assert locate_app("cliapp.app").name == "testapp" - assert locate_app("cliapp.app:testapp").name == "testapp" - assert locate_app("cliapp.multiapp:app1").name == "app1" - pytest.raises(NoAppException, locate_app, "notanpp.py") - pytest.raises(NoAppException, locate_app, "cliapp/app") - pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") - pytest.raises(NoAppException, locate_app, "cliapp.importerrorapp") + script_info = ScriptInfo() + assert locate_app(script_info, "cliapp.app").name == "testapp" + assert locate_app(script_info, "cliapp.app:testapp").name == "testapp" + assert locate_app(script_info, "cliapp.multiapp:app1").name == "app1" + pytest.raises(NoAppException, locate_app, + script_info, "notanpp.py") + pytest.raises(NoAppException, locate_app, + script_info, "cliapp/app") + pytest.raises(RuntimeError, locate_app, + script_info, "cliapp.app:notanapp") + pytest.raises(NoAppException, locate_app, + script_info, "cliapp.importerrorapp") def test_find_default_import_path(test_apps, monkeypatch, tmpdir):