v0.2.0 — Document IR v1 + binding 구조 개선
MINOR release — Phase 2 착수. RAG / LLM 파이프라인이 직접 소비하는 구조화 Document IR v1 (Pydantic V2 + JSON Schema Draft 2020-12) 을 도입. 기존 Document / HwpLoader API 는 변경 없음 (backward-compatible). 상류 edwardkim/rhwp 커밋 핀을 bea635b (main HEAD) 로 갱신 — v0.1.0 의 1636213 이후 upstream 변경은 docs (매뉴얼 현행화 / README 동기화 / 자기검열) 만으로 코드 동작 변화 없음. BMP→PNG 재인코딩 fix (#240) 는 여전히 upstream origin/devel 에만 있으며 본 release pin 에 미포함 — BMP 임베딩 HWP 의 SVG/PDF 렌더링 이슈는 upstream main 머지를 대기.
Added — Document IR v1
Document IR v1 — RAG / LLM 파이프라인이 직접 소비 가능한 구조화 문서 모델. Pydantic V2 기반 공개 타입 + JSON Schema (Draft 2020-12).
rhwp.ir.nodes모듈 —HwpDocument/ParagraphBlock/TableBlock/TableCell/InlineRun/Provenance/UnknownBlock/Furniture/DocumentMetadata/Section(10 노드, 전부frozen=True+extra="forbid").- Callable
Discriminator기반Block태그드 유니온 — 미지kind는UnknownBlock으로 라우팅하여 forward-compat 보장 (v0.3.0 의 새 블록 타입이 v0.2.0 소비자를 깨뜨리지 않음). Document.to_ir() -> HwpDocument+Document.to_ir_json(*, indent=None) -> str— RustOnceCell<Py<PyAny>>lazy 캐시 (unsendable 덕에 lock 불필요).HwpDocument.iter_blocks(*, scope, recurse)— body/furniture/all scope + TableCell 재귀 DFS 순회.- Rust 측 HTML/text 직렬화 — attribute 순서 고정 (rowspan→colspan), HtmlRAG 호환.
- JSON Schema export —
rhwp.ir.schema.export_schema()/load_schema()/SCHEMA_ID/SCHEMA_DIALECT+ in-packagehwp_ir_v1.json+python -m rhwp.ir.schemaCLI. - Discriminator 후처리 —
_harden_unknown_variant()가 UnknownBlock.kind 에not.enum: [known kinds]주입하여 oneOf 검증 정확도 보장. HwpLoader에mode="ir-blocks"추가 — Block 을 LangChainDocument로 매핑 (표는 HTML content + 구조화 메타, 단락은 text + Provenance).TableCell.role="layout"자동 태깅 — 병합된 빈 셀 (구조 유지용 비의미 셀) 을 LLM 이 "레이아웃 요소" 로 인식하도록 시맨틱 구분. 보수적 heuristic: 병합 AND 공백만 있는 셀만layout, 그 외 empty 셀은data유지..github/workflows/publish-schema.yml— GitHub Pages 배포 파이프라인, 불변 경로 정책 (v1 URL 영구) 자동화.- Provenance 단위는 Unicode codepoint — Python
str[i]슬라이싱과 직접 호환 (이모지/SMP CJK 혼용에서도 off-by-one 없음). - 신규 런타임 의존성:
pydantic>=2.5,<3. 테스트 의존성:jsonschema>=4. - 문서:
docs/roadmap/v0.2.0/ir.md(사양),docs/design/v0.2.0/ir-design-research.md(7개 결정 증거),docs/implementation/v0.2.0/stages/stage-{1..5}.md. - 테스트: 165 passed — IR schema/roundtrip/tables/iter/export + LangChain ir-blocks + Rust unit tests (
cargo test5 passed).
Added — Binding 구조 개선 (Python wrapper class + async 진입점)
#[pyclass(unsendable)] 제약 안에서 가능한 최선의 binding 구조와 async 진입점을 정착. 사용자-대면 API 는 전부 보존 — 가산만 있음 (breaking 없음).
- Python wrapper class 패턴: Rust
_Document는#[pyclass(name = "_Document", module = "rhwp._rhwp", unsendable)]thin core 로, Pythonrhwp.Document는__slots__ = ("_inner",)+_from_rustfactory 로 thin core 를 감싸는 wrapper. 모든 메서드는 pass-through. Document.from_bytes(data, *, source_uri=None) -> Document— bytes 기반 생성 classmethod (Rust_Document::from_bytes+py.detach로 GIL 해제). 네트워크 fetch / in-memory archive /aparse내부 경로용.rhwp.aparse(path) -> Documentasync 함수 —aiofiles로 파일 I/O 만 async 처리, 파싱은 event-loop 스레드에서 sync (Document.from_bytes).unsendable제약 상asyncio.to_thread(parse, path)가 panic 하므로 이 경로가 유일하게 안전한 async 진입점.[async]optional extras 추가 —aiofiles>=23. 미설치 시aparse호출 시점에 명시적ImportError(silent fallback 없음).HwpLoader.aload/alazy_loadasync override —rhwp.aparse위에 구축. 공통 yield 로직_yield_documents헬퍼로 sync/async 공유.python/rhwp/_rhwp.pyi신규 — Rust extension (_Document,version,rhwp_core_version) 의 Python 측 타입 stub.
Changed — Phase 2 계획 전환
- 원안의 CLI 도구 (
rhwp바이너리) 는 폐기. 업스트림edwardkim/rhwp의 Rust 바이너리가 같은 이름을 점유하므로 충돌 방지 + Python 고유 가치 (RAG / LangChain 통합) 에 집중. 상세:docs/roadmap/v0.2.0/ir.md§방향 전환 배경. python/rhwp/__init__.pyi에Document.to_ir/to_ir_json타입 힌트 추가.pyproject.toml [tool.maturin] include에python/rhwp/ir/schema/*.json포함 (wheel + sdist).
Changed — Python 지원 범위 상향 (3.10+)
- Python 3.9 지원 드랍 —
requires-python = ">=3.10",pyo3feature 를abi3-py39→abi3-py310으로 전환, CI 매트릭스에서3.9제거. Python 3.9 는 2025-10-31 EOL 이후 보안 패치가 중단된 상태 (> 6 개월 경과). 기존 공개 API 는 전부 호환 — 3.9 사용자는 PyPI 의rhwp-python 0.1.x를 계속 사용 가능. rhwp.ir.schema.load_schema()의Traversable.joinpath()호출을 chain 패턴 (joinpath(a).joinpath(b)) 으로 정리 —*descendants가변 인자 시그니처가 표준 라이브러리에 도입된 시점이 버전별로 달라 typeshed 기준 pyright 가 py3.9/3.10/3.11 에서reportCallIssue를 내는 문제 제거.
Changed — IR 매핑 구조 (Rust → Python 이전)
IR 합성 (HTML 직렬화 / cell role 분류 / inline run 폴백) 을 Rust 에서 Python 으로 이전. IR 진화 시 maturin rebuild 회피 + Python-only 기여자 진입장벽 제거. 외부 API (Document.to_ir(), to_ir_json(), iter_blocks 등) 모두 동일, 캐시 identity (doc.to_ir() is doc.to_ir()) 유지.
src/ir.rs527 → 254 줄. raw 평탄화 + UTF-16↔codepoint 변환만 담당.#[derive(IntoPyObject)]struct 5개 (RawDocument/RawParagraph/RawTable/RawCell/RawCharRun) 로 PyDict 자동 생성.python/rhwp/ir/_mapper.py신규 — raw dict →HwpDocument합성. Rust 에 있던escape_html/cell_role/table_to_html/build_inline_runs폴백 로직 전부 이전.python/rhwp/ir/_raw_types.py신규 — Rust struct 미러TypedDict5개. nested 구조에서BaseModel대비 약 2.5× 빠른 internal raw record (공식 벤치마크 기준).tests/test_ir_mapper.py신규 — Rust 에서 사라진#[cfg(test)]단위 테스트 (escape 순서, cell role 3갈래, inline run 폴백 정책) 의 Python 측 보존.tests/test_from_bytes.py신규 — bytes 기반 생성 검증.tests/test_async.py신규 —aparse+aiofiles경로 검증 + 미설치 시ImportError검증.
Documentation
CLAUDE.mdasync direction 섹션 갱신 —asyncio.to_thread(rhwp.parse, path)가unsendable제약 상 panic 함을 실험으로 확인. forbidden vs supported async 패턴,aparse+aiofiles권장 경로, 향후 upstreamRefCell변경 시 재검토 가능성 안내.
Deferred to v0.3.0+
PictureBlock/FormulaBlock/FootnoteBlock/ListItemBlock/CaptionBlock/TocEntryBlock/FieldBlock— 현재는 미지kind→UnknownBlock폴백.- Furniture 본문 파싱 (머리글/꼬리말/각주 내용).
DocumentMetadata.creation_time/modification_time을datetime으로 교체 (현재str | None).- text/table 정확 interleaving (컨트롤 문자 0x0B 위치 기반).
- LLM strict-mode 완전 호환 —
export_schema(strict=True)옵션. - SchemaStore 카탈로그 등록 / content-addressed alias — GA 후 별도 PR.