Spaces:
Running
Running
File size: 7,148 Bytes
478e60e 5112943 1ef330c 5112943 478e60e 1ef330c ebddecf 5112943 478e60e 50b07b8 478e60e 50b07b8 478e60e 5112943 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | """Helpers tests — pattern ``RunOrchestrator`` pour les tests B4.
Phase B3-final (mai 2026) — implémente directement le pattern 3
étapes ``prepare_preset_args`` → ``execute_preset`` →
``run_result_to_benchmark_result`` pour servir les 6 fichiers de
tests catégorie A migrés en Phase B4.
Pourquoi un helper test dédié plutôt qu'inline dans chaque test ?
-----------------------------------------------------------------
Les tests B4 (~30+ cas) consomment ce helper avec la même signature
que l'ancien ``run_benchmark_via_service``. Le mettre inline dans
chaque test ajouterait ~10 lignes de boilerplate par cas, noyant
l'intention du test.
Ce helper ``run_via_orchestrator`` est un **outil de test**
(préfixe ``_`` du module + dossier ``tests/``). Son existence ne
constitue pas de la dette technique en production : il n'y a pas
de shim équivalent dans ``picarones/`` (les call sites CLI/Web
font le pattern 3 étapes explicitement).
Convention « ``code_version`` dans les tests »
==============================================
De nombreux tests (~50+ fichiers) instancient des ``RunContext``,
``ProvenanceRecord``, ``ArtifactKey``, ``RunSpec`` avec
``code_version="1.0.0"`` en littéral. **Cette valeur est un
placeholder de fixture**, pas une assertion sur la version réelle
de Picarones au moment du test.
Picarones suit SemVer pré-1.0 et sa version courante est résolue
dynamiquement via ``picarones.__version__`` (voir
``docs/explanation/versioning.md``). Les tests utilisent ``"1.0.0"``
comme valeur arbitraire stable parce que :
- la sémantique testée ne dépend pas de la valeur réelle (cache,
manifeste, provenance — ce qui compte est l'**égalité** ou la
**non-égalité** entre deux ``code_version``, pas la valeur) ;
- une string ``"X.Y.Z"`` cohérente PEP 440 facilite les assertions
sur la structure ;
- la stabilité historique évite de devoir réécrire 50+ tests à
chaque bump de version.
Le garde-fou ``tests/architecture/test_no_hardcoded_version.py``
neutralise spécifiquement les patterns ``code_version="X.Y.Z"`` via
``PLACEHOLDER_PATTERNS`` pour ne pas les confondre avec de vraies
mentions de version Picarones.
"""
from __future__ import annotations
import tempfile
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable
if TYPE_CHECKING:
from picarones.evaluation.benchmark_result import BenchmarkResult
from picarones.evaluation.corpus import Corpus
def run_via_orchestrator(
corpus: "Corpus",
engines: list[Any],
*,
views: tuple[str, ...] = ("text_final",),
char_exclude: Any | None = None,
normalization_profile: Any | None = None,
output_json: str | Path | None = None,
code_version: str | None = None,
show_progress: bool = True, # noqa: ARG001 — absorbé pour compat tests
progress_callback: Callable[[str, int, str], None] | None = None,
timeout_seconds: float = 60.0,
cancel_event: Any | None = None,
partial_dir: str | Path | None = None,
entity_extractor: Callable[[str], list[dict]] | str | None = None,
profile: str = "standard",
) -> "BenchmarkResult":
"""Helper test : invoque ``RunOrchestrator`` et retourne un
``BenchmarkResult`` legacy.
Reproduit le pattern 3 étapes utilisé en production (CLI/Web)
pour que les tests B4 valident le même chemin que les utilisateurs.
Signature alignée sur l'ancien ``run_benchmark_via_service`` pour
minimiser le boilerplate dans les tests.
NER attach
----------
Si ``entity_extractor`` est un callable direct (pattern legacy),
le helper invoque ``attach_ner_metrics_to_benchmark`` en post-
process. Si c'est un dotted path string, ``execute_preset`` le
résout lui-même via ``RunSpec.entity_extractor``.
"""
from picarones.app.services import (
RunOrchestrator,
prepare_preset_args,
run_result_to_benchmark_result,
)
# Séparation callable vs dotted path (cf. shim historique).
entity_extractor_dotted: str | None = None
entity_extractor_callable: Callable | None = None
if entity_extractor is not None:
if isinstance(entity_extractor, str):
entity_extractor_dotted = entity_extractor
elif callable(entity_extractor):
entity_extractor_callable = entity_extractor
pipeline_to_engine_name = {
# Construit après pipeline_specs ci-dessous (closure-friendly).
}
wrapped_callback = None
if progress_callback is not None:
def wrapped_callback(
pipeline_name: str, doc_idx: int, doc_id: str,
) -> None:
engine_name = pipeline_to_engine_name.get(
pipeline_name, pipeline_name,
)
progress_callback(engine_name, doc_idx, doc_id)
with tempfile.TemporaryDirectory(prefix="picarones_test_") as ws:
ws_path = Path(ws)
run_dir = ws_path / "run"
args = prepare_preset_args(
corpus, engines,
workspace_dir=ws_path / "gt",
output_dir=run_dir,
views=views,
char_exclude=char_exclude,
normalization_profile=normalization_profile,
partial_dir=partial_dir,
entity_extractor=entity_extractor_dotted,
profile=profile,
output_json=output_json,
timeout_seconds_per_doc=timeout_seconds,
code_version=code_version,
)
# Map pipeline_name → engine.name pour le callback wrapper.
pipeline_to_engine_name.update({
spec.name: engine.name
for spec, engine in zip(args.pipeline_specs, engines)
})
orch_result = RunOrchestrator(run_dir).execute_preset(
spec=args.spec,
corpus_spec=args.corpus_spec,
extracted_dir=args.extracted_dir,
pipeline_specs=args.pipeline_specs,
adapter_resolver=args.adapter_resolver,
adapter_kwargs=args.adapter_kwargs,
progress_callback=wrapped_callback,
cancel_event=cancel_event,
)
benchmark_result = run_result_to_benchmark_result(
orch_result.run_result,
corpus=corpus, engines=engines,
char_exclude=char_exclude,
normalization_profile=normalization_profile,
profile=profile,
)
# NER attach post-process si callable direct fourni.
if entity_extractor_callable is not None:
from picarones.app.services._benchmark_ner import (
attach_ner_metrics_to_benchmark,
)
attach_ner_metrics_to_benchmark(
benchmark_result, corpus, entity_extractor_callable,
)
# Sérialisation output_json (legacy comportement).
if output_json is not None:
from picarones.app.services._benchmark_persistence import (
persist_benchmark_result_json,
)
persist_benchmark_result_json(
benchmark_result, Path(output_json),
)
return benchmark_result
__all__ = ["run_via_orchestrator"]
|