fix: 평가환경(python 3.9)에서 임포트 실패 해결
계획서 p.24 가 No.3/4/7 의 평가환경을 python 3.9 로 못박았는데, 실제로는
성능지표 평가 스크립트 3종이 전부 3.9 에서 임포트조차 되지 않았다.
python:3.9-slim 컨테이너에서 재현·수정·재검증했다.
원인: config.py 와 schemas.py 가 `from __future__ import annotations` 없이
`str | None` (PEP 604) 을 썼다. 3.10 부터의 문법이라 클래스 본문이 실행되는
순간 인터프리터가 평가하며 TypeError 로 죽는다. 이 두 파일을 타고 detector /
extractor / clustering / taxonomy / jobs.store / routes / main 이 연쇄로 깨졌다.
No.7 eval_rouge.py → summarizer → core.config
No.3 eval_metadata_f1.py → extractor → api.schemas
No.4 evaluate_pairs.py → detector → api.schemas
개발환경이 3.14 라 로컬에서는 전혀 드러나지 않았다. 정답셋을 받아 측정하는
시점에 발견했다면 일정이 밀렸을 문제다.
수정은 두 가지가 모두 필요하다. future 임포트만 넣으면 pydantic 이 문자열
'str | None' 을 해석하지 못하고("Unable to evaluate type annotation"),
eval_type_backport 만 넣으면 인터프리터가 먼저 죽는다.
검증: 3.9 에서 eval_rouge.py / eval_metadata_f1.py 가 정상 실행되고 수치가
3.14 와 동일하다(ROUGE-1 recall 0.5778). evaluate_pairs.py 와 app.main 임포트도
통과. 3.14 회귀 없음.
재발 방지로 tests/test_py39_compat.py 를 추가했다. Pydantic 모델을 정의하는
모듈에 future 임포트가 있는지 AST 로 검사하므로 3.14 에서도 회귀를 잡는다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
91aa077898
commit
28c9ad1ee7
@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@ -69,20 +69,37 @@
|
||||
| 4 | 자체 제작 실제 표절 글 vs 비표절 글을 classification 하여 precision 계산 | **Ubuntu 22.04, python 3.9**, 자체 test script (GPU 불필요) |
|
||||
| 7 | 요약 데이터셋 구축 후 n-gram ROUGE **recall** 계산 (수식 분모가 참조 n-gram 수). 다중 참조 전제 | **A100 GPU, python 3.9, pytorch 2.2.2+cu121, transformers 4.39.3** |
|
||||
|
||||
### ⚠️ python 3.9 호환성 위험 (확인 필요)
|
||||
### python 3.9 호환성 — 확인 완료, 수정됨 (2026-08-19)
|
||||
|
||||
평가환경이 **python 3.9 고정**인데, 현재 코드 일부가 3.9에서 임포트 단계에 실패할
|
||||
가능성이 있다.
|
||||
평가환경이 **python 3.9 고정**인데 실제로 **성능지표 평가 스크립트 3종이 전부
|
||||
임포트 불가**였다. Docker `python:3.9-slim` 에서 재현·수정·재검증했다.
|
||||
|
||||
- `app/core/config.py`, `app/api/schemas.py` 가 `from __future__ import annotations`
|
||||
없이 `float | None` (PEP 604) 문법을 쓴다.
|
||||
- 두 파일 모두 Pydantic 모델이라 어노테이션이 **런타임에 평가**된다. python 3.9에는
|
||||
`X | Y` 연산자가 없어 `TypeError` 가 난다.
|
||||
- 현재 개발 환경은 python 3.14 라 이 문제가 드러나지 않는다.
|
||||
**원인**: `app/core/config.py`, `app/api/schemas.py` 가 `from __future__ import
|
||||
annotations` 없이 `str | None` (PEP 604) 을 썼다. 3.10 부터의 문법이라 클래스
|
||||
본문 실행 시점에 인터프리터가 평가하며 `TypeError` 로 죽는다.
|
||||
|
||||
**해야 할 일**: python 3.9 컨테이너에서 임포트 스모크 테스트를 돌려 실제로 깨지는지
|
||||
확인하고, 깨지면 `Optional[float]` 로 바꾸거나 평가 스크립트를 본체와 분리한다.
|
||||
No.4 측정 직전에 발견하면 일정이 밀린다.
|
||||
**파급**: 이 두 파일을 타고 `detector` / `extractor` / `clustering` / `taxonomy` /
|
||||
`jobs.store` / `routes` / `main` 이 연쇄로 깨졌고, 결과적으로
|
||||
|
||||
| 지표 | 평가 스크립트 | 3.9 임포트 경로 |
|
||||
|---|---|---|
|
||||
| No.7 | `eval_rouge.py` | → `summarizer` → `core.config` ❌ |
|
||||
| No.3 | `eval_metadata_f1.py` | → `extractor` → `api.schemas` ❌ |
|
||||
| No.4 | `evaluate_pairs.py` | → `detector` → `api.schemas` ❌ |
|
||||
|
||||
개발환경이 3.14 라 로컬에서는 전혀 드러나지 않았다.
|
||||
|
||||
**수정**: 두 파일에 `from __future__ import annotations` 추가 + `requirements.txt`
|
||||
에 `eval_type_backport>=0.2; python_version < "3.10"` 추가. 둘 다 필요하다 —
|
||||
future 임포트만 넣으면 pydantic 이 문자열 `'str | None'` 을 해석하지 못하고,
|
||||
백포트만 넣으면 인터프리터가 먼저 죽는다.
|
||||
|
||||
**검증**: 3.9 컨테이너에서 `eval_rouge.py` / `eval_metadata_f1.py` 가 정상 실행되고
|
||||
수치가 3.14 와 동일함을 확인했다(ROUGE-1 recall 0.5778). `evaluate_pairs.py` 와
|
||||
`app.main` 임포트도 통과. 3.14 회귀 없음(153 passed).
|
||||
|
||||
**재발 방지**: `tests/test_py39_compat.py` — Pydantic 모델을 정의하는 모듈에
|
||||
future 임포트가 있는지 AST 로 검사한다. 3.14 에서도 회귀를 잡는다.
|
||||
|
||||
## 5. AI 생성 의심도의 위상 — 지표가 아니다
|
||||
|
||||
@ -100,7 +117,7 @@ No.4 측정 직전에 발견하면 일정이 밀린다.
|
||||
1. **사용자 맞춤형 요약 옵션** — 상세도, 강조 내용 (계획서 명시, 현재 없음)
|
||||
2. **Human Feedback Preference Optimization** — 현재 골격만. 선호 라벨 + GPU 필요
|
||||
3. **도메인별 요약 데이터셋 구축** — 컴북스 데이터 수령 후
|
||||
4. **python 3.9 호환성 정리** — 평가환경 대비
|
||||
4. ~~python 3.9 호환성 정리~~ — **완료** (2026-08-19)
|
||||
|
||||
### 측정해야 하는 것 (전부 데이터 대기)
|
||||
1. **No.4 표절 정밀도 97%** — 자체 제작 표절/비표절 글 필요. 정밀도 지표이므로
|
||||
@ -114,5 +131,4 @@ No.4 측정 직전에 발견하면 일정이 밀린다.
|
||||
### 데이터와 무관하게 지금 가능한 것
|
||||
- SW 저작권 등록 1건 (서류 제출)
|
||||
- 사용자 맞춤형 요약 옵션 개발
|
||||
- python 3.9 호환성 확인
|
||||
- 바이칼/컴북스 E2E 통합
|
||||
|
||||
@ -14,3 +14,6 @@ joblib>=1.4
|
||||
scipy>=1.13
|
||||
pypdf>=5.0
|
||||
python-docx>=1.1
|
||||
# 공인인증 평가환경이 python 3.9 고정이라(계획서 p.24) PEP 604 어노테이션을
|
||||
# pydantic 이 해석하려면 이 백포트가 필요하다. 3.10+ 에서는 설치되지 않는다.
|
||||
eval_type_backport>=0.2; python_version < "3.10"
|
||||
|
||||
84
tests/test_py39_compat.py
Normal file
84
tests/test_py39_compat.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""공인인증 평가환경(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 전용입니다. 환경 마커를 붙이세요."
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user