Browse Source

rewrite cli errors

consistent order for arguments to load functions
refactor find_app_by_string to flow better
more cli loader tests
pull/2490/head
David Lord 7 years ago
parent
commit
5436dddf64
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
  1. 109
      flask/cli.py
  2. 4
      tests/test_apps/cliapp/factory.py
  3. 8
      tests/test_cli.py

109
flask/cli.py

@ -46,6 +46,7 @@ def find_best_app(script_info, module):
# Search for the most common names first. # Search for the most common names first.
for attr_name in ('app', 'application'): for attr_name in ('app', 'application'):
app = getattr(module, attr_name, None) app = getattr(module, attr_name, None)
if isinstance(app, Flask): if isinstance(app, Flask):
return app return app
@ -58,8 +59,8 @@ def find_best_app(script_info, module):
return matches[0] return matches[0]
elif len(matches) > 1: elif len(matches) > 1:
raise NoAppException( raise NoAppException(
'Auto-detected multiple Flask applications in module "{module}".' 'Detected multiple Flask applications in module "{module}". Use '
' Use "FLASK_APP={module}:name" to specify the correct' '"FLASK_APP={module}:name" to specify the correct '
'one.'.format(module=module.__name__) 'one.'.format(module=module.__name__)
) )
@ -69,25 +70,29 @@ def find_best_app(script_info, module):
if inspect.isfunction(app_factory): if inspect.isfunction(app_factory):
try: try:
app = call_factory(app_factory, script_info) app = call_factory(script_info, app_factory)
if isinstance(app, Flask): if isinstance(app, Flask):
return app return app
except TypeError: except TypeError:
raise NoAppException( raise NoAppException(
'Auto-detected "{function}()" in module "{module}", but ' 'Detected factory "{factory}" in module "{module}", but '
'could not call it without specifying arguments.'.format( 'could not call it without arguments. Use '
function=attr_name, module=module.__name__ '"FLASK_APP=\'{module}:{factory}(args)\'" to specify '
'arguments.'.format(
factory=attr_name, module=module.__name__
) )
) )
raise NoAppException( raise NoAppException(
'Failed to find application in module "{module}". Are you sure ' 'Failed to find Flask application or factory in module "{module}". '
'it contains a Flask application? Maybe you wrapped it in a WSGI ' 'Use "FLASK_APP={module}:name to specify one.'.format(
'middleware.'.format(module=module.__name__) module=module.__name__
)
) )
def call_factory(app_factory, script_info, arguments=()): def call_factory(script_info, app_factory, arguments=()):
"""Takes an app factory, a ``script_info` object and optionally a tuple """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 of arguments. Checks for the existence of a script_info argument and calls
the app_factory depending on that and the arguments provided. the app_factory depending on that and the arguments provided.
@ -102,54 +107,65 @@ def call_factory(app_factory, script_info, arguments=()):
return app_factory(*arguments) return app_factory(*arguments)
elif not arguments and len(arg_names) == 1 and arg_defaults is None: elif not arguments and len(arg_names) == 1 and arg_defaults is None:
return app_factory(script_info) return app_factory(script_info)
return app_factory() return app_factory()
def find_app_by_string(string, script_info, module): def find_app_by_string(script_info, module, app_name):
"""Checks if the given string is a variable name or a function. If it is """Checks if the given string is a variable name or a function. If it is a
a function, it checks for specified arguments and whether it takes function, it checks for specified arguments and whether it takes a
a ``script_info`` argument and calls the function with the appropriate ``script_info`` argument and calls the function with the appropriate
arguments.""" arguments.
from . import Flask """
function_regex = r'^(?P<name>\w+)(?:\((?P<args>.*)\))?$' from flask import Flask
match = re.match(function_regex, string) match = re.match(r'^ *([^ ()]+) *(?:\((.*?) *,? *\))? *$', app_name)
if match:
if not match:
raise NoAppException(
'"{name}" is not a valid variable name or function '
'expression.'.format(name=app_name)
)
name, args = match.groups() name, args = match.groups()
try: try:
if args is not None: attr = getattr(module, name)
args = args.rstrip(' ,') except AttributeError as e:
raise NoAppException(e.args[0])
if inspect.isfunction(attr):
if args: if args:
args = ast.literal_eval( try:
"({args}, )".format(args=args)) args = ast.literal_eval('({args},)'.format(args=args))
except (ValueError, SyntaxError)as e:
raise NoAppException(
'Could not parse the arguments in '
'"{app_name}".'.format(e=e, app_name=app_name)
)
else: else:
args = () args = ()
app_factory = getattr(module, name, None)
app = call_factory(app_factory, script_info, args) try:
else: app = call_factory(script_info, attr, args)
attr = getattr(module, name, None) except TypeError as e:
if inspect.isfunction(attr): raise NoAppException(
app = call_factory(attr, script_info) '{e}\nThe factory "{app_name}" in module "{module}" could not '
'be called with the specified arguments.'.format(
e=e, app_name=app_name, module=module.__name__
)
)
else: else:
app = attr app = attr
if isinstance(app, Flask): if isinstance(app, Flask):
return app return app
else:
raise NoAppException('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( raise NoAppException(
'The provided string "{string}" is not a valid variable name' 'A valid Flask application was not obtained from '
'or function expression.'.format(string=string)) '"{module}:{app_name}".'.format(
module=module.__name__, app_name=app_name
)
)
def prepare_import(path): def prepare_import(path):
@ -181,7 +197,6 @@ def prepare_import(path):
def locate_app(script_info, module_name, app_name, raise_if_not_found=True): def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
"""Attempts to locate the application."""
__traceback_hide__ = True __traceback_hide__ = True
try: try:
@ -206,7 +221,7 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
if app_name is None: if app_name is None:
return find_best_app(script_info, module) return find_best_app(script_info, module)
else: else:
return find_app_by_string(app_name, script_info, module) return find_app_by_string(script_info, module, app_name)
def get_version(ctx, param, value): def get_version(ctx, param, value):
@ -312,7 +327,7 @@ class ScriptInfo(object):
app = None app = None
if self.create_app is not None: if self.create_app is not None:
app = call_factory(self.create_app, self) app = call_factory(self, self.create_app)
else: else:
if self.app_import_path: if self.app_import_path:
path, name = (self.app_import_path.split(':', 1) + [None])[:2] path, name = (self.app_import_path.split(':', 1) + [None])[:2]

4
tests/test_apps/cliapp/factory.py

@ -13,3 +13,7 @@ def create_app2(foo, bar):
def create_app3(foo, script_info): def create_app3(foo, script_info):
return Flask('_'.join(['app3', foo, script_info.data['test']])) return Flask('_'.join(['app3', foo, script_info.data['test']]))
def no_app():
pass

8
tests/test_cli.py

@ -200,6 +200,8 @@ def test_prepare_import(request, value, path, result):
('cliapp.factory', 'create_app2("foo", "bar", )', 'app2_foo_bar'), ('cliapp.factory', 'create_app2("foo", "bar", )', 'app2_foo_bar'),
# takes script_info # takes script_info
('cliapp.factory', 'create_app3("foo")', 'app3_foo_spam'), ('cliapp.factory', 'create_app3("foo")', 'app3_foo_spam'),
# strip whitespace
('cliapp.factory', ' create_app () ', 'app'),
)) ))
def test_locate_app(test_apps, iname, aname, result): def test_locate_app(test_apps, iname, aname, result):
info = ScriptInfo() info = ScriptInfo()
@ -213,12 +215,14 @@ def test_locate_app(test_apps, iname, aname, result):
('cliapp.app', 'notanapp'), ('cliapp.app', 'notanapp'),
# not enough arguments # not enough arguments
('cliapp.factory', 'create_app2("foo")'), ('cliapp.factory', 'create_app2("foo")'),
# invalid identifier
('cliapp.factory', 'create_app('),
# no app returned
('cliapp.factory', 'no_app'),
# nested import error # nested import error
('cliapp.importerrorapp', None), ('cliapp.importerrorapp', None),
# not a Python file # not a Python file
('cliapp.message.txt', None), ('cliapp.message.txt', None),
# space before arg list
('cliapp.factory', 'create_app ()'),
)) ))
def test_locate_app_raises(test_apps, iname, aname): def test_locate_app_raises(test_apps, iname, aname):
info = ScriptInfo() info = ScriptInfo()

Loading…
Cancel
Save