Browse Source

Merge pull request #2326 from HndrkMkt/#2264-handle-app-factory-in-FLASK_APP

Handle app factory with arguments in FLASK_APP
pull/2338/head
Kenneth Reitz 8 years ago committed by GitHub
parent
commit
090109b637
  1. 90
      flask/cli.py
  2. 15
      tests/test_apps/cliapp/factory.py
  3. 38
      tests/test_cli.py

90
flask/cli.py

@ -9,7 +9,10 @@
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import ast
import inspect
import os import os
import re
import sys import sys
import traceback import traceback
from functools import update_wrapper from functools import update_wrapper
@ -55,20 +58,20 @@ def find_best_app(script_info, module):
' one.'.format(module=module.__name__) ' one.'.format(module=module.__name__)
) )
# Search for app factory callables. # Search for app factory functions.
for attr_name in ('create_app', 'make_app'): for attr_name in ('create_app', 'make_app'):
app_factory = getattr(module, attr_name, None) app_factory = getattr(module, attr_name, None)
if callable(app_factory): if inspect.isfunction(app_factory):
try: try:
app = call_factory(app_factory, script_info) app = call_factory(app_factory, script_info)
if isinstance(app, Flask): if isinstance(app, Flask):
return app return app
except TypeError: except TypeError:
raise NoAppException( raise NoAppException(
'Auto-detected "{callable}()" in module "{module}", but ' 'Auto-detected "{function}()" in module "{module}", but '
'could not call it without specifying arguments.'.format( 'could not call it without specifying arguments.'.format(
callable=attr_name, module=module.__name__ function=attr_name, module=module.__name__
) )
) )
@ -79,18 +82,66 @@ def find_best_app(script_info, module):
) )
def call_factory(func, script_info): def call_factory(app_factory, script_info, arguments=()):
"""Checks if the given app factory function has an argument named """Takes an app factory, a ``script_info` object and optionally a tuple
``script_info`` or just a single argument and calls the function passing of arguments. Checks for the existence of a script_info argument and calls
``script_info`` if so. Otherwise, calls the function without any arguments the app_factory depending on that and the arguments provided.
and returns the result.
""" """
arguments = getargspec(func).args arg_names = getargspec(app_factory).args
if 'script_info' in arguments: if 'script_info' in arg_names:
return func(script_info=script_info) return app_factory(*arguments, script_info=script_info)
elif len(arguments) == 1: elif arguments:
return func(script_info) return app_factory(*arguments)
return func() elif not arguments and len(arg_names) == 1:
return app_factory(script_info)
return app_factory()
def find_app_by_string(string, script_info, module):
"""Checks if the given string is a variable name or a function. If it is
a function, it checks for specified arguments and whether it takes
a ``script_info`` argument and calls the function with the appropriate
arguments. If it is a """
from . import Flask
function_regex = r'^(?P<name>\w+)(?:\((?P<args>.*)\))?$'
match = re.match(function_regex, string)
if match:
name, args = match.groups()
try:
if args is not None:
args = args.rstrip(' ,')
if args:
args = ast.literal_eval(
"({args}, )".format(args=args))
else:
args = ()
app_factory = getattr(module, name, None)
app = call_factory(app_factory, script_info, args)
else:
attr = getattr(module, name, None)
if inspect.isfunction(attr):
app = call_factory(attr, script_info)
else:
app = attr
if isinstance(app, Flask):
return app
else:
raise RuntimeError('Failed to find application in module '
'"{name}"'.format(name=module))
except TypeError as e:
new_error = NoAppException(
'{e}\nThe app factory "{factory}" in module "{module}" could'
' not be called with the specified arguments (and a'
' script_info argument automatically added if applicable).'
' Did you make sure to use the right number of arguments as'
' well as not using keyword arguments or'
' non-literals?'.format(e=e, factory=string, module=module))
reraise(NoAppException, new_error, sys.exc_info()[2])
else:
raise NoAppException(
'The provided string "{string}" is not a valid variable name'
'or function expression.'.format(string=string))
def prepare_exec_for_file(filename): def prepare_exec_for_file(filename):
@ -148,14 +199,9 @@ def locate_app(script_info, app_id):
mod = sys.modules[module] mod = sys.modules[module]
if app_obj is None: if app_obj is None:
app = find_best_app(script_info, mod) return find_best_app(script_info, mod)
else: else:
app = getattr(mod, app_obj, None) return find_app_by_string(app_obj, script_info, mod)
if app is None:
raise RuntimeError('Failed to find application in module "%s"'
% module)
return app
def find_default_import_path(): def find_default_import_path():

15
tests/test_apps/cliapp/factory.py

@ -0,0 +1,15 @@
from __future__ import absolute_import, print_function
from flask import Flask
def create_app():
return Flask('create_app')
def create_app2(foo, bar):
return Flask("_".join(['create_app2', foo, bar]))
def create_app3(foo, bar, script_info):
return Flask("_".join(['create_app3', foo, bar]))

38
tests/test_cli.py

@ -150,15 +150,37 @@ def test_locate_app(test_apps):
script_info = ScriptInfo() script_info = ScriptInfo()
assert locate_app(script_info, "cliapp.app").name == "testapp" 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.app:testapp").name == "testapp"
assert locate_app(script_info, "cliapp.factory").name == "create_app"
assert locate_app(
script_info, "cliapp.factory").name == "create_app"
assert locate_app(
script_info, "cliapp.factory:create_app").name == "create_app"
assert locate_app(
script_info, "cliapp.factory:create_app()").name == "create_app"
assert locate_app(
script_info, "cliapp.factory:create_app2('foo', 'bar')"
).name == "create_app2_foo_bar"
assert locate_app(
script_info, "cliapp.factory:create_app2('foo', 'bar', )"
).name == "create_app2_foo_bar"
assert locate_app(
script_info, "cliapp.factory:create_app3('baz', 'qux')"
).name == "create_app3_baz_qux"
assert locate_app(script_info, "cliapp.multiapp:app1").name == "app1" assert locate_app(script_info, "cliapp.multiapp:app1").name == "app1"
pytest.raises(NoAppException, locate_app, pytest.raises(
script_info, "notanpp.py") NoAppException, locate_app, script_info, "notanpp.py")
pytest.raises(NoAppException, locate_app, pytest.raises(
script_info, "cliapp/app") NoAppException, locate_app, script_info, "cliapp/app")
pytest.raises(RuntimeError, locate_app, pytest.raises(
script_info, "cliapp.app:notanapp") RuntimeError, locate_app, script_info, "cliapp.app:notanapp")
pytest.raises(NoAppException, locate_app, pytest.raises(
script_info, "cliapp.importerrorapp") NoAppException, locate_app,
script_info, "cliapp.factory:create_app2('foo')")
pytest.raises(
NoAppException, locate_app,
script_info, "cliapp.factory:create_app ()")
pytest.raises(
NoAppException, locate_app, script_info, "cliapp.importerrorapp")
def test_find_default_import_path(test_apps, monkeypatch, tmpdir): def test_find_default_import_path(test_apps, monkeypatch, tmpdir):

Loading…
Cancel
Save