"""어댑터 레지스트리 — 'URL 하나에 어댑터 하나' 를 결정하는 유일한 지점.""" import os from typing import Optional from common.logger import LOG from services.collector.base import AdapterDisabled, AdapterNotFound, SourceAdapter from services.collector.mock_adapter import MockAdapter from services.collector.naver_place_adapter import NaverPlaceAdapter from services.collector.static_html_adapter import StaticHtmlAdapter from services.collector.tour_api_adapter import TourApiAdapter from services.collector.yanolja_adapter import YanoljaAdapter # 이 환경에서 켜둘 어댑터 id 집합. ENABLED_ADAPTERS: frozenset = frozenset( x.strip() for x in os.getenv("COLLECT_ADAPTERS", "mock,naver_place,tour_api,static_html,yanolja").split(",") if x.strip() ) class AdapterRegistry: """등록 순서대로 can_handle 을 물어 첫 번째로 손드는 어댑터를 쓴다.""" def __init__(self, enabled: Optional[frozenset] = None): self._adapters: list[SourceAdapter] = [] self._enabled = ENABLED_ADAPTERS if enabled is None else enabled def register(self, adapter: SourceAdapter) -> None: if any(a.id == adapter.id for a in self._adapters): raise ValueError(f"어댑터 id 중복: {adapter.id}") self._adapters.append(adapter) def adapter_ids(self) -> list[str]: """등록된 어댑터 id — 기동 로그·테스트가 '지금 뭐가 켜져 있나' 를 확인하는 창구.""" return [a.id for a in self._adapters] def enabled_ids(self) -> list[str]: return [a.id for a in self._adapters if a.id in self._enabled] def get_adapter(self, url: str) -> SourceAdapter: """URL 을 처리할 어댑터.""" for adapter in self._adapters: if not adapter.can_handle(url): continue if adapter.id not in self._enabled: raise AdapterDisabled(f"어댑터 '{adapter.id}' 는 이 환경에서 꺼져 있다: {url}") return adapter raise AdapterNotFound(f"이 URL 을 처리할 어댑터가 없다: {url}") def can_handle(self, url: str) -> bool: """예외 없이 처리 가능 여부만 — 확정 링크를 수집 대상으로 거를 때 쓴다.""" try: self.get_adapter(url) return True except (AdapterNotFound, AdapterDisabled): return False def _register_default() -> AdapterRegistry: registry = AdapterRegistry() # 좁은 어댑터(특정 도메인)를 먼저, 넓은 어댑터를 뒤에. registry.register(NaverPlaceAdapter()) registry.register(TourApiAdapter()) registry.register(YanoljaAdapter()) registry.register(MockAdapter()) registry.register(StaticHtmlAdapter()) return registry REGISTRY = _register_default() def get_adapter(url: str) -> SourceAdapter: """기본 레지스트리에서 어댑터를 찾는다.""" return REGISTRY.get_adapter(url) def adapter_ids() -> list[str]: return REGISTRY.adapter_ids() def log_registry() -> None: """기동 로그용 — 이 프로세스가 실제로 긁을 수 있는 소스.""" ids = REGISTRY.enabled_ids() LOG.i(f"[collector] 사용 가능 어댑터: {', '.join(ids) if ids else '없음'}") if ids == ["mock"]: LOG.w("[collector] MockAdapter 만 켜져 있습니다 — 실제 수집은 일어나지 않습니다")