Claude
fix(evaluation): Sprint A14-S25 β€” Projector retourne (Artifact, payload, report)
f3b0e4a unverified
Raw
History Blame
4.9 kB
"""``ProjectorRegistry`` β€” Sprint A14-S13.
Container instanciΓ© explicitement qui mappe ``projector_name``
vers une instance ``Projector``. SymΓ©trique du ``MetricRegistry``
(S5) : pas de singleton global, pas de side-effect d'import.
Pattern d'utilisation
---------------------
.. code-block:: python
from picarones.evaluation.projectors import (
ProjectorRegistry, AltoToText,
)
from picarones.formats.alto import AltoToText as _AltoToText
registry = ProjectorRegistry()
registry.register(_AltoToText())
registry.register(PageToText())
projector = registry.get("alto_to_text")
target_artifact, payload, report = projector.project(source_artifact, {})
Au S20, ce registre sera construit par
``app/services/registry_service.py`` au dΓ©marrage de l'application.
Pour S13-S18, chaque test ou consommateur l'instancie explicitement.
Anti-sur-ingΓ©nierie
-------------------
Pas de versioning de projecteur, pas de namespace, pas de recherche
par tag. Ces extras viendront quand un caller en aura concrètement
besoin (probablement avec les projecteurs contribuΓ©s par des modules
tiers, post-livraison).
"""
from __future__ import annotations
from picarones.domain.errors import PicaronesError
from picarones.evaluation.projectors.base import Projector
class ProjectorRegistrationError(PicaronesError):
"""Tentative d'enregistrement invalide d'un projecteur."""
class ProjectorNotFoundError(PicaronesError):
"""Le projecteur demandΓ© n'est pas enregistrΓ©."""
class ProjectorRegistry:
"""Container mutable de projecteurs indexΓ©s par ``name``.
Thread-safe en lecture après initialisation ; la séquence
d'enregistrement attendue est : un seul service, au dΓ©marrage,
enregistre tous les projecteurs en une fois, puis l'instance
est figΓ©e par convention.
"""
def __init__(self) -> None:
self._projectors: dict[str, Projector] = {}
# ──────────────────────────────────────────────────────────────────
# Enregistrement
# ──────────────────────────────────────────────────────────────────
def register(self, projector: Projector) -> None:
"""Enregistre un projecteur.
Raises
------
ProjectorRegistrationError
Si un projecteur du mΓͺme nom est dΓ©jΓ  enregistrΓ© (sauf
re-enregistrement strict du mΓͺme objet, tolΓ©rΓ© pour les
tests qui re-instancient).
"""
if not hasattr(projector, "name"):
raise ProjectorRegistrationError(
"register : l'objet n'expose pas d'attribut ``name``."
)
if not isinstance(projector, Projector):
raise ProjectorRegistrationError(
f"register : {projector!r} ne satisfait pas le protocole "
"Projector (attributs ``name``, ``source_type``, "
"``target_type``, mΓ©thode ``project``)."
)
existing = self._projectors.get(projector.name)
if existing is not None:
if existing is projector:
return # idempotent
raise ProjectorRegistrationError(
f"Projecteur {projector.name!r} dΓ©jΓ  enregistrΓ© avec "
"une autre instance."
)
self._projectors[projector.name] = projector
# ──────────────────────────────────────────────────────────────────
# Lecture
# ──────────────────────────────────────────────────────────────────
def __contains__(self, name: str) -> bool:
return name in self._projectors
def __len__(self) -> int:
return len(self._projectors)
def names(self) -> list[str]:
"""Liste des noms enregistrΓ©s (ordre d'enregistrement)."""
return list(self._projectors.keys())
def get(self, name: str) -> Projector:
"""Récupère le projecteur par son ``name``.
Raises
------
ProjectorNotFoundError
Si le nom n'est pas enregistrΓ©.
"""
if name not in self._projectors:
raise ProjectorNotFoundError(
f"Projecteur {name!r} non enregistrΓ©. "
f"Disponibles : {sorted(self._projectors)}."
)
return self._projectors[name]
__all__ = [
"ProjectorRegistry",
"ProjectorRegistrationError",
"ProjectorNotFoundError",
]