0f1e609888
CycloneStore split precedent (SP21) — lift three helpers from scheduler.py + api.py into one module. Both callers will switch to this in Task 6. The new package also defines HandleResult and the HANDLERS registry; handle_999 / handle_ta1 / handle_277ca / handle_835 fill in over Tasks 2-5. The registry is lazy + best-effort: register_handlers() catches broad Exception so a partial-modification SyntaxError in one in-flight handler module can't break scheduler or API import. 11 tests added; existing suite unchanged (1 failed + 2 errors pre-existing in test_provider_extended_response.py are not introduced by this commit).
114 lines
3.9 KiB
Python
114 lines
3.9 KiB
Python
"""File-type handlers for inbound MFT files (SP27).
|
|
|
|
Each handler is a pure function that parses + persists + dispatches
|
|
events for one file type. The scheduler and the FastAPI endpoints
|
|
both delegate here; the ``HANDLERS`` registry maps ``file_type`` →
|
|
handler function.
|
|
|
|
Public API:
|
|
HandleResult — dataclass returned by every handler
|
|
HANDLERS — ``{"999": handle_999, "835": handle_835, ...}``
|
|
handle_999, handle_ta1, handle_277ca, handle_835
|
|
— call signatures: ``handle(text: str, source_file: str) -> HandleResult``
|
|
|
|
The handlers own their own DB session lifecycle. They emit pubsub
|
|
events via the optional ``event_bus`` parameter (the FastAPI endpoint
|
|
injects ``app.state.event_bus``; the scheduler passes ``None``).
|
|
They never raise on per-segment problems; per-segment issues are
|
|
logged and folded into the result. Whole-document failures
|
|
(missing ISA, bad encoding) surface as ``CycloneParseError``, which
|
|
the caller catches and records as ``STATUS_ERROR``.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import logging
|
|
|
|
from ._ack_id import (
|
|
ack_count_summary,
|
|
ack_synthetic_source_batch_id,
|
|
two77ca_synthetic_source_batch_id,
|
|
)
|
|
from .handle_result import HandleResult
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
# Module-level HANDLERS dict populated lazily once handler modules
|
|
# ship. Keys are file_type strings, values are the ``handle``
|
|
# callable for that type.
|
|
HANDLERS: dict[str, object] = {}
|
|
|
|
|
|
# Candidate handlers, in registration order. Each tuple is
|
|
# (module-path, file_type). The 277/277CA mapping is added explicitly
|
|
# after registration when the 277CA handler is present.
|
|
_CANDIDATES: list[tuple[str, str]] = [
|
|
("cyclone.handlers.handle_999", "999"),
|
|
("cyclone.handlers.handle_ta1", "TA1"),
|
|
("cyclone.handlers.handle_277ca", "277CA"),
|
|
("cyclone.handlers.handle_835", "835"),
|
|
]
|
|
|
|
|
|
def register_handlers() -> None:
|
|
"""Populate ``HANDLERS`` from the per-type handler modules.
|
|
|
|
Tolerates missing or broken handler modules so the package can be
|
|
imported incrementally as each handler ships (Tasks 2-5), and so
|
|
a partial-modification error in one handler module can't break
|
|
scheduler / API import. Safe to call multiple times.
|
|
"""
|
|
if HANDLERS:
|
|
return
|
|
for mod_path, file_type in _CANDIDATES:
|
|
try:
|
|
mod = importlib.import_module(mod_path)
|
|
fn = getattr(mod, "handle")
|
|
except Exception as exc: # noqa: BLE001 — best-effort registry
|
|
log.debug(
|
|
"handler %s unavailable: %s",
|
|
mod_path, exc,
|
|
)
|
|
continue
|
|
HANDLERS[file_type] = fn
|
|
# The 277 filename maps to the same 277CA handler.
|
|
if file_type == "277CA":
|
|
HANDLERS["277"] = fn
|
|
|
|
|
|
register_handlers()
|
|
|
|
|
|
# Re-export handler functions on this package so callers can use the
|
|
# flat import (``from cyclone.handlers import handle_999``) once each
|
|
# module ships. Set after registration so we know what's present.
|
|
def _reexport_handlers() -> None:
|
|
"""Re-export each handler's ``handle`` fn as ``handle_<type>``.
|
|
|
|
No-op for absent handlers. Re-run on each import so freshly-
|
|
installed handler modules (e.g. Tasks 2-5 commits) are visible
|
|
after ``register_handlers()`` without a process restart.
|
|
"""
|
|
for file_type, fn in list(HANDLERS.items()):
|
|
if file_type in ("277",): # alias of 277CA; don't re-export twice
|
|
continue
|
|
globals()[f"handle_{file_type.lower()}"] = fn
|
|
|
|
|
|
_reexport_handlers()
|
|
|
|
|
|
__all__ = [
|
|
"HANDLERS",
|
|
"HandleResult",
|
|
"register_handlers",
|
|
"ack_count_summary",
|
|
"ack_synthetic_source_batch_id",
|
|
"two77ca_synthetic_source_batch_id",
|
|
# handler_* names are added at module load via _reexport_handlers
|
|
]
|
|
# Populate __all__ with the present handler symbols.
|
|
for _h in ("handle_999", "handle_ta1", "handle_277ca", "handle_835"):
|
|
if _h in globals():
|
|
__all__.append(_h) # noqa: PYI056
|