Skip to content

Commit d171cf5

Browse files
authored
Merge branch 'main' into main
2 parents 83c4911 + c922c4a commit d171cf5

42 files changed

Lines changed: 1428 additions & 539 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
- "[0-9]*"
8+
9+
permissions:
10+
contents: write
11+
id-token: write
12+
13+
jobs:
14+
release:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v4
20+
21+
- name: Set up Python
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: "3.12"
25+
26+
- name: Validate tag/version consistency
27+
shell: bash
28+
run: |
29+
set -euo pipefail
30+
31+
TAG_NAME="${GITHUB_REF_NAME}"
32+
TAG_VERSION="${TAG_NAME#v}"
33+
34+
PYPROJECT_VERSION=$(python - <<'PY'
35+
import tomllib
36+
from pathlib import Path
37+
38+
data = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
39+
print(data["project"]["version"])
40+
PY
41+
)
42+
43+
CHANGELOG_VERSION=$(python - <<'PY'
44+
import re
45+
from pathlib import Path
46+
47+
changelog = Path("CHANGELOG.md").read_text(encoding="utf-8")
48+
match = re.search(r"^## \[([^\]]+)\]", changelog, re.MULTILINE)
49+
if not match:
50+
raise SystemExit("CHANGELOG.md에서 버전 섹션(## [x.y.z])을 찾지 못했습니다.")
51+
print(match.group(1).strip())
52+
PY
53+
)
54+
55+
echo "Tag version: ${TAG_VERSION}"
56+
echo "pyproject version: ${PYPROJECT_VERSION}"
57+
echo "changelog version: ${CHANGELOG_VERSION}"
58+
59+
if [[ "${TAG_VERSION}" != "${PYPROJECT_VERSION}" ]]; then
60+
echo "태그 버전(${TAG_VERSION})과 pyproject 버전(${PYPROJECT_VERSION})이 일치하지 않습니다." >&2
61+
exit 1
62+
fi
63+
64+
if [[ "${TAG_VERSION}" != "${CHANGELOG_VERSION}" ]]; then
65+
echo "태그 버전(${TAG_VERSION})과 changelog 최신 버전(${CHANGELOG_VERSION})이 일치하지 않습니다." >&2
66+
exit 1
67+
fi
68+
69+
- name: Extract latest changelog section for release notes
70+
run: |
71+
python - <<'PY'
72+
from pathlib import Path
73+
74+
lines = Path("CHANGELOG.md").read_text(encoding="utf-8").splitlines()
75+
start = next((i for i, line in enumerate(lines) if line.startswith("## [")), None)
76+
if start is None:
77+
raise SystemExit("CHANGELOG.md에서 릴리스 섹션을 찾지 못했습니다.")
78+
79+
end = next(
80+
(i for i in range(start + 1, len(lines)) if lines[i].startswith("## [")),
81+
len(lines),
82+
)
83+
84+
section = "\n".join(lines[start:end]).strip() + "\n"
85+
Path("release_notes.md").write_text(section, encoding="utf-8")
86+
PY
87+
88+
- name: Build distributions (migrated from scripts/build-and-publish.sh)
89+
run: |
90+
python -m pip install --upgrade build twine
91+
rm -rf dist build
92+
python -m build
93+
twine check dist/*
94+
95+
- name: Publish package to PyPI
96+
uses: pypa/gh-action-pypi-publish@release/v1
97+
98+
- name: Create GitHub Release
99+
uses: softprops/action-gh-release@v2
100+
with:
101+
body_path: release_notes.md
102+
files: dist/*

.github/workflows/tests.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
test:
9+
runs-on: ubuntu-latest
10+
strategy:
11+
fail-fast: true
12+
matrix:
13+
python-version: ["3.10", "3.11", "3.12"]
14+
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Python ${{ matrix.python-version }}
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: ${{ matrix.python-version }}
23+
24+
- name: Install dependencies
25+
run: pip install -e .[test,typecheck]
26+
27+
28+
- name: Validate gradual typing migration scope
29+
run: python scripts/check_typing_generics_scope.py
30+
31+
- name: Run mypy (gradual scope)
32+
run: mypy
33+
34+
- name: Run pyright (gradual scope)
35+
run: pyright
36+
37+
- name: Run tests and keep summary log
38+
env:
39+
# 단계적 도입: 기준을 50 -> 60 -> 70으로 상향합니다.
40+
COVERAGE_FAIL_UNDER: 50
41+
run: |
42+
pytest -v -ra --cov=hwpx --cov-report=term-missing --cov-fail-under=${COVERAGE_FAIL_UNDER} 2>&1 | tee pytest-summary.log
43+
44+
- name: Upload pytest summary log
45+
if: always()
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: pytest-summary-${{ matrix.python-version }}
49+
path: pytest-summary.log

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
모든 중요한 변경 사항은 이 문서에 기록됩니다. 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)[Semantic Versioning](https://semver.org/lang/ko/)을 따릅니다.
44

5+
## [1.9] - 2026-02-18
6+
### 변경
7+
- `hwpx.__version__` 하드코딩 값을 제거하고 `importlib.metadata.version("python-hwpx")` 기반으로 노출하도록 정리했습니다.
8+
- editable/로컬 소스 실행처럼 배포 메타데이터가 없는 환경에서도 동작하도록 `PackageNotFoundError` fallback(`0+unknown`)을 추가했습니다.
9+
510
## [0.1.0] - 2025-09-17
611
### 추가
712
- `hwpx.opc.package.HwpxPackage``hwpx.document.HwpxDocument`를 포함한 핵심 API를 공개했습니다.

CONTRIBUTING.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,13 @@
3333

3434
오타 수정부터 새 파서 추가까지 모든 기여를 환영합니다. HWPX 생태계를 위한
3535
더 나은 도구를 함께 만들어 주셔서 감사합니다!
36+
37+
## 타입 힌트 및 `from __future__ import annotations` 정책
38+
39+
- 이 저장소는 Python 3.10을 최소 지원 버전으로 유지하므로, 타입 힌트는 `list`/`dict`/`tuple` 같은 **내장 제네릭(PEP 585)** 을 우선 사용합니다.
40+
- 신규 파일에서 타입 힌트에 전방 참조(아직 정의되지 않은 클래스 이름)나 `|` 유니온 표기를 사용한다면 `from __future__ import annotations`를 파일 상단에 추가하세요.
41+
- 기존 파일을 수정할 때도 같은 기준을 적용해 파일 단위로 일관성을 맞춥니다. 즉, 해당 파일이 미래 지연 평가가 필요하면 유지하고, 필요하지 않으면 제거합니다.
42+
- 점진 변환 범위(현재: `src/hwpx/document.py`, `src/hwpx/oxml/document.py`)는 CI에서 다음 항목으로 검증합니다.
43+
- `scripts/check_typing_generics_scope.py`: `List`/`Dict`/`Tuple` 별칭 사용 금지 확인
44+
- `mypy`, `pyright`: 지정된 파일 범위 타입 검사
45+

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Sphinx 문서는 `docs/` 아래에 있으며, `python -m pip install -r docs/req
3838
```python
3939
from io import BytesIO
4040

41-
from hwpx.document import HwpxDocument
41+
from hwpx import HwpxDocument
4242
from hwpx.templates import blank_document_bytes
4343

4444
# 1) 빈 템플릿으로 문서 열기

docs/examples.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
```python
88
from io import BytesIO
99

10-
from hwpx.document import HwpxDocument
10+
from hwpx import HwpxDocument
1111
from hwpx.templates import blank_document_bytes
1212

1313
document = HwpxDocument.open(BytesIO(blank_document_bytes()))
@@ -18,7 +18,7 @@ document.save("playground.hwpx")
1818
## 1. 보고서 템플릿에 표와 개체 추가하기
1919

2020
```python
21-
from hwpx.document import HwpxDocument
21+
from hwpx import HwpxDocument
2222

2323
document = HwpxDocument.open("examples/FormattingShowcase.hwpx")
2424
section = document.sections[-1]

docs/faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ $env:PYTHONPATH = "$(Get-Location)\src;" + $env:PYTHONPATH
4141

4242
```python
4343
from io import BytesIO
44-
from hwpx.document import HwpxDocument
44+
from hwpx import HwpxDocument
4545

4646
with open("sample.hwpx", "rb") as fp:
4747
data = fp.read()

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ api_reference
4141
자세한 단계별 설명은 {doc}`quickstart`에서 확인할 수 있습니다.
4242

4343
```python
44-
from hwpx.document import HwpxDocument
44+
from hwpx import HwpxDocument
4545

4646
# 1) 문서 열기
4747
document = HwpxDocument.open("sample.hwpx")

docs/installation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ python -m pip install python-hwpx
2323
python - <<'PY'
2424
from io import BytesIO
2525
26-
from hwpx.document import HwpxDocument
26+
from hwpx import HwpxDocument
2727
from hwpx.templates import blank_document_bytes
2828
2929
doc = HwpxDocument.open(BytesIO(blank_document_bytes()))
@@ -57,7 +57,7 @@ PY
5757

5858
```bash
5959
python - <<'PY'
60-
from hwpx.opc.package import HwpxPackage
60+
from hwpx import HwpxPackage
6161
print("Package class loaded:", hasattr(HwpxPackage, "open"))
6262
PY
6363
```

docs/quickstart.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Jupyter Notebook이나 IPython에서 실험하면 XML 구조를 꾸준히 탐색
1919
```python
2020
from io import BytesIO
2121

22-
from hwpx.document import HwpxDocument
22+
from hwpx import HwpxDocument
2323
from hwpx.templates import blank_document_bytes
2424

2525
source = BytesIO(blank_document_bytes())
@@ -30,6 +30,16 @@ print("첫 번째 문단 텍스트:", document.paragraphs[0].text)
3030

3131
`HwpxDocument.open()`은 파일 경로, 바이트, 파일 객체 등 다양한 입력을 받아 문서를 로드합니다. 반환된 `document` 객체는 섹션, 문단, 표 등 주요 구성 요소에 바로 접근할 수 있는 고수준 API를 제공합니다.
3232

33+
컨텍스트 매니저(`with`)와 함께 사용하면 블록 종료 시점(정상/예외 모두)에도 내부 자원 정리가 자동으로 수행됩니다.
34+
35+
```python
36+
from hwpx import HwpxDocument
37+
38+
with HwpxDocument.open("input/sample.hwpx") as document:
39+
document.add_paragraph("with 블록 안에서 안전하게 편집")
40+
document.save("output/sample-updated.hwpx")
41+
```
42+
3343
## 2. 새 문단 추가하기
3444

3545
```python

0 commit comments

Comments
 (0)