여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다. - 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>
86 lines
3.3 KiB
Python
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 만 켜져 있습니다 — 실제 수집은 일어나지 않습니다")
|