ModuleNotFoundError when running imported Flask app ModuleNotFoundError when running imported Flask app flask flask

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


Have you tried from foo import app in your main.py file?