chore(api): lift ROUTING_TARGETS to a leaf module (breaks import cycle)#83
Merged
Conversation
New module ainfera_api/routing_targets.py owns the SP-1 routing-string
constants: INFERENCE_MODEL, ROUTING_ALIASES, ROUTING_TARGETS,
CANONICAL_ROUTER, MITHRIL/AUTO/AUTO_SLASH aliases, MITHRIL_MODEL +
AUTO_MODEL back-compat names, and a pure is_routed() classifier.
Motivation: capture_invariant.py pulled ROUTING_TARGETS from
routers/inference, which forced a 50-import chain and created a real
circular-import bug. Any module wanting to import capture_invariant
at top-level closed the cycle:
services/routing_outcomes -> services/capture_invariant
-> routers/inference (for ROUTING_TARGETS)
-> services/routing_brain
-> services/routing_outcomes
AIN-285 had to work around this with two function-local imports
(noqa: PLC0415). With ROUTING_TARGETS lifted to a leaf module, any
future module — including the AIN-285 wire-ins when feat/ain-285
rebases — can use plain top-level imports.
## What changed
- NEW: ainfera_api/routing_targets.py (~75 lines, zero ainfera_api
deps). Owns the constants + is_routed(). No FastAPI / no logging /
no I/O.
- routers/inference.py imports from the leaf module and re-exports
the constants + _is_routed (back-compat alias for is_routed). All
existing callers — anthropic_compat.py, openai_compat.py, test
suites — keep working unchanged.
- services/capture_invariant.py imports ROUTING_TARGETS from the leaf
module instead of routers/inference. Cycle broken.
## What did NOT change
- Public string values (every SP-1 constant has the same value).
- _is_routed semantics — the router-level alias and the leaf-level
is_routed return identical results for every input (tested
explicitly against canonical + 3 aliases + vendor slugs + empty).
- /v1 OpenAPI surface (the route definitions in routers/inference
are unmoved).
## What enables (future PRs)
When feat/ain-285-capture-metric (PR #82) rebases onto this:
- routers/inference.py — the lazy get_counter import becomes top-level.
- services/routing_outcomes.py — same retirement.
- Both noqa: PLC0415 comments come out.
## Tests
- NEW: tests/unit/test_routing_targets_module.py (4 tests) locks:
* routing_targets.py has zero ainfera_api imports (leaf invariant)
* back-compat re-exports work for every constant + _is_routed
* capture_invariant imports from leaf, not from router
* leaf is_routed() agrees with router _is_routed for every SP-1
target + a vendor-slug sample (so the two impls can't drift)
- 550/550 unit + smoke tests green (was 546; +4 from the new file).
- ruff check + ruff format --check + mypy --strict clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pure hygiene; independent of any other in-flight PR. Per the GATE 4 GO brief: "ROUTING_TARGETS → constants module cleanup PR (retire the two noqa: PLC0415 lazy imports from AIN-285). Pure hygiene, independent, ship it."
Motivation
`services/capture_invariant.py` pulled `ROUTING_TARGETS` from `routers/inference`, which created a real circular-import bug. Any module wanting to import `capture_invariant` at top-level closed this cycle:
```
services/routing_outcomes -> services/capture_invariant
-> routers/inference (for ROUTING_TARGETS)
-> services/routing_brain
-> services/routing_outcomes ← back here
```
AIN-285 worked around it with two function-local imports (`noqa: PLC0415`). Lifting the constants to a leaf module breaks the cycle for good.
What this PR does
What this PR does NOT change
What this enables (next PR)
When AIN-285 (PR #82) rebases onto this, the two `noqa: PLC0415` lazy imports become plain top-level imports of `get_counter`. One-line cleanup per call site; PR #82's diff shrinks.
Test plan
🤖 Generated with Claude Code
Note
Low Risk
Pure refactor with re-exports and tests; no change to routing strings, API routes, or dispatch logic.
Overview
Moves SP-1 routing-target constants (
ainfera-inference, alias frozensets,is_routed) out ofrouters/inference.pyinto a new leaf moduleainfera_api/routing_targets.py, so services can classify routed vs passthrough without importing the FastAPI inference router (and its brain/ORM chain).capture_invariant.pynow importsROUTING_TARGETSfrom that leaf module, breaking a circular import (capture_invariant→routers/inference→ … → back to services). The inference router re-exports the same names and_is_routedso existingfrom ainfera_api.routers.inference import …call sites stay unchanged; new code is directed torouting_targets.Adds
tests/unit/test_routing_targets_module.pyto guard the leaf has no internalainfera_apideps, back-compat re-exports, the capture_invariant import path, and leafis_routedvs router_is_routedparity. Public routing strings and dispatch behavior are unchanged.Reviewed by Cursor Bugbot for commit 6d34f39. Bugbot is set up for automated code reviews on this repo. Configure here.