orthonym.assembly.inner_dispatch#
Note
Internal API. Names and behaviour may change between releases.
a phase inner-dispatch substrate (DECOMP-01 + internal notes).
Mirrors a phase outer CFR routing/dispatch_table.py substrate at the
INNER (post-class-routing) dispatch layer. Per internal notes the inner
table is structurally identical to the outer CFR table:
@dataclass(frozen=True) class InnerDispatchEntrywith 7 fields (handler_id, priority, predicate, handler, iupac_section, description, side_effect_inventory).INNER_DISPATCH_TABLE: OrderedDict[str, InnerDispatchEntry]populated at module-import time by_register_inner(...)calls in the per-handler atomic commits 02-01..02-29 (Plan-02) + 03-01..03-09 (Plan-03)._register_inner(...)private helper; raisesRuntimeErrorif called after_INNER_REGISTRATION_FROZENis set, or if a handler_id / priority is already registered.dispatch_inner(features) -> Optional[InnerDispatchResult]first-match- wins iteration in priority order.
Plan-02 substrate commit (02-00) ships the table EMPTY; commits 02-01..02-29
each append ONE _register_inner(...) call (Tier-1 + Tier-1.5 handlers).
Plan-03 ships the catch-all general_acyclic at priority 99999 (commit
03-09) — per internal notes the catch-all closes the Plan-02 fallthrough gap.
Anti-pattern hygiene: - -08 banned: silent fallthrough in inner-dispatch without explicit
general_acycliccatch-all entry at priority 99999. Plan-02 fallthrough to inline composer.py mid-tier + root branches is the deliberate Plan-02 bridge — Plan-03 closes the gap.
-09 banned: opt-out flag for inner dispatch (no
_disable_inner_dispatchkwarg or env-var-controlled bypass).-13: module-global mutable state for inner-dispatch_stats — keep per-Orthonym-instance counter mirror of a phase; stats helpers are stateful but per-instance-isolated.
-15 / hard invariant:
side_effect_inventory == `` for every entry; ``_register_innerraisesValueErrorif a non-empty tuple is passed.-10 banned: invent-as-you-go INNER_DISPATCH_TABLE entries not in the audit Every
_register_inner(...)call MUST cite the audit row it implements.
References: - internal notes-DECOMP.md — inner-dispatch branch enumeration (39 rows;
source-of-truth for the 29 Plan-02 commits + 10 Plan-03 commits).
160-internal notes — substrate shape matches a phase.
internal notes — analog: routing/dispatch_table.py:130-211, 696-720.
- class orthonym.assembly.inner_dispatch.InnerDispatchEntry(handler_id, priority, predicate, handler, iupac_section, description, side_effect_inventory=())#
Bases:
objecta phase: frozen dataclass row of INNER_DISPATCH_TABLE.
Mirrors a phase + (routing/dispatch_table.ClassDispatchEntry) at the INNER dispatch layer. The 7-field schema is locked per internal notes :
handler_id HANDLER_POLICIES key (lowercase snake_case) priority spaced int; lower = earlier; first-match-wins predicate Callable[…, bool]; MolecularFeatures → bool;
PURE per (no mutation of features / mol / module-global state)
- handler Callable[…, Optional[NamingResult]]; the
handler module’s name_<handler_id> entry point
iupac_section Blue Book P-section cite (audit metadata) description one-line summary for audit logging side_effect_inventory MUST be per hard invariant; non-empty
tuples raise ValueError in _register_inner.
Per internal notes + -15:
side_effect_inventory == `` is the HARD invariant; the integrity test ``test_side_effect_inventory_is_empty(Plan-04) asserts emptiness for every entry. -26 explicitly bans any predicate that mutatesMolecularFeatures,mol, module-global state, or thread-local state. Handler invocations MAY callpool.add(the SOLE accepted shared-state interaction per a phase contract); recursiveorthonym.name_compound(...)calls are PERMITTED in the N-oxide handler (audited per of internal notes-DECOMP.md).- handler_id: str#
- priority: int#
- predicate: Callable[[...], bool]#
- handler: Callable[[...], Any | None]#
- iupac_section: str#
- description: str#
- side_effect_inventory: Tuple[str, ...] = ()#
- class orthonym.assembly.inner_dispatch.InnerDispatchResult(handler_id, handler, audit_record, matched_entry, result=None)#
Bases:
objecta phase + a phase / -04: result of a successful
dispatch_inner(features, mol, style)call.Pre-amendment (a phase): returned the matched-entry reference; the caller (composer.py:_assemble_name_impl) invoked
result.handler(...)separately and handled gate-fail (handler returning None) via inline fall-through to the legacy cascade.Post-amendment (a phase + -04 — first-match-AND-succeeds-wins):
dispatch_innerinvokes the handler INTERNALLY and retries the next-priority entry on gate-fail (handler returningNone). The newresult: NamingResultfield carries the non-NoneNamingResultthat the chosen handler returned. The caller collapses to a singlereturn _inner_result.result.name.- Handler contract per -04:
Return non-None
NamingResult⇒ “I succeeded; use this result.”Return
None⇒ “I gate-failed; defer to next-priority handler.”Raise
Exception⇒ surfaced asRuntimeErrorchained via__cause__(no swallowing per internal notes honest-fail-on-data).
Predicate purity (a phase) is preserved verbatim: predicates report whether a handler CAN POSSIBLY apply (cheap structural check); the handler’s own gate decides whether it SHOULD apply (full IUPAC compliance check on coverage / orientation / locant feasibility / etc.).
The
audit_recordfield is a dict of audit metadata (handler_id, priority, iupac_section) for the Plan-04--dump-treeCLI + the inner-dispatch stats counter (stats now count SUCCESSFUL handlers only, not predicate matches per).- handler_id: str#
- handler: Callable[[...], Any | None]#
- audit_record: Dict[str, str]#
- matched_entry: InnerDispatchEntry#
- result: Any | None = None#
- orthonym.assembly.inner_dispatch.freeze_inner_table()#
a phase: lock INNER_DISPATCH_TABLE against further registration.
Called by Plan-03 commit 03-10 (composer.py thinning) at the end of module import; after this call,
_register_inner(...)raisesRuntimeErrorper -09 (no opt-out / runtime modification).Plan-02 / Plan-03 atomic commits do NOT call this — the table stays open across the migration. Plan-04 is the FIRST plan that calls
freeze_inner_tableafter registering all 30 entries.
- orthonym.assembly.inner_dispatch.dispatch_inner(features, mol=None, style='pin')#
a phase + a phase / -04: first-match-AND- succeeds-wins inner-cascade dispatch.
Iterates
INNER_DISPATCH_TABLEin priority order (lowest first; OrderedDict insertion order matches priority-sorted insertion via_register_inner); for each entry whose predicate matches, INVOKES the handler internally. On non-None return, builds and returnsInnerDispatchResultcarrying the result. On None return (gate-fail per -04 contract), CONTINUES to the next-priority entry. Final no-match returnsNone.-04 amends a phase internal notes from “first-match-wins” to “first-match-AND-succeeds-wins.” Handler contract:
Return non-None
NamingResult⇒ “I succeeded; use this result.”Return
None⇒ “I gate-failed; defer to next-priority handler.”Raise
Exception⇒ surfaced asRuntimeErrorchained via__cause__(no silent swallowing).
Predicate purity (a phase) preserved verbatim: predicates report whether a handler CAN POSSIBLY apply (cheap structural check); handlers may perform internal pool.add side effects per their individual contracts.
- Parameters:
features (Any) –
MolecularFeaturesinstance (the upstream perception output that handlers consume).mol (Any) – RDKit
Molobject. Defaults tofeatures.molwhen None (preserves the pre-amendment call-site signature).style (str) – PIN / IUPAC name-style flag forwarded to handlers; default
"pin".
- Returns:
InnerDispatchResultcarrying the SUCCESSFUL handler’sNamingResultin.result, orNoneif no handler succeeded. Oncegeneral_acycliccatch-all ships (Plan-03-03 per internal notes), the None branch is unreachable in production.- Return type:
InnerDispatchResult | None
Per internal notes honest-fail-on-data: defensive try/except around BOTH predicate AND handler invocations surface bugs (NoneType attribute access, etc.) as
RuntimeErrorchained via__cause__.
- orthonym.assembly.inner_dispatch.get_inner_dispatch_stats()#
a phase + -13: read per-handler dispatch counters.
Returns a COPY of the in-memory counter dict so callers cannot mutate the underlying state. Plan-04 wires this into the CLI (–audit-trace flag) to surface per-handler hit rates during benchmarking.
a phase Plan-04-02: reads the per-
ContextVarcontext dict so concurrent Orthonym instances each see their own counter snapshot.
- orthonym.assembly.inner_dispatch.reset_inner_dispatch_stats()#
a phase: clear the per-handler dispatch counters.
Called by tests + Plan-04 benchmarks to isolate counter state across runs. Mirrors a phase
reset_dispatch_counterper -13.a phase Plan-04-02: clears the current
ContextVarcontext’s dict only; other concurrent contexts keep their counters.