628d886cba
배포 검증 중 발견: domain_policy.yaml 이 repo root 에 있지만 fastapi 컨테이너의 build context 는 ./app 이라 COPY 가 포함하지 못함. 결과 load_policy() 가 FileNotFoundError. 1. docker-compose.yml: config.yaml 과 동일 패턴으로 읽기전용 bind mount - ./domain_policy.yaml:/app/domain_policy.yaml:ro 2. app/policy/loader.py: _resolve_path 에 4 개 후보 검색 추가 — cwd / /app / /app/.. / <this>.parent.parent.parent 순으로 파일 존재 확인. 첫 매칭 반환. 로컬/컨테이너/다른 배포 환경 모두 호환. CI: pytest tests/policy/ -q → 98 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
68 lines
2.1 KiB
Python
68 lines
2.1 KiB
Python
"""domain_policy.yaml loader with lru_cache."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
from policy.schema import DomainPolicy
|
|
|
|
|
|
DEFAULT_POLICY_FILENAME = "domain_policy.yaml"
|
|
POLICY_PATH_ENV = "POLICY_PATH"
|
|
|
|
|
|
def _resolve_path(path: str | None) -> Path:
|
|
if path is not None:
|
|
return Path(path)
|
|
env_path = os.environ.get(POLICY_PATH_ENV)
|
|
if env_path:
|
|
return Path(env_path)
|
|
|
|
# 검색 순서 (multi-env 호환):
|
|
# 1. cwd / domain_policy.yaml 로컬 pytest (repo-root 실행)
|
|
# 2. /app / domain_policy.yaml container bind-mount 경로
|
|
# 3. /app/../domain_policy.yaml container: /app 의 parent
|
|
# 4. <this>.parent.parent.parent / yaml policy 패키지 기준 repo-root
|
|
candidates = [
|
|
Path.cwd() / DEFAULT_POLICY_FILENAME,
|
|
Path("/app") / DEFAULT_POLICY_FILENAME,
|
|
Path("/app").parent / DEFAULT_POLICY_FILENAME,
|
|
Path(__file__).resolve().parent.parent.parent / DEFAULT_POLICY_FILENAME,
|
|
]
|
|
for c in candidates:
|
|
if c.is_file():
|
|
return c
|
|
# 찾지 못한 경우 첫 후보 반환 → 나중에 FileNotFoundError 로 명확히 실패
|
|
return candidates[0]
|
|
|
|
|
|
@lru_cache(maxsize=8)
|
|
def _load_cached(resolved: str) -> DomainPolicy:
|
|
text = Path(resolved).read_text(encoding="utf-8")
|
|
raw = yaml.safe_load(text)
|
|
return DomainPolicy.model_validate(raw)
|
|
|
|
|
|
def load_policy(path: str | None = None) -> DomainPolicy:
|
|
"""Load policy yaml and validate via pydantic.
|
|
|
|
Cache key = resolved absolute path (문자열). 테스트에서 다른 path 주면 별도 캐시.
|
|
"""
|
|
resolved = str(_resolve_path(path).resolve())
|
|
return _load_cached(resolved)
|
|
|
|
|
|
def clear_cache() -> None:
|
|
"""테스트용 — 연속 호출 시 서로 다른 yaml 을 반영해야 할 때."""
|
|
_load_cached.cache_clear()
|
|
|
|
|
|
def read_policy_bytes(path: str | None = None) -> bytes:
|
|
"""policy_version hash 계산용 — yaml 원본 바이트."""
|
|
resolved = _resolve_path(path).resolve()
|
|
return resolved.read_bytes()
|