"""공인인증 평가환경(python 3.9) 호환성 가드. 왜 필요한가: 계획서 p.24 는 No.3/4/7 의 평가환경을 **python 3.9** 로 못박았다. 그런데 `str | None` (PEP 604) 은 3.10 부터의 문법이라, `from __future__ import annotations` 없이 쓰면 클래스 본문이 실행되는 순간 인터프리터가 평가해 TypeError 로 죽는다. Pydantic 모델은 어노테이션이 런타임에 해석되므로 특히 위험하다. 개발 환경이 3.14 라 이 문제는 로컬에서 절대 드러나지 않는다. 실제로 `app/core/config.py` 와 `app/api/schemas.py` 가 이 상태였고, 그 두 파일을 타고 detector·routes·main 과 **성능지표 평가 스크립트 3종이 전부** 3.9 에서 임포트 불가였다(2026-08-19 확인·수정). 이 테스트는 3.14 에서도 그 회귀를 잡는다. 문법이 아니라 소스에 선언이 있는지를 본다. """ from __future__ import annotations import ast from pathlib import Path import pytest ROOT = Path(__file__).resolve().parent.parent PYDANTIC_BASES = {"BaseModel", "BaseSettings"} def _has_future_annotations(tree: ast.Module) -> bool: for node in tree.body: if isinstance(node, ast.ImportFrom) and node.module == "__future__": if any(alias.name == "annotations" for alias in node.names): return True return False def _defines_pydantic_model(tree: ast.Module) -> bool: for node in ast.walk(tree): if isinstance(node, ast.ClassDef): for base in node.bases: name = base.id if isinstance(base, ast.Name) else getattr(base, "attr", None) if name in PYDANTIC_BASES: return True return False def _pydantic_modules() -> list[Path]: found = [] for path in sorted((ROOT / "app").rglob("*.py")): tree = ast.parse(path.read_text(encoding="utf-8")) if _defines_pydantic_model(tree): found.append(path) return found def test_pydantic_modules_exist(): """탐지 자체가 망가지면 아래 테스트가 조용히 무력화되므로 먼저 확인한다.""" assert _pydantic_modules(), "Pydantic 모델 모듈을 하나도 못 찾았습니다." @pytest.mark.parametrize( "path", _pydantic_modules(), ids=lambda p: str(p.relative_to(ROOT)) ) def test_pydantic_module_defers_annotations(path: Path): """Pydantic 모델을 정의하는 모듈은 어노테이션 평가를 미뤄야 한다.""" tree = ast.parse(path.read_text(encoding="utf-8")) assert _has_future_annotations(tree), ( f"{path.relative_to(ROOT)} 에 `from __future__ import annotations` 가 " "없습니다. PEP 604(`X | None`)를 쓰면 평가환경(python 3.9)에서 임포트가 " "실패합니다." ) def test_eval_type_backport_pinned_for_py39(): """3.9 에서는 __future__ 만으로 부족하고 pydantic 이 백포트를 요구한다.""" req = (ROOT / "requirements.txt").read_text(encoding="utf-8") assert "eval_type_backport" in req, ( "requirements.txt 에 eval_type_backport 가 없습니다. python 3.9 에서 " "pydantic 이 문자열 어노테이션 'str | None' 을 해석하지 못합니다." ) assert 'python_version < "3.10"' in req, ( "eval_type_backport 는 3.9 전용입니다. 환경 마커를 붙이세요." )