Files
scrap/pricewatch/app/core/registry.py
2026-01-13 19:49:04 +01:00

192 lines
5.4 KiB
Python
Executable File

"""
Registry pour la détection automatique des stores.
Le Registry maintient une liste de tous les stores disponibles et
peut détecter automatiquement quel store correspond à une URL donnée.
"""
from typing import Optional
from pricewatch.app.core.logging import get_logger
from pricewatch.app.stores.base import BaseStore
logger = get_logger("core.registry")
class StoreRegistry:
"""
Registry central pour tous les stores.
Permet d'enregistrer des stores et de détecter automatiquement
le bon store depuis une URL via la méthode match().
Justification technique:
- Pattern Registry pour découpler la détection des stores du code métier
- Extensible: ajouter un nouveau store = juste register() un nouvel objet
- Pas de dépendances hardcodées entre modules
"""
def __init__(self):
"""Initialise un registry vide."""
self._stores: list[BaseStore] = []
logger.debug("Registry initialisé")
def register(self, store: BaseStore) -> None:
"""
Enregistre un nouveau store dans le registry.
Args:
store: Instance de BaseStore à enregistrer
"""
if not isinstance(store, BaseStore):
raise TypeError(f"Expected BaseStore, got {type(store)}")
# Éviter les doublons
if any(s.store_id == store.store_id for s in self._stores):
logger.warning(f"Store '{store.store_id}' déjà enregistré, remplacement")
self._stores = [s for s in self._stores if s.store_id != store.store_id]
self._stores.append(store)
logger.info(f"Store enregistré: {store.store_id}")
def unregister(self, store_id: str) -> bool:
"""
Retire un store du registry.
Args:
store_id: ID du store à retirer
Returns:
True si le store a été retiré, False s'il n'était pas présent
"""
initial_count = len(self._stores)
self._stores = [s for s in self._stores if s.store_id != store_id]
removed = len(self._stores) < initial_count
if removed:
logger.info(f"Store désenregistré: {store_id}")
else:
logger.warning(f"Store non trouvé pour désenregistrement: {store_id}")
return removed
def get_store(self, store_id: str) -> Optional[BaseStore]:
"""
Récupère un store par son ID.
Args:
store_id: ID du store à récupérer
Returns:
Instance du store ou None si non trouvé
"""
for store in self._stores:
if store.store_id == store_id:
return store
return None
def detect_store(self, url: str) -> Optional[BaseStore]:
"""
Détecte automatiquement le store correspondant à une URL.
Args:
url: URL à analyser
Returns:
Store avec le meilleur score, ou None si aucun match
Justification technique:
- Teste tous les stores enregistrés avec leur méthode match()
- Retourne celui avec le score le plus élevé (> 0)
- Permet de gérer les ambiguïtés (ex: sous-domaines multiples)
"""
if not url or not url.strip():
logger.warning("URL vide fournie pour détection")
return None
if not self._stores:
logger.warning("Aucun store enregistré dans le registry")
return None
best_store: Optional[BaseStore] = None
best_score = 0.0
logger.debug(f"Détection du store pour: {url}")
for store in self._stores:
try:
score = store.match(url)
logger.debug(f" {store.store_id}: score={score:.2f}")
if score > best_score:
best_score = score
best_store = store
except Exception as e:
logger.error(f"Erreur lors du match de {store.store_id}: {e}")
continue
if best_store:
logger.info(
f"Store détecté: {best_store.store_id} (score={best_score:.2f})"
)
else:
logger.warning(f"Aucun store trouvé pour: {url}")
return best_store
def list_stores(self) -> list[str]:
"""
Liste tous les stores enregistrés.
Returns:
Liste des IDs de stores
"""
return [store.store_id for store in self._stores]
def __len__(self) -> int:
"""Retourne le nombre de stores enregistrés."""
return len(self._stores)
def __repr__(self) -> str:
stores_list = ", ".join(self.list_stores())
return f"<StoreRegistry stores=[{stores_list}]>"
# Instance globale du registry
# Les stores s'y enregistreront lors de leur import
_global_registry = StoreRegistry()
def get_registry() -> StoreRegistry:
"""
Retourne l'instance globale du registry.
Returns:
Registry singleton
"""
return _global_registry
def register_store(store: BaseStore) -> None:
"""
Enregistre un store dans le registry global.
Args:
store: Instance de BaseStore
"""
_global_registry.register(store)
def detect_store(url: str) -> Optional[BaseStore]:
"""
Détecte le store depuis le registry global.
Args:
url: URL à analyser
Returns:
Store détecté ou None
"""
return _global_registry.detect_store(url)