Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,17 @@ jobs:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: pip install -e .[test]
run: pip install -e .[test,typecheck]


- name: Validate gradual typing migration scope
run: python scripts/check_typing_generics_scope.py

- name: Run mypy (gradual scope)
run: mypy

- name: Run pyright (gradual scope)
run: pyright

- name: Run tests and keep summary log
run: |
Expand Down
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@

오타 수정부터 새 파서 추가까지 모든 기여를 환영합니다. HWPX 생태계를 위한
더 나은 도구를 함께 만들어 주셔서 감사합니다!

## 타입 힌트 및 `from __future__ import annotations` 정책

- 이 저장소는 Python 3.10을 최소 지원 버전으로 유지하므로, 타입 힌트는 `list`/`dict`/`tuple` 같은 **내장 제네릭(PEP 585)** 을 우선 사용합니다.
- 신규 파일에서 타입 힌트에 전방 참조(아직 정의되지 않은 클래스 이름)나 `|` 유니온 표기를 사용한다면 `from __future__ import annotations`를 파일 상단에 추가하세요.
- 기존 파일을 수정할 때도 같은 기준을 적용해 파일 단위로 일관성을 맞춥니다. 즉, 해당 파일이 미래 지연 평가가 필요하면 유지하고, 필요하지 않으면 제거합니다.
- 점진 변환 범위(현재: `src/hwpx/document.py`, `src/hwpx/oxml/document.py`)는 CI에서 다음 항목으로 검증합니다.
- `scripts/check_typing_generics_scope.py`: `List`/`Dict`/`Tuple` 별칭 사용 금지 확인
- `mypy`, `pyright`: 지정된 파일 범위 타입 검사

20 changes: 20 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ dev = [
test = [
"pytest>=7.4",
]
typecheck = [
"mypy>=1.10",
"pyright>=1.1.390",
]

[project.urls]
Homepage = "https://github.com/airmang/python-hwpx"
Expand All @@ -63,3 +67,19 @@ include = ["hwpx*"]
pythonpath = ["src"]
addopts = "-ra"
testpaths = ["tests"]


[tool.mypy]
python_version = "3.10"
files = ["src/hwpx/document.py", "src/hwpx/oxml/document.py"]
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["hwpx.document", "hwpx.oxml.document"]
ignore_errors = true

[tool.pyright]
include = ["src/hwpx/document.py", "src/hwpx/oxml/document.py"]
pythonVersion = "3.10"
typeCheckingMode = "off"
reportMissingTypeStubs = false
37 changes: 37 additions & 0 deletions scripts/check_typing_generics_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env python3
"""점진 변환 대상 파일에서 typing 제네릭 별칭 사용을 검사한다."""

from __future__ import annotations

from pathlib import Path
import re
import sys

TARGET_FILES = [
Path("src/hwpx/document.py"),
Path("src/hwpx/oxml/document.py"),
]

FORBIDDEN = ("List[", "Dict[", "Tuple[")


def main() -> int:
has_error = False
for path in TARGET_FILES:
text = path.read_text(encoding="utf-8")
for token in FORBIDDEN:
for match in re.finditer(re.escape(token), text):
line_no = text.count("\n", 0, match.start()) + 1
print(f"{path}:{line_no}: 금지된 typing 별칭 '{token}' 사용 발견")
has_error = True

if has_error:
print("\n점진 변환 범위 검사 실패: list/dict/tuple 내장 제네릭을 사용하세요.")
return 1

print("점진 변환 범위 검사 통과: 대상 파일에서 List/Dict/Tuple 사용이 없습니다.")
return 0


if __name__ == "__main__":
sys.exit(main())
20 changes: 10 additions & 10 deletions src/hwpx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import uuid

from os import PathLike
from typing import Any, BinaryIO, Iterator, List, Tuple
from typing import Any, BinaryIO, Iterator

from lxml import etree

Expand Down Expand Up @@ -193,22 +193,22 @@ def oxml(self) -> HwpxOxmlDocument:
return self._root

@property
def sections(self) -> List[HwpxOxmlSection]:
def sections(self) -> list[HwpxOxmlSection]:
"""Return the sections contained in the document."""
return self._root.sections

@property
def headers(self) -> List[HwpxOxmlHeader]:
def headers(self) -> list[HwpxOxmlHeader]:
"""Return the header parts referenced by the document."""
return self._root.headers

@property
def master_pages(self) -> List[HwpxOxmlMasterPage]:
def master_pages(self) -> list[HwpxOxmlMasterPage]:
"""Return the master-page parts declared in the manifest."""
return self._root.master_pages

@property
def histories(self) -> List[HwpxOxmlHistory]:
def histories(self) -> list[HwpxOxmlHistory]:
"""Return document history parts referenced by the manifest."""
return self._root.histories

Expand Down Expand Up @@ -299,10 +299,10 @@ def track_change_author(
return self._root.track_change_author(author_id_ref)

@property
def memos(self) -> List[HwpxOxmlMemo]:
def memos(self) -> list[HwpxOxmlMemo]:
"""Return all memo entries declared in every section."""

memos: List[HwpxOxmlMemo] = []
memos: list[HwpxOxmlMemo] = []
for section in self._root.sections:
memos.extend(section.memos)
return memos
Expand Down Expand Up @@ -494,7 +494,7 @@ def add_memo_with_anchor(
return memo, target_paragraph, field_value

@property
def paragraphs(self) -> List[HwpxOxmlParagraph]:
def paragraphs(self) -> list[HwpxOxmlParagraph]:
"""Return all paragraphs across every section."""
return self._root.paragraphs

Expand Down Expand Up @@ -540,10 +540,10 @@ def find_runs_by_style(
underline_type: str | None = None,
underline_color: str | None = None,
char_pr_id_ref: str | int | None = None,
) -> List[HwpxOxmlRun]:
) -> list[HwpxOxmlRun]:
"""Return runs matching the requested style criteria."""

matches: List[HwpxOxmlRun] = []
matches: list[HwpxOxmlRun] = []
target_char = str(char_pr_id_ref).strip() if char_pr_id_ref is not None else None

for run in self.iter_runs():
Expand Down
Loading