Skip to content

v0.2.0 — Document IR v1 + binding 구조 개선

Choose a tag to compare

@DanMeon DanMeon released this 25 Apr 06:14

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 태그드 유니온 — 미지 kindUnknownBlock 으로 라우팅하여 forward-compat 보장 (v0.3.0 의 새 블록 타입이 v0.2.0 소비자를 깨뜨리지 않음).
  • Document.to_ir() -> HwpDocument + Document.to_ir_json(*, indent=None) -> str — Rust OnceCell<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-package hwp_ir_v1.json + python -m rhwp.ir.schema CLI.
  • Discriminator 후처리 — _harden_unknown_variant() 가 UnknownBlock.kind 에 not.enum: [known kinds] 주입하여 oneOf 검증 정확도 보장.
  • HwpLoadermode="ir-blocks" 추가 — Block 을 LangChain Document 로 매핑 (표는 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 test 5 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 로, Python rhwp.Document__slots__ = ("_inner",) + _from_rust factory 로 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) -> Document async 함수 — 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_load async 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__.pyiDocument.to_ir / to_ir_json 타입 힌트 추가.
  • pyproject.toml [tool.maturin] includepython/rhwp/ir/schema/*.json 포함 (wheel + sdist).

Changed — Python 지원 범위 상향 (3.10+)

  • Python 3.9 지원 드랍requires-python = ">=3.10", pyo3 feature 를 abi3-py39abi3-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.rs 527 → 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 미러 TypedDict 5개. 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.md async direction 섹션 갱신 — asyncio.to_thread(rhwp.parse, path)unsendable 제약 상 panic 함을 실험으로 확인. forbidden vs supported async 패턴, aparse + aiofiles 권장 경로, 향후 upstream RefCell 변경 시 재검토 가능성 안내.

Deferred to v0.3.0+

  • PictureBlock / FormulaBlock / FootnoteBlock / ListItemBlock / CaptionBlock / TocEntryBlock / FieldBlock — 현재는 미지 kindUnknownBlock 폴백.
  • Furniture 본문 파싱 (머리글/꼬리말/각주 내용).
  • DocumentMetadata.creation_time / modification_timedatetime 으로 교체 (현재 str | None).
  • text/table 정확 interleaving (컨트롤 문자 0x0B 위치 기반).
  • LLM strict-mode 완전 호환 — export_schema(strict=True) 옵션.
  • SchemaStore 카탈로그 등록 / content-addressed alias — GA 후 별도 PR.