ModuleNotFoundError when running imported Flask app
It turns out it's a bug in werkzeug.The code works as expected if werkzeug's reloader is disabled.
How to reproduce the behaviour
Directory structure:
foo | __init__.py | __main__.py
Content of __init__.py
:
from flask import Flaskapp = Flask(__name__)app.config["DEBUG"] = True
Content of __main__.py
:
from foo import appapp.run()
If we run it:
$python3 -m foo * Serving Flask app "foo" (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with statTraceback (most recent call last): File "/home/user/projects/ftest/foo/__main__.py", line 1, in <module> from foo import appModuleNotFoundError: No module named 'foo'
If we change __main__.py
:
from foo import appapp.run(use_reloader=False)
Everything works just fine.
What's going on
The problem is in werkzeug._reloader.ReloaderLoop.restart_with_reloader
. It calls a subprocess with the arguments provided by werkzeug._reloader._get_args_for_reloading
but this function does not behave as expected when executing a package via the -m
switch.
def _get_args_for_reloading(): """Returns the executable. This contains a workaround for windows if the executable is incorrectly reported to not have the .exe extension which can cause bugs on reloading. """ rv = [sys.executable] py_script = sys.argv[0] if os.name == 'nt' and not os.path.exists(py_script) and \ os.path.exists(py_script + '.exe'): py_script += '.exe' if os.path.splitext(rv[0])[1] == '.exe' and os.path.splitext(py_script)[1] == '.exe': rv.pop(0) rv.append(py_script) rv.extend(sys.argv[1:]) return rv
In our case it returns ['/usr/local/bin/python3.7', '/home/user/projects/ftest/foo/__main__.py']
. This is because sys.argv[0]
is set to the full path of the module file but it should return ['/usr/local/bin/python3.7', '-m', 'foo']` (At least from my understanding it should and it works this way).
I have no good idea on how to fix this behaviour, or if it is something that need to be fixed. It just seems weird to me that I'm the only one that has encountered this problem, since it doesn't seem too much of a corner case to me.
Adding the following line before app.run() works around the werkzeug reloader bug:
os.environ['PYTHONPATH'] = os.getcwd()
Thanks to @bootc for the tip! https://github.com/pallets/flask/issues/1246