From 7c40aa9e50d97b7c412745a53c8cdc2597c46680 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Thu, 15 Jun 2017 11:27:50 -0700 Subject: [PATCH 1/3] Import app from wsgi.py or app.py if FLASK_APP is not defined Fixes #2376 --- flask/cli.py | 29 ++++++++++++++++++++--------- tests/test_apps/helloworld/hello.py | 6 ++++++ tests/test_apps/helloworld/wsgi.py | 1 + tests/test_cli.py | 22 +++++++++++++++++++++- 4 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 tests/test_apps/helloworld/hello.py create mode 100644 tests/test_apps/helloworld/wsgi.py diff --git a/flask/cli.py b/flask/cli.py index 4b5323c7..dbee18f0 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -172,11 +172,12 @@ def prepare_exec_for_file(filename): if not os.path.isfile(os.path.join(dirpath, '__init__.py')): break - sys.path.insert(0, dirpath) + if sys.path[0] != dirpath: + sys.path.insert(0, dirpath) return '.'.join(module[::-1]) -def locate_app(script_info, app_id): +def locate_app(script_info, app_id, raise_if_not_found=True): """Attempts to locate the application.""" __traceback_hide__ = True if ':' in app_id: @@ -193,12 +194,14 @@ def locate_app(script_info, app_id): if sys.exc_info()[-1].tb_next: stack_trace = traceback.format_exc() raise NoAppException('There was an error trying to import' - ' the app (%s):\n%s' % (module, stack_trace)) - else: + ' the app (%s):\n%s' % (module, stack_trace)) + elif raise_if_not_found: raise NoAppException('The file/path provided (%s) does not appear' ' to exist. Please verify the path is ' 'correct. If app is not on PYTHONPATH, ' 'ensure the extension is .py' % module) + else: + return mod = sys.modules[module] if app_obj is None: @@ -326,15 +329,23 @@ class ScriptInfo(object): if self.create_app is not None: rv = call_factory(self.create_app, self) else: - if not self.app_import_path: + if self.app_import_path: + rv = locate_app(self, self.app_import_path) + else: + for module in ['wsgi.py', 'app.py']: + import_path = prepare_exec_for_file(module) + rv = locate_app(self, import_path, + raise_if_not_found=False) + if rv: + break + if not rv: raise NoAppException( 'Could not locate Flask application. You did not provide ' - 'the FLASK_APP environment variable.\n\nFor more ' - 'information see ' + 'the FLASK_APP environment variable, and a wsgi.py or ' + 'app.py module was not found in the current directory.\n\n' + 'For more information see ' 'http://flask.pocoo.org/docs/latest/quickstart/') - rv = locate_app(self, self.app_import_path) - debug = get_debug_flag() if debug is not None: diff --git a/tests/test_apps/helloworld/hello.py b/tests/test_apps/helloworld/hello.py new file mode 100644 index 00000000..bbf7e467 --- /dev/null +++ b/tests/test_apps/helloworld/hello.py @@ -0,0 +1,6 @@ +from flask import Flask +app = Flask(__name__) + +@app.route("/") +def hello(): + return "Hello World!" diff --git a/tests/test_apps/helloworld/wsgi.py b/tests/test_apps/helloworld/wsgi.py new file mode 100644 index 00000000..fab4048b --- /dev/null +++ b/tests/test_apps/helloworld/wsgi.py @@ -0,0 +1 @@ +from hello import app diff --git a/tests/test_cli.py b/tests/test_cli.py index 899fb1f0..65993920 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -181,6 +181,8 @@ def test_locate_app(test_apps): script_info, "cliapp.factory:create_app ()") pytest.raises( NoAppException, locate_app, script_info, "cliapp.importerrorapp") + assert locate_app(script_info, "notanpp.py", + raise_if_not_found=False) is None def test_find_default_import_path(test_apps, monkeypatch, tmpdir): @@ -214,7 +216,7 @@ def test_get_version(test_apps, capsys): assert py_ver in out -def test_scriptinfo(test_apps): +def test_scriptinfo(test_apps, monkeypatch): """Test of ScriptInfo.""" obj = ScriptInfo(app_import_path="cliapp.app:testapp") assert obj.load_app().name == "testapp" @@ -228,6 +230,24 @@ def test_scriptinfo(test_apps): assert app.name == "createapp" assert obj.load_app() == app + obj = ScriptInfo() + pytest.raises( + NoAppException, obj.load_app) + + # import app from wsgi.py in current directory + monkeypatch.chdir(os.path.abspath( + os.path.join(os.path.dirname(__file__), 'test_apps', 'helloworld'))) + obj = ScriptInfo() + app = obj.load_app() + assert app.name == 'hello' + + # import app from app.py in current directory + monkeypatch.chdir(os.path.abspath( + os.path.join(os.path.dirname(__file__), 'test_apps', 'cliapp'))) + obj = ScriptInfo() + app = obj.load_app() + assert app.name == 'testapp' + def test_with_appcontext(runner): """Test of with_appcontext.""" From 0b80acb25cb4ee3f2acad6ae8064d834e0583408 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Thu, 15 Jun 2017 12:15:38 -0700 Subject: [PATCH 2/3] document wsgi.py and app.py default modules --- docs/cli.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 1c06f343..8fbfe962 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -26,9 +26,13 @@ built-in commands that are always there. Flask extensions can also register more commands there if they desire so. For the :command:`flask` script to work, an application needs to be -discovered. This is achieved by exporting the ``FLASK_APP`` environment -variable. It can be either set to an import path or to a filename of a -Python module that contains a Flask application. +discovered. Flask looks for a module named wsgi.py or app.py by default, and +if it finds one it assumes the application is defined in it. + +You can instruct Flask to look for the application in a different module by +exporting the ``FLASK_APP`` environment variable. It can be either set to an +import path or to a filename of a Python module that contains a Flask +application. In that imported file the name of the app needs to be called ``app`` or optionally be specified after a colon. For instance From 448368e226c8edd1077cf8e5a12ddaa9222b8ec9 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 16 Jun 2017 06:59:37 -0700 Subject: [PATCH 3/3] style cleanup [ci skip] --- docs/cli.rst | 8 ++++---- flask/cli.py | 30 +++++++++++++++++++++--------- tests/test_cli.py | 18 ++++++++++-------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 8fbfe962..52885f43 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -25,12 +25,12 @@ your Flask application's :attr:`Flask.cli` instance as well as some built-in commands that are always there. Flask extensions can also register more commands there if they desire so. -For the :command:`flask` script to work, an application needs to be -discovered. Flask looks for a module named wsgi.py or app.py by default, and -if it finds one it assumes the application is defined in it. +For the :command:`flask` script to work, an application needs to be discovered. +Flask looks for a module named :file:`wsgi.py` or :file:`app.py` by default, +and if it finds one it assumes the application is defined in it. You can instruct Flask to look for the application in a different module by -exporting the ``FLASK_APP`` environment variable. It can be either set to an +exporting the ``FLASK_APP`` environment variable. It can be either set to an import path or to a filename of a Python module that contains a Flask application. diff --git a/flask/cli.py b/flask/cli.py index dbee18f0..536f53ee 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -174,12 +174,14 @@ def prepare_exec_for_file(filename): if sys.path[0] != dirpath: sys.path.insert(0, dirpath) + return '.'.join(module[::-1]) def locate_app(script_info, app_id, raise_if_not_found=True): """Attempts to locate the application.""" __traceback_hide__ = True + if ':' in app_id: module, app_obj = app_id.split(':', 1) else: @@ -193,17 +195,23 @@ def locate_app(script_info, app_id, raise_if_not_found=True): # Determine this by checking whether the trace has a depth > 1. if sys.exc_info()[-1].tb_next: stack_trace = traceback.format_exc() - raise NoAppException('There was an error trying to import' - ' the app (%s):\n%s' % (module, stack_trace)) + raise NoAppException( + 'There was an error trying to import the app ({module}):\n' + '{stack_trace}'.format( + module=module, stack_trace=stack_trace + ) + ) elif raise_if_not_found: - raise NoAppException('The file/path provided (%s) does not appear' - ' to exist. Please verify the path is ' - 'correct. If app is not on PYTHONPATH, ' - 'ensure the extension is .py' % module) + raise NoAppException( + 'The file/path provided (%s) does not appear to exist. Please' + ' verify the path is correct. If app is not on PYTHONPATH,' + ' ensure the extension is .py.'.format(module=module) + ) else: return mod = sys.modules[module] + if app_obj is None: return find_best_app(script_info, mod) else: @@ -334,17 +342,21 @@ class ScriptInfo(object): else: for module in ['wsgi.py', 'app.py']: import_path = prepare_exec_for_file(module) - rv = locate_app(self, import_path, - raise_if_not_found=False) + rv = locate_app( + self, import_path, raise_if_not_found=False + ) + if rv: break + if not rv: raise NoAppException( 'Could not locate Flask application. You did not provide ' 'the FLASK_APP environment variable, and a wsgi.py or ' 'app.py module was not found in the current directory.\n\n' 'For more information see ' - 'http://flask.pocoo.org/docs/latest/quickstart/') + 'http://flask.pocoo.org/docs/latest/quickstart/' + ) debug = get_debug_flag() diff --git a/tests/test_cli.py b/tests/test_cli.py index 65993920..0e0a56ad 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -181,8 +181,9 @@ def test_locate_app(test_apps): script_info, "cliapp.factory:create_app ()") pytest.raises( NoAppException, locate_app, script_info, "cliapp.importerrorapp") - assert locate_app(script_info, "notanpp.py", - raise_if_not_found=False) is None + assert locate_app( + script_info, "notanpp.py", raise_if_not_found=False + ) is None def test_find_default_import_path(test_apps, monkeypatch, tmpdir): @@ -231,19 +232,20 @@ def test_scriptinfo(test_apps, monkeypatch): assert obj.load_app() == app obj = ScriptInfo() - pytest.raises( - NoAppException, obj.load_app) + pytest.raises(NoAppException, obj.load_app) # import app from wsgi.py in current directory - monkeypatch.chdir(os.path.abspath( - os.path.join(os.path.dirname(__file__), 'test_apps', 'helloworld'))) + monkeypatch.chdir(os.path.abspath(os.path.join( + os.path.dirname(__file__), 'test_apps', 'helloworld' + ))) obj = ScriptInfo() app = obj.load_app() assert app.name == 'hello' # import app from app.py in current directory - monkeypatch.chdir(os.path.abspath( - os.path.join(os.path.dirname(__file__), 'test_apps', 'cliapp'))) + monkeypatch.chdir(os.path.abspath(os.path.join( + os.path.dirname(__file__), 'test_apps', 'cliapp' + ))) obj = ScriptInfo() app = obj.load_app() assert app.name == 'testapp'