Hey, I’ve been experimenting with moving to hosting our moderately large Django installation into ASGI hosting mode, running on uvicorn. On a few of our projects everything seemed to be fine, but I have one where we have an issue with our setup. It appears that starting up in asgi mode, it doesn’t like the direct ORM call in our accounts app setup, where we ensure that one of our feature flags exists.
I’m not sure what the recommended way to do this is, or if there’s something obvious we’re doing wrong. The traceback we get on startup is below:
(apologies for image, the Stackdriver logs aren’t making it easy to export as text)
The asgi.py file looks like:
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
os.environ.setdefault("REQUESTS_CA_BUNDLE", "/etc/ssl/certs/ca-certificates.crt")
os.environ.setdefault(
"NODE_PATH", "/usr/lib/nodejs:/usr/lib/node_modules:/usr/share/javascript"
)
application = get_asgi_application()
and the app.ready in question is:
from contextlib import suppress
from django.apps import AppConfig
from django.db import OperationalError
from django.db import ProgrammingError
class AccountsConfig(AppConfig):
name = "accounts"
def ready(self):
from common.models import FeatureFlag
with suppress(ProgrammingError, OperationalError):
FeatureFlag.objects.get_or_create(
name="SMS_SPECIFIC_TIME",
defaults={
"enabled": True,
"description": "Send SMS at the time (24H) that is declared in data", # noqa
"data": {"time": "20:00"},
},
)
import accounts.signals # noqa
import accounts.messages # noqa
Would greatly appreciate any advice on what we should be doing differently!
I feel like this likely counts as a bug, it’s quite common to use the ORM/DB in app.ready()
. Django should run the ready()
calls in sync mode. Probably worth a ticket.
Thanks for the reply! I’ve created the issue here https://code.djangoproject.com/ticket/31339
The documentation explicitly warns against performing queries during app.ready()
so making Django call ready()
in sync mode would encourage this pattern and all of its pitfalls.
I think it’s very unexpected that putting ORM calls here would work in WSGI mode and then stop working in ASGI mode. Given that I expect most people will continue to run WSGI mode for a long time, I can see this being a source of potential future headaches for a lot of people. Perhaps if calling the ORM in app.ready is not intended it should be entirely banned?
I think it’s very unexpected that putting ORM calls here would work in WSGI mode and then stop working in ASGI mode.
It’s only working because you are suppressing the exceptions (ProgrammingError, OperationalError
) that are raised in WSGI mode.
You had to suppress
these exceptions because if you make some schema changes to your FeatureFlag
model or start from a new database it will even prevent you from calling any command, runserver
, migrate
, and makemigrations
included.
I don’t see why you shouldn’t have to explicitly suppress SynchronousOnlyOperation
as well if you want to swim against the current.
Perhaps if calling the ORM in app.ready is not intended it should be entirely banned?
Likely yes and it has already been discussed in on the mailing list.
1 Like
You’re right Simon. I was getting a bit mixed up between places queries can occur - we allow them in checks because some compare to the database.
@lcannon Did you ever found a workaround for this? Running into the same problem.
@stvdrsch
This is an unsolvable problem. Your ASGI server is running the event loop, therefore your ready()
method should be awaited. But the Django framework does “just” call the ready()
method. This means you may put as much asynchronous code as you want in the ready()
method, but it will never be executed, since ready()
is never awaited.
The cleanest solution to my knowledge: Create additional admin commands, that set your project into a certain base state you want/need. Call these admin commands before you start your WSGI/ASGI server.
How you do that is entirely up to you. You may write some shell scripts, or again use another Python script by utilizing the subprocess module for example.