Files
zeekay 1e7db0467a docs: name the endpoint instead of calling it a door
"door" stood for several different things across this fork — the ingest
endpoint cloud serves at POST /v1/event, the replay endpoint that produces the
session-replay topic, a code path that is not an address at all, the importer
that pulls a heavy package onto the startup path — so a reader had to work out
which one each comment meant.

Every metaphor site now says what it is. Measured over the worktree:

  before  93 occurrences of (?i)\bdoors?\b  (89 of \bdoor\b)
  after    3

90 uses replaced, by sense:

  endpoint/endpoints  49  an HTTP address that accepts requests — cloud's
                          /v1/event and /v1/replay, this deployment's /v1/flags,
                          the capture routes at /v1/e, /v1/s and /v1/ai
  path/paths          13  a code path that is not an address: the two ways a
                          report reaches PENDING_INPUT, the three ways a team
                          gets created, a second route around an access check
  ingest               6  where the referent is the ingest tier itself
  importer             4  the module that pulls a package onto the Django
                          startup path
  entry point, starting point, registration point, nav entry, landing page,
  "on entry" and "another way" carry the remaining one-off senses

Three sites keep the word, deliberately:

  - insights/api/test/test_flags.py — test_the_door_is_not_under_api is a test
    function name, so it is left alone.
  - insights/temporal/README.md — a blockquote of upstream prose. Editing a
    quotation misrepresents its source.
  - products/warehouse_sources/.../pretix/canonical_descriptions.py — a Pretix
    check-in list admits attendees at a literal door. Not a metaphor.

Verification:

  python3 -m py_compile   the 30 changed .py files, exit 0
  yaml.safe_load          .hanzo/workflows/deploy.yml parses
  every changed .rs/.ts/.tsx line is a //, ///, //! or /** comment, bar three
  balanced string literals — a jest description, a setFlagsUnavailable reason,
  and a Rust assert message

The pre-commit formatters did not run: bin/insightscli needs a project venv this
checkout has none of, so it exits 127 on `python: not found` with nothing staged
at all. Neither ruff nor prettier nor rustfmt rewraps comment text, and ruff
ignores E501, so no changed line is one they would have altered.
2026-08-23 15:28:20 -07:00

4.5 KiB

name, description
name description
django-startup-time 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 (insights/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.

Django startup time

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 insights/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 insights/test/setup_import_baseline.txt costing ≥100ms at setup fails the build — defer it; baseline only what every process genuinely needs at setup.

Defaults when adding backend code

  • New app/product: register models from models/__init__.py, wire receivers from AppConfig.ready(), keep ready() light.
  • New receiver: wire it at the owning 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.
  • New heavy dependency (vendor SDK, Temporal/AI/Datastore, pandas/pyarrow/scipy): import it function-locally on the path that uses it with # noqa: PLC0415, never at module scope.
  • Schema types on a setup-path module: enums from insights.schema_enums (cheap); pydantic models from insights.schema only inside the method that uses them. No module-level from insights.tasks... on setup paths — CeleryQueue lives in insights.celery_queues.
  • New viewset/route: it no longer loads at setup — don't rely on import side effects; routes go in rest_router.py, not the __init__.py shim.
  • Any deferral relocates cost — ask which process pays now, on what path, and whether that path is latency-sensitive (background workers paying lazily: fine; web workers paying on first requests: usually not).

Traps to check before you commit

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.
  • Silent receiver loss — lost receivers don't error; reproduce in a process that does NOT build the router (manage.py shell, celery, migrate).
  • Models vanishing from the registry — a model reachable only via a viewset import breaks makemigrations/admin/mypy; import it from models/__init__.py, then dmypy stop.
  • Lazy-router merge conflicts — keep the __init__.py shim, port master's aggregator deltas into rest_router.py.
  • Aggregator package __init__s — shim with PEP 562 __getattr__, via importlib.import_module and an __all__ whitelist (catch-alls recurse or deadlock on sibling imports).
  • Serializer field kwargs run at import — choices=... inputs can't be function-locally deferred; move the constant to an import-light module.
  • Vanished re-exports — regenerating/shimming a module drops names other code imported from it incidentally (e.g. schema.BaseModel); grep for consumers of the old namespace, including relative-import spellings.
  • Patch targets break on call-time imports — @patch("mod.helper") stops intercepting once mod imports helper at call time; patch the defining module instead.
  • Snapshot regen on a bad merge — regenerate .ambr against the merged branch, then confirm it isn't masking a regression.
  • Measuring the wrapper, not the work — time inside the process; importtime + tuna, not pyinstrument; grimp for cycles and importer enumeration.
  • Phantom importtime costs from GC — re-capture with gc.disable(); if the cost vanishes, the finding is GC, not the module. Keep the entrypoints' GC window closing in a finally.