In Django, what is AppConfig.ready() for, and what should you avoid doing inside it?
answer
- third phase of app loading
- models are importable here
- every management command runs it
- queries at startup are the trap
- idempotent, not guaranteed once
basics
~20 sAppConfig.ready() runs after every installed app's models are loaded, so it suits startup wiring such as importing signal receivers. Avoid database queries there: ready() runs on every management command and can run more than once in tests.
solid answer
~40 s`ready()` is the last phase of app loading: Django has imported every app and every `models` module, so you may import models or call `self.get_model()`. Its standard job is wiring — importing a `signals` module so receivers connect, registering system checks. The docs warn against touching the database there: `ready()` runs during startup of **every** management command, including `migrate` before tables exist and `test`, which would still query the configured production database. Django raises `RuntimeWarning: Accessing the database during app initialization is discouraged` when it sees such a query. Values cached from the database at startup also go stale. Finally, `ready()` normally runs once, but tests that change installed apps can call it again, so the code should be idempotent.
code
python · 10 lines# scheduling/apps.py
from django.apps import AppConfig
class SchedulingConfig(AppConfig):
name = "scheduling"
def ready(self):
# Wiring only: importing the module connects its receivers.
from . import signals # noqa: F401go deeper
Know that ready() is a method on AppConfig that runs at startup after models load, and that importing signal receivers there is its usual job.
Explain the three loading phases, why models can be imported in ready() but not at apps.py module level, and why queries there are discouraged.
Diagnose startup failures and slow commands caused by ready() queries, recognise the RuntimeWarning, and make ready() idempotent for tests.
Set a policy that app startup does wiring only, pushing data loading to lazy or cached paths so every process and command starts fast and safely.
## Where ready() sits in startup `django.setup()` populates the app registry in three phases, each walking `INSTALLED_APPS` in order: 1. **Import apps** — create an `AppConfig` for each entry. Importing models here is not allowed. 2. **Import models** — import each app's `models` submodule. After this, `apps.get_model()` works. 3. **Run `ready()`** — call each app config's `ready()` method. So `ready()` is the **first moment every model exists**. That makes it the hook for startup wiring that needs models but must not run at import time. ## What belongs in ready() - **Importing signal receivers**, for example `from . import signals`, so `@receiver` decorators connect. The docs also allow connecting with a string sender such as `sender="scheduling.Appointment"`. - **Registering system checks** with `django.core.checks.register`, as `django.contrib.admin` does. - **Light configuration** that reads settings or model metadata, never data. ## What to avoid: the clinic-booking example Imagine a `scheduling` app whose config caches clinic opening hours at startup: ```python from django.apps import AppConfig class SchedulingConfig(AppConfig): name = "scheduling" def ready(self): from .models import Clinic # Do not do this: a query at app initialization self.opening_hours = {c.pk: c.hours for c in Clinic.objects.all()} ``` This breaks in several ways, all named in Django's documentation: | Problem | Why it happens | |---|---| | `migrate` crashes on a fresh database | `ready()` runs before `migrate` has created the `scheduling_clinic` table | | `manage.py test` queries production | the test database is created later; startup still uses the configured `DATABASES` | | every command gets slower | `ready()` runs for `shell`, `check`, `makemigrations` and all others | | data goes stale | the dict is built once per process and never refreshed | | a `RuntimeWarning` appears | Django warns that accessing the database during app initialization is discouraged | The fix is to load the data **lazily**, inside the request or task that needs it, or cache it with the cache framework and an expiry. ## ready() is not guaranteed to run once Django's docs say that in the usual process `ready()` is called once, but "in some corner cases, particularly in tests which are fiddling with installed applications, `ready` might be called more than once". Code in it should therefore be **idempotent**: connecting a receiver twice without a `dispatch_uid` could fire it twice, and appending to a global list would duplicate entries. Either write idempotent code or guard it with a flag on the config. ## Other pitfalls - **Doing the work in `apps.py` at module level** instead of inside `ready()` runs it in phase 1, where importing models raises `AppRegistryNotReady`. - **Forgetting the config is used**: if `INSTALLED_APPS` lists a package and `apps.py` defines two `AppConfig` subclasses without `default = True`, Django uses a plain default config and your `ready()` never runs. - **Long-running work** such as starting threads or opening network connections belongs in the serving process's own startup path, not in every management command. ## Checklist for reviewing a ready() method - Does it only **import or register** things, such as receivers, checks or lookups? - Does it run **any query**, directly, through a manager, a model method such as `save()`, or raw SQL through `django.db.connection`? If so, move it. - Would running it **twice** change behaviour? If so, make it idempotent or guard it with a flag on the config. - Does it depend on **another app's** `ready()` having run? Apps run `ready()` in `INSTALLED_APPS` order, so such a dependency is fragile and should be removed rather than solved by reordering. - Is it **cheap**? It runs for every process and every management command. ## What an interviewer is listening for A middle candidate knows `ready()` runs after models load and uses it for receivers. A senior one explains why queries there are dangerous (every command, wrong database, stale data), recognises the warning, and writes it idempotently.
- Why can't the same wiring live at the top of apps.py?`apps.py` is imported in the first loading phase, before any `models` module is imported. Importing a model there, even indirectly through a signals module, raises `AppRegistryNotReady: Apps aren't loaded yet.` Inside `ready()` all models are loaded, so the same import succeeds.
- How do you find which ready() triggered the database warning?Django emits a `RuntimeWarning` for queries before the registry is ready. Make Python treat warnings as errors, for example `python -Werror manage.py shell`, and the traceback points at the query and the `ready()` or module-level code that ran it.
saying these in an interview costs you the question
- Uses ready() to query the database and cache reference data
- Believes ready() runs only when the web server starts
- Assumes ready() is guaranteed to run exactly once
- Imports models at the top of apps.py instead of inside ready()
- Thinks manage.py test isolates ready() queries from the real database