o2o-site-AEO/solution/backend/services/collector/registry.py
Mina Choi 11d30bb3d1 [chore] solution,admin,ontology: 코드 주석을 한 줄로 — 히스토리 주석 삭제
여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다.

- ts·tsx·js·mjs·css·py 478개: 여러 줄 주석은 첫 문장 한 줄로, 과거형·날짜 문장은 삭제
- 주석 위치는 TypeScript 파서·파이썬 tokenize/ast 로 찾는다 — 문자열 안의 # · /* 는 건드리지 않는다
- eslint·ts·noqa·type: ignore 같은 지시 주석은 그대로 둔다

파이썬 275개 정리 전후 AST 동일, TS 298개 주석 뺀 토큰 동일(빈 JSX 주석 10곳만 차이).
site·frontend·admin tsc, site vitest 105 passed

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 16:05:19 +09:00

86 lines
3.3 KiB
Python

"""어댑터 레지스트리 — '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 만 켜져 있습니다 — 실제 수집은 일어나지 않습니다")