I'm using 5ff4819 on Mac, Python 3.6.4. I have a simple example structured like this:
.
`-- app
|-- __init__.py (empty)
|-- extensions.py (defines a single function, foo())
`-- server.py
server.py looks like this:
from sanic import Sanic
# Extensions
from app.extensions import foo
def create_app(**kwargs):
app = Sanic()
return app
if __name__ == "__main__":
sanic_app: Sanic = create_app()
sanic_app.run(debug=True)
But it fails when run in debug mode; can't load module app.extensions after it's running:
python -m app.server
[2018-06-18 10:13:02 -0400] [65764] [DEBUG]
[logo]
[2018-06-18 10:14:57 -0400] [65851] [INFO] Goin' Fast @ http://0.0.0.0:8000
Traceback (most recent call last):
File "/Users/garyo/dss/consulting/shorelight/Director/sanic-test/app/server.py", line 14, in <module>
from app.extensions import foo
ModuleNotFoundError: No module named 'app'
This seems to be related to auto_reload. In fact if I run it with debug=False, auto_reload=True I get the same results.
The simple reason is when you run python -m <module>, python helpfully rewrites sys.argv to contain the full path of the module rather than -m <module>. So the reloader can't get the original args properly, and ends up trying to run python <modulename>.py which loads it as __main__ so package imports don't work. Have I mentioned how much I hate the python import system?
Note that even if I fix (or work-around) that by appending the proper dir to sys.path in server.py, the reloader fails in a different way when it tries to reload on some change:
[2018-06-18 11:12:09 -0400] [66721] [ERROR] Unable to start server
Traceback (most recent call last):
File "uvloop/loop.pyx", line 1083, in uvloop.loop.Loop._create_server
File "uvloop/handles/streamserver.pyx", line 51, in uvloop.loop.UVStreamServer.listen
File "uvloop/handles/streamserver.pyx", line 89, in uvloop.loop.UVStreamServer._fatal_error
OSError: [Errno 48] Address already in use
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/garyo/.local/share/virtualenvs/shorelight-director/lib/python3.6/site-packages/sanic/server.py", line 614, in serve
http_server = loop.run_until_complete(server_coroutine)
File "uvloop/loop.pyx", line 1422, in uvloop.loop.Loop.run_until_complete
File "uvloop/loop.pyx", line 1599, in create_server
File "uvloop/loop.pyx", line 1087, in uvloop.loop.Loop._create_server
OSError: [Errno 48] error while attempting to bind on address ('0.0.0.0', 8000): address already in use
[2018-06-18 11:12:09 -0400] [66721] [INFO] Server Stopped
Perhaps the reloader isn't ready for use on Mac?
Same problem on Ubuntu 18.04 with python 3.7.0.
I'm currently working around this by starting the server from an external run-server.py above the app dir:
import sys
from app.server import main
sys.exit(main())
and that works (with my prev fix in #1249).
I've had a similar issue with the auto_reloader not supporting relative imports, so using the following in my entry point failed:
from . import __description__ as description, __title__ as title, log
Setting auto_reload=False in the app.Run() fixed my issue.
You may be seeing a similar issue with auto_reload when running python3 -m <package>
However, after the package is built and installed, you will not have these issues.
This is a little unfortunate as auto_reload would ideally be used in development.
EDIT:
To clarify: debug=True automatically enables auto_reload=True
python import system is simply trash. I feel your pain.
There's been a discussion regarding the auto_reload feature in the community forums. It does not only seeks to be a solution for Windows developers but also to avoid these kind of errors, while taking some of the logic out of the main Sanic repository.
Just run into this problem myself.
The simple reason is when you run
python -m <module>, python helpfully rewrites sys.argv to contain the full path of the module rather than-m <module>. So the reloader can't get the original args properly, and ends up trying to runpython <modulename>.pywhich loads it as__main__so package imports don't work. Have I mentioned how much I hate the python import system?
This is true, but surely the reloader _can_ get the original args? You'd just have to use _get_args_for_reloading to amend the contents of sys.argv using __spec__:
def _get_args_for_reloading():
"""Returns the executable."""
rv = [sys.executable]
# at this point sys.argv is e.g. ['/tmp/my_module/__main__.py']
spec = getattr(sys.modules['__main__'], '__spec__', None)
if spec and spec[:2] != ['-m', spec.name]:
# now sys.argv is e.g. ['-m my_module.__main__']
sys.argv = ['-m', spec.name] + sys.argv[1:]
rv.extend(sys.argv)
return rv
Why need to have this auto_reload when we can use entr?
My folder structure:
---app
-----| app.py
---run.sh
run.sh
#!/usr/bin/env bash
find app/ -name \*.py -o -name \*.html | entr -r python -m app.app
You just added a UNIX subsystem and entr (doesn't seem to be available on my package manager) to the requirements, to implement a reloader that is worse than Sanic's. Sanic detects changes and reloads even if modules outside current directory are updated. The dozens of other autoreloaders (such as gunicorn) weren't unified enough, so let's add one more to the pile.
There are clear advantages of having this internal to Sanic. First of all, no extra deps beyond what pip install gets for Sanic, so it is trivial to use it. Secondly, livereload.js (reloads the page in browser when files change on server) could easily be supported within a web framework (this is already used in aiohttp-devtools, which does autoreloading and also livereload).
Granted, there could be room for a shared python-asyncio autoreloader. I wonder how difficult it would be to add Sanic support into aiohttp-devtools or livereload Python packages.
Most helpful comment
The simple reason is when you run
python -m <module>, python helpfully rewrites sys.argv to contain the full path of the module rather than-m <module>. So the reloader can't get the original args properly, and ends up trying to runpython <modulename>.pywhich loads it as__main__so package imports don't work. Have I mentioned how much I hate the python import system?