Typing strict, Protocols et dataclasses
Le contrat : mypy --strict
Le projet compile avec mypy --strict : toute fonction est typĂ©e, tout Any qui fuit fait Ă©chouer la CI. Ce choix transforme Python en langage Ă contrats â les signatures deviennent la documentation, et le refactoring redevient sĂ»r.
async def match_line(line: StructuredLine, catalog: CatalogProvider) -> MatchResult: ...
Protocol : l'interface sans héritage
La rĂšgle du projet : composition > hĂ©ritage, jamais d'ABC. L'outil : typing.Protocol â le structural typing (duck typing vĂ©rifiĂ© statiquement) :
from typing import Protocol
class CatalogProvider(Protocol):
async def search(self, query: str, limit: int) -> list[Variant]: ...
async def get_by_sku(self, sku: str) -> Variant | None: ...
# AUCUNE classe n'hérite de CatalogProvider. Toute classe qui a
# ces méthodes EST un CatalogProvider aux yeux de mypy :
class LocalCatalogProvider: # pas de « (CatalogProvider) » !
async def search(self, query: str, limit: int) -> list[Variant]: ...
Avantage dĂ©cisif dans un modular monolith : engine dĂ©clare le Protocol chez lui (ou dans shared), catalog fournit une implĂ©mentation sans dĂ©pendre du consommateur â zĂ©ro couplage d'import, la frontiĂšre du module 0 tient toute seule.
dataclass(frozen=True, slots=True) : la donnée immuable
Les objets-valeurs du projet sont des dataclasses gelées :
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class MatchResult:
variant_id: str
score: float
method: str # "exact" | "hybrid" | "deep"
frozen=True: immuable â partageable entre tĂąches async sans effroi ;slots=True: pas de__dict__â moins de mĂ©moire, et les fautes de frappe d'attributs explosent au lieu de crĂ©er un champ silencieux (le « fail loud » du projet).
Ce qui distingue Protocol d'une classe abstraite (ABC) :