"""Registration-phase registry and its immutable runtime product. Extensions only ever see the write-only public ``ExtensionRegistry`` contract. The concrete host type additionally owns attribution, rollback, and immutable runtime projection. """ from __future__ import annotations from collections.abc import Iterator from contextlib import contextmanager from dataclasses import dataclass from typing import Any from deerflow_extension_api import ExtensionData, MiddlewareContributor from deerflow_extension_api import ExtensionRegistry as ExtensionRegistryContract _Entry = tuple[str, Any] @dataclass(frozen=True) class LoadedExtensions: """Immutable view consumed at runtime. Every entry carries its source string so diagnostics, provenance and ordering errors can name the extension responsible. """ app_store: ExtensionData middleware_contributors: tuple[tuple[str, MiddlewareContributor], ...] = () # Precomputed attributes, not methods: hook sites read one attribute to # short-circuit, so the zero-extension path constructs nothing. has_middleware_contributors: bool = False needs_task_store: bool = False class ExtensionRegistry(ExtensionRegistryContract): """Mutable, registration-phase only. Subclasses the public contract Protocol so the host implementation is type-checked against what extensions annotate; the host-only machinery below (attribution, discard, mark/rollback_to, build) stays out of the contract on purpose. """ def __init__(self) -> None: self._middlewares: list[_Entry] = [] self._current_source: str | None = None @contextmanager def attributed_to(self, source: str) -> Iterator[None]: """Attribute everything registered inside the block to ``source``.""" previous = self._current_source self._current_source = source try: yield finally: self._current_source = previous def _source(self) -> str: if self._current_source is None: raise RuntimeError("registration must happen inside ExtensionRegistry.attributed_to(...)") return self._current_source def middlewares(self, contributor: MiddlewareContributor) -> None: self._middlewares.append((self._source(), contributor)) def discard(self, source: str) -> None: """Remove every entry registered by ``source``. Called when install() raises partway through. A half-registered extension is more dangerous than an absent one because the data it produces looks complete. Note: this matches by source string, so it is unsafe when two specs share the same ``use`` with different config — it would remove a different, successfully-installed instance's entries too. Callers that process one install() at a time should prefer ``mark()``/``rollback_to()`` instead. """ self._middlewares[:] = [entry for entry in self._middlewares if entry[0] != source] def mark(self) -> int: """Snapshot bucket lengths so one install() can be undone positionally.""" return len(self._middlewares) def rollback_to(self, mark: int) -> None: """Undo every registration made since ``mark``. Positional rather than source-keyed: two specs may legitimately share a ``use`` string with different config, and deleting by source would take the other instance's successful registrations with it. """ del self._middlewares[mark:] def build(self) -> LoadedExtensions: return LoadedExtensions( app_store=ExtensionData("app"), middleware_contributors=tuple(self._middlewares), has_middleware_contributors=bool(self._middlewares), needs_task_store=bool(self._middlewares), ) #: Shared empty instance for hosts that load no extensions. EMPTY_EXTENSIONS = ExtensionRegistry().build()