From c23a63a185104b4dc39fe53d82254059c5ff4727 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 25 Nov 2017 00:05:57 +0100 Subject: [PATCH 1/3] Improved bad factory error handling --- flask/cli.py | 10 ++++++++++ tests/test_cli.py | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/flask/cli.py b/flask/cli.py index 8bfb21f6..b9d56b05 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -33,6 +33,12 @@ except ImportError: dotenv = None +def _called_with_wrong_args(factory, exc_info): + exc_type, exc_value, tb = exc_info + return exc_type is TypeError and \ + str(exc_value).startswith('%s() takes' % factory.__name__) + + class NoAppException(click.UsageError): """Raised if an application cannot be found or loaded.""" @@ -75,6 +81,8 @@ def find_best_app(script_info, module): if isinstance(app, Flask): return app except TypeError: + if not _called_with_wrong_args(app_factory, sys.exc_info()): + raise raise NoAppException( 'Detected factory "{factory}" in module "{module}", but ' 'could not call it without arguments. Use ' @@ -148,6 +156,8 @@ def find_app_by_string(script_info, module, app_name): try: app = call_factory(script_info, attr, args) except TypeError as e: + if not _called_with_wrong_args(attr, sys.exc_info()): + raise raise NoAppException( '{e}\nThe factory "{app_name}" in module "{module}" could not ' 'be called with the specified arguments.'.format( diff --git a/tests/test_cli.py b/tests/test_cli.py index 811ef0c8..d530b093 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -144,6 +144,13 @@ def test_find_best_app(test_apps): pytest.raises(NoAppException, find_best_app, script_info, Module) + class Module: + @staticmethod + def create_app(): + raise TypeError('bad bad factory!') + + pytest.raises(TypeError, find_best_app, script_info, Module) + @pytest.mark.parametrize('value,path,result', ( ('test', cwd, 'test'), From 1a2dc6f205003cd2307b33927b8628a3d97b7ce6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 25 Nov 2017 00:18:10 +0100 Subject: [PATCH 2/3] Fixed tests for python 3 --- flask/cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask/cli.py b/flask/cli.py index b9d56b05..0a556ddc 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -36,7 +36,9 @@ except ImportError: def _called_with_wrong_args(factory, exc_info): exc_type, exc_value, tb = exc_info return exc_type is TypeError and \ - str(exc_value).startswith('%s() takes' % factory.__name__) + str(exc_value).startswith(( + '%s() takes' % factory.__name__, + '%s() missing' % factory.__name__)) class NoAppException(click.UsageError): From 2cba0d77eef983b605bf2f5c85a9d02fe212eda0 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 28 Jan 2018 11:20:42 -0800 Subject: [PATCH 3/3] use traceback to detect call more reliably --- flask/cli.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/flask/cli.py b/flask/cli.py index 0a556ddc..383fe459 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -33,14 +33,6 @@ except ImportError: dotenv = None -def _called_with_wrong_args(factory, exc_info): - exc_type, exc_value, tb = exc_info - return exc_type is TypeError and \ - str(exc_value).startswith(( - '%s() takes' % factory.__name__, - '%s() missing' % factory.__name__)) - - class NoAppException(click.UsageError): """Raised if an application cannot be found or loaded.""" @@ -83,7 +75,7 @@ def find_best_app(script_info, module): if isinstance(app, Flask): return app except TypeError: - if not _called_with_wrong_args(app_factory, sys.exc_info()): + if not _called_with_wrong_args(app_factory): raise raise NoAppException( 'Detected factory "{factory}" in module "{module}", but ' @@ -121,6 +113,30 @@ def call_factory(script_info, app_factory, arguments=()): return app_factory() +def _called_with_wrong_args(factory): + """Check whether calling a function raised a ``TypeError`` because + the call failed or because something in the factory raised the + error. + + :param factory: the factory function that was called + :return: true if the call failed + """ + tb = sys.exc_info()[2] + + try: + while tb is not None: + if tb.tb_frame.f_code is factory.__code__: + # in the factory, it was called successfully + return False + + tb = tb.tb_next + + # didn't reach the factory + return True + finally: + del tb + + 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 a function, it checks for specified arguments and whether it takes a @@ -158,8 +174,9 @@ def find_app_by_string(script_info, module, app_name): try: app = call_factory(script_info, attr, args) except TypeError as e: - if not _called_with_wrong_args(attr, sys.exc_info()): + if not _called_with_wrong_args(attr): raise + raise NoAppException( '{e}\nThe factory "{app_name}" in module "{module}" could not ' 'be called with the specified arguments.'.format(