我一直在尝试从daphne切换到uvicorn,以便使用django 3和通道进行生产。我在加载通道的经典asgi文件时遇到了错误。由于同步调用django.setup或get_application,我无法使用它。我试图用sync_to_async调用调整这个文件,但没有成功。有人设法做到了吗?
原始asgi.py
代码
import os
import django
from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
django.setup()
application = get_default_application()
StackTrace
Traceback (most recent call last):
File "/usr/local/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
self.run()
File "/usr/local/lib/python3.8/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/uvicorn/subprocess.py", line 61, in subprocess_started
target(sockets=sockets)
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/uvicorn/main.py", line 407, in run
loop.run_until_complete(self.serve(sockets=sockets))
File "/usr/local/lib/python3.8/asyncio/base_events.py", line 612, in run_until_complete
return future.result()
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/uvicorn/main.py", line 414, in serve
config.load()
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/uvicorn/config.py", line 300, in load
self.loaded_app = import_from_string(self.app)
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/uvicorn/importer.py", line 20, in import_from_string
module = importlib.import_module(module_str)
File "/usr/local/lib/python3.8/importlib/__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
File "<frozen importlib._bootstrap>", line 991, in _find_and_load
File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 783, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "./config/asgi.py", line 16, in <module>
django.setup()
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/django/__init__.py", line 24, in setup
apps.populate(settings.INSTALLED_APPS)
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/django/apps/registry.py", line 122, in populate
app_config.ready()
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/django_prometheus/apps.py", line 23, in ready
ExportMigrations()
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/django_prometheus/migrations.py", line 52, in ExportMigrations
executor = MigrationExecutor(connections[alias])
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/django/db/migrations/executor.py", line 18, in __init__
self.loader = MigrationLoader(self.connection)
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/django/db/migrations/loader.py", line 49, in __init__
self.build_graph()
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/django/db/migrations/loader.py", line 212, in build_graph
self.applied_migrations = recorder.applied_migrations()
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/django/db/migrations/recorder.py", line 76, in applied_migrations
if self.has_table():
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/django/db/migrations/recorder.py", line 56, in has_table
return self.Migration._meta.db_table in self.connection.introspection.table_names(self.connection.cursor())
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/django/utils/asyncio.py", line 24, in inner
raise SynchronousOnlyOperation(message)
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
修改为asgi.py
代码
import os
import django
from asgiref.sync import sync_to_async
from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
sync_to_async(django.setup, thread_sensitive=True)
application = sync_to_async(get_default_application, thread_sensitive=True)
Stacktrace
Traceback (most recent call last):
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/uvicorn/lifespan/on.py", line 55, in main
await app(scope, self.receive, self.send)
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/asgiref/sync.py", line 296, in __call__
ret = await asyncio.wait_for(future, timeout=None)
File "/usr/local/lib/python3.8/asyncio/tasks.py", line 455, in wait_for
return await fut
File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "/var/www/.cache/pypoetry/virtualenvs/project-4ffvdAoS-py3.8/lib/python3.8/site-packages/asgiref/sync.py", line 334, in thread_handler
return func(*args, **kwargs)
TypeError: get_default_application() takes 0 positional arguments but 3 were given
ERROR: Application startup failed. Exiting.
感谢阅读我的文章伙计们
- 传递给uvicorn的可调用函数将按照此函数在事件循环中运行
- 因为1。
django.setup()
需要转换为协同例程 get_default_application()
不应该异步运行,它将自动神奇地返回路由器应用程序。这就是uvicorn应该指出的django.setup()
必须在主事件循环启动之前返回
给定这四点,您的代码应该如下:
import os
import django
from asgiref.sync import sync_to_async
from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
await sync_to_async(django.setup, thread_sensitive=True)()
application = get_default_application()
但是,在撰写本文时,您不能将wait用作顶级指令,如果删除它,则不能保证在模块解析之前完成设置(不会(。因此,如果在启动应用程序之前要运行同步代码,则不能使用uvicorn的命令行。
然而,您可以通过程序启动它:
# server.py
import uvicorn
import os
import django
from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
django.setup()
app = get_default_application()
if __name__ == "__main__":
uvicorn.run("server:app") # Pass additional command line options as kwargs
我对这个问题做了一个稍微不同的(在我看来更容易使用(方法,那就是在启动uvicorn服务器之前执行Django东西的同步初始化。
只需创建一个具有执行权限的python文件uvicorn-django
,内容如下:
#!/usr/bin/env python
# Patch uvicorn to work properly with django setup
import uvicorn
import importlib
# monkeypatch uvicorn run function to import app before launching server
orig_run = uvicorn.run
def run(*args, **kwargs):
# import application entrypoint before running uvicorn
importlib.import_module(args[0])
orig_run(*args, **kwargs)
uvicorn.run = run
if __name__ == "__main__":
# start uvicorn programmatically after initializing django application
# - django needs to be initialized in non async context (which is not the case if application is started from uvicorn directly)
# - uvicorn.main is the cli (click) implementation that would be executed when starting uvicorn from shell, therefore parsing all the passed arguments as if uvicorn was started directly
uvicorn.main()
通过这种方式,您可以像启动uvicorn
CLI本身一样使用此python脚本,包括uvicorn
提供的所有参数:
$ uvicorn-django --host 0.0.0.0 --port 8080 YOUR_MODULE:YOUR_APPLICATION
另一个解决方案是将gunicorn与uvicorn工作程序一起使用,后者也没有异步问题:
$ gunicorn -k uvicorn.workers.UvicornWorker YOUR_MODULE:YOUR_APPLICATION