Keep heavy imports off the django.setup() path that every process (web, celery, temporal, migrate, shell, CI) pays for. Use when touching AppConfig.ready(), wiring signal receivers, editing the lazy API router (posthog/api/rest_router.py or its __init__.py shim), deferring a heavy import, when the startup-import-budget guard fails, or when merging master into a long-lived branch that made the router lazy.
70
86%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Full background — the four mechanisms (lazy router, model registration, receiver wiring, boot GC window), the regression guards, how to add code without regressing them, how to measure, and the detailed trap write-ups: docs/internal/django-startup-time.md. That doc is the single source of detail; this skill is the trigger and the checklist.
The guards live in posthog/test/test_startup_import_budget.py. When one fails, defer the import — don't remove an entry to dodge it. Conversely, when you deliberately defer a significant heavy lib off setup, add it to FORBIDDEN_AT_SETUP (after confirming it's absent from a bare django.setup()). New imports nobody has named yet are caught by test_no_new_heavy_imports_at_setup: any package not in posthog/test/setup_import_baseline.txt costing ≥100ms at setup fails the build — defer it; baseline only what every process genuinely needs at setup.
models/__init__.py, wire receivers from AppConfig.ready(), keep ready() light.AppConfig.ready(), from a dedicated light module (activity_logging.py, signals.py) — never via the API/viewset module, even if it looks light today; those accumulate heavy imports and ready() silently inherits them.# noqa: PLC0415, never at module scope.posthog.schema_enums (cheap); pydantic models from posthog.schema only inside the method that uses them. No module-level from posthog.tasks... on setup paths — CeleryQueue lives in posthog.celery_queues.rest_router.py, not the __init__.py shim.One line each — the doc has the full write-up and the fix recipe for every entry.
ready() re-drags a heavy dep onto startup — importing a receiver's module runs its module-level imports too; re-measure after wiring.manage.py shell, celery, migrate).makemigrations/admin/mypy; import it from models/__init__.py, then dmypy stop.__init__.py shim, port master's aggregator deltas into rest_router.py.__init__s — shim with PEP 562 __getattr__, via importlib.import_module and an __all__ whitelist (catch-alls recurse or deadlock on sibling imports).choices=... inputs can't be function-locally deferred; move the constant to an import-light module.schema.BaseModel); grep for consumers of the old namespace, including relative-import spellings.@patch("mod.helper") stops intercepting once mod imports helper at call time; patch the defining module instead..ambr against the merged branch, then confirm it isn't masking a regression.importtime + tuna, not pyinstrument; grimp for cycles and door enumeration.gc.disable(); if the cost vanishes, the finding is GC, not the module. Keep the entrypoints' GC window closing in a finally.d1dd198
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.