Django async with AppConfig.ready sync/async
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Django's AppConfig.ready() method runs during application startup to perform initialization tasks like connecting signals, running checks, or populating caches. This method is synchronous — it runs before the ASGI/WSGI server starts and before any event loop is available. You cannot use await inside ready() because there is no running event loop at that point. To run async code during startup, you must use asyncio.run(), sync_to_async, or defer the async work to the first request. Django 4.1+ provides better async support, but ready() itself remains synchronous.
The Problem
Django calls ready() synchronously during django.setup(). Defining it as async def either causes it to return a coroutine object (never awaited) or raises SynchronousOnlyOperation.
Solution 1: asyncio.run() for One-Shot Async Work
asyncio.run() creates a new event loop, runs the coroutine, and closes the loop. This works because no event loop is running during ready().
Solution 2: sync_to_async with Django's Async Utilities
Solution 3: Defer Async Work to First Request
If the async initialization can wait, use a middleware or signal to run it on the first request.
Solution 4: Signal Connection in ready() (Common Use Case)
The most common use of ready() is connecting signals, which is synchronous and works without any async workarounds.
Solution 5: Background Task for Async Initialization
Common Pitfalls
- Defining
ready()asasync def: Django callsready()synchronously. Declaring itasync defreturns a coroutine object that is never awaited. The initialization code silently does not execute, and Python may emit a "coroutine was never awaited" warning. - Calling
asyncio.run()when an event loop is already running: If Django is running under an ASGI server (like Uvicorn/Daphne), an event loop may already exist during startup in some configurations.asyncio.run()raisesRuntimeError: This event loop is already running. Useasync_to_sync()fromasgirefinstead, which handles this case. - Database operations in
ready()before migrations:ready()runs duringdjango.setup(), which happens before migrations in management commands likemigrate. Performing database queries inready()can fail if the table does not exist yet. Guard with try/except or checkconnection.introspection.table_names(). - Importing models at module level in apps.py: Importing models at the top of
apps.pycausesAppRegistryNotReadybecause the app registry is still being populated. Import models insideready()or inside signal handler functions. - Running
ready()multiple times: Django can callready()more than once (e.g., during test setup). Use a module-level flag (_initialized = False) to ensure one-time initialization logic runs only once.
Summary
AppConfig.ready()is synchronous — you cannotawaitinside it directly- Use
asyncio.run()for one-shot async initialization during startup - Use
async_to_sync()from asgiref when running under ASGI servers - Defer non-critical async work to a background thread or first-request middleware
- The most common
ready()use case (connecting signals) is synchronous and needs no async handling

