# backend/app/connectors/registry.py — CONNECTOR_ 로 mock|real 선택하는 단일 지점. # 페이지/라우터는 항상 레지스트리를 통해서만 sync 커넥터를 얻는다(코드 불변의 핵심). from sqlmodel import Session, select from ..config import get_settings from ..models import ConnectorAccount from .base import BaseConnector def _impl(domain: str, mode: str, provider: str = ""): """도메인 × 모드 → 구현 클래스 (lazy import 로 의존성 격리).""" if domain == "mail": # mock 제거(phase-16+): 실 계정만 동기화 if mode == "real": # real 메일은 provider 로 Gmail/Outlook 분기 from .mail.real_gmail import GmailConnector from .mail.real_outlook import OutlookConnector return OutlookConnector if provider == "outlook" else GmailConnector if mode == "imap": from .mail.real_imap import ImapConnector return ImapConnector raise ValueError(f"unsupported mail connector mode: {mode}") if domain == "calendar": # mock 제거(phase-16+) if mode == "real": # real 캘린더는 provider 로 Google/Outlook 분기 if provider == "outlook_calendar": from .calendar.real_outlook import OutlookCalendarConnector return OutlookCalendarConnector from .calendar.real_google import GoogleCalendarConnector return GoogleCalendarConnector if mode == "ics": from .calendar.ics_import import IcsConnector return IcsConnector raise ValueError(f"unsupported calendar connector mode: {mode}") raise ValueError(f"unknown domain {domain}") class ConnectorRegistry: @staticmethod def mode_for(domain) -> str: dom = domain.value if hasattr(domain, "value") else str(domain) return getattr(get_settings(), f"connector_{dom}", "mock") @staticmethod def effective_mode(account: ConnectorAccount) -> str: """모드 결정 정본: - 계정 mode 가 mock 이 아니면(real/csv/healthkit/ics) 그 모드를 쓴다(연결된 계정). - 시드 mock 계정은 env=real 이어도 mock 유지(데모 결정성). - csv/healthkit/ics env 면 그 모드를 따른다(로컬 우선 임포트).""" env_mode = ConnectorRegistry.mode_for(account.domain) acct_mode = account.mode.value if hasattr(account.mode, "value") else str(account.mode) if acct_mode != "mock": return acct_mode return "mock" if env_mode == "real" else env_mode @staticmethod def get(session: Session, account: ConnectorAccount) -> BaseConnector: mode = ConnectorRegistry.effective_mode(account) dom = account.domain.value if hasattr(account.domain, "value") else account.domain return _impl(dom, mode, account.provider)(account) @staticmethod def accounts(session: Session, domain: str | None = None) -> list[ConnectorAccount]: q = select(ConnectorAccount) if domain: q = q.where(ConnectorAccount.domain == domain) return session.exec(q.order_by(ConnectorAccount.id)).all()