Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support create_app without script_info or with script_info as named argument #2319

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
--------------
Expand Down
2 changes: 2 additions & 0 deletions flask/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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')
Expand Down
28 changes: 21 additions & 7 deletions flask/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -259,15 +273,15 @@ 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(
'Could not locate Flask application. You did not provide '
'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
Expand Down
62 changes: 41 additions & 21 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down