""" 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"" # 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)