Skip to content

Commit 207f575

Browse files
committed
feat: add PyPI package support
1 parent 931b67a commit 207f575

10 files changed

Lines changed: 198 additions & 1 deletion

File tree

.github/workflows/pypi-publish.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Python Package
2+
3+
on:
4+
release:
5+
types: [created]
6+
workflow_dispatch:
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: read
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-python@v5
16+
with:
17+
python-version: "3.x"
18+
- run: python -m pip install build
19+
- run: python -m build
20+
- uses: actions/upload-artifact@v4
21+
with:
22+
name: python-package-distributions
23+
path: dist/
24+
25+
publish-pypi:
26+
runs-on: ubuntu-latest
27+
needs: build
28+
environment:
29+
name: pypi
30+
url: https://pypi.org/p/opencc-data
31+
permissions:
32+
id-token: write
33+
steps:
34+
- uses: actions/download-artifact@v4
35+
with:
36+
name: python-package-distributions
37+
path: dist/
38+
- uses: pypa/gh-action-pypi-publish@release/v1

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.DS_Store
2+
/.venv-build/
3+
/build/
4+
/dist/
5+
/*.egg-info/
6+
__pycache__/

.npmignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
/.github
22
/.gitattributes
33
/.opencc-resource-manifest.json
4+
/.venv-build
5+
/build
6+
/dist
47
/scripts
8+
/*.egg-info
9+
/MANIFEST.in
10+
/opencc_data
11+
/pyproject.toml
12+
/setup.py

MANIFEST.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
include LICENSE
2+
include README.md
3+
include README.zh-TW.md
4+
include package.json
5+
graft data
6+
graft test-data
7+
prune scripts
8+
global-exclude .DS_Store

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,15 @@ This package syncs dictionary `.txt` files and config `.json` files from OpenCC'
2020
Use the config files shipped in `data/config/` as the source of truth for dictionary order and conversion-chain behavior. Config contents can change between OpenCC versions, so consumers should load the matching config file for the package version they depend on instead of hard-coding dictionary lists from this README.
2121

2222
Dictionary text files are shipped in `data/`. Config files reference those dictionaries by file name and preserve OpenCC's stage/group ordering semantics.
23+
24+
Python consumers can install the `opencc-data` package from PyPI and use the `opencc_data` module to locate packaged resources:
25+
26+
```python
27+
import opencc_data
28+
29+
config_file = opencc_data.config_path("s2t.json")
30+
dictionary_file = opencc_data.data_path("STCharacters.txt")
31+
testcases_file = opencc_data.test_data_path("testcases.json")
32+
```
33+
34+
Release versions match across npm and PyPI, such as `1.3.2`. For `next` prereleases, PyPI uses the PEP 440 equivalent of the npm version: `1.3.2-next.20260628` is published to PyPI as `1.3.2.dev20260628`.

README.zh-TW.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,15 @@
2020
請以套件內 `data/config/` 提供的設定檔作為詞典順序與轉換鏈行為的依據。設定內容可能隨 OpenCC 版本改變,因此使用端應載入其依賴版本對應的設定檔,而不是從 README 固定寫死詞典列表。
2121

2222
詞典文字檔位於 `data/`。設定檔會以檔名引用這些詞典,並保留 OpenCC 的 stage/group 排序語意。
23+
24+
Python 使用者可以從 PyPI 安裝 `opencc-data`,並透過 `opencc_data` module 定位套件內資源:
25+
26+
```python
27+
import opencc_data
28+
29+
config_file = opencc_data.config_path("s2t.json")
30+
dictionary_file = opencc_data.data_path("STCharacters.txt")
31+
testcases_file = opencc_data.test_data_path("testcases.json")
32+
```
33+
34+
正式版在 npm 與 PyPI 會使用相同版本號,例如 `1.3.2``next` prerelease 則會在 PyPI 使用對應的 PEP 440 版本:`1.3.2-next.20260628` 會以 `1.3.2.dev20260628` 發佈到 PyPI。

opencc_data/__init__.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from __future__ import annotations
2+
3+
from importlib import resources
4+
from importlib.metadata import PackageNotFoundError, version
5+
from pathlib import PurePosixPath
6+
7+
8+
try:
9+
__version__ = version("opencc-data")
10+
except PackageNotFoundError:
11+
__version__ = "0.0.0"
12+
13+
14+
def data_path(filename: str | None = None):
15+
base = resources.files(__name__).joinpath("data")
16+
return base if filename is None else base.joinpath(PurePosixPath(filename))
17+
18+
19+
def config_path(filename: str | None = None):
20+
base = data_path("config")
21+
return base if filename is None else base.joinpath(PurePosixPath(filename))
22+
23+
24+
def test_data_path(filename: str | None = None):
25+
base = resources.files(__name__).joinpath("test_data")
26+
return base if filename is None else base.joinpath(PurePosixPath(filename))
27+
28+
29+
__all__ = ["__version__", "config_path", "data_path", "test_data_path"]

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "opencc-data",
33
"version": "1.3.2-next.20260628",
4-
"description": "Collection of OpenCC dictionary data",
4+
"description": "Collection of OpenCC dictionary data, configs, and test data",
55
"main": null,
66
"scripts": {
77
"test": "echo \"Error: no test specified\" && exit 1"

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[build-system]
2+
requires = ["setuptools>=77", "wheel"]
3+
build-backend = "setuptools.build_meta"

setup.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from __future__ import annotations
2+
3+
import json
4+
import re
5+
import shutil
6+
from pathlib import Path
7+
8+
from setuptools import setup
9+
from setuptools.command.build_py import build_py as _build_py
10+
11+
12+
ROOT = Path(__file__).parent
13+
14+
15+
def read_package_json() -> dict:
16+
return json.loads((ROOT / "package.json").read_text(encoding="utf-8"))
17+
18+
19+
def python_version(npm_version: str) -> str:
20+
if re.fullmatch(r"\d+\.\d+\.\d+", npm_version):
21+
return npm_version
22+
23+
dated_next = re.fullmatch(r"(\d+\.\d+\.\d+)-next\.(\d{8})", npm_version)
24+
if dated_next:
25+
return f"{dated_next.group(1)}.dev{dated_next.group(2)}"
26+
27+
numbered_next = re.fullmatch(r"(\d+\.\d+\.\d+)-next\.(\d+)", npm_version)
28+
if numbered_next:
29+
return f"{numbered_next.group(1)}.dev{numbered_next.group(2)}"
30+
31+
raise ValueError(
32+
f"{npm_version!r} is not a supported PyPI version. "
33+
"Use a release version like 1.3.2 or a next prerelease like "
34+
"1.3.2-next.20260628."
35+
)
36+
37+
38+
class build_py(_build_py):
39+
def run(self) -> None:
40+
super().run()
41+
package_root = Path(self.build_lib) / "opencc_data"
42+
self.copy_tree(ROOT / "data", package_root / "data")
43+
self.copy_tree(ROOT / "test-data", package_root / "test_data")
44+
45+
@staticmethod
46+
def copy_tree(src: Path, dst: Path) -> None:
47+
if dst.exists():
48+
shutil.rmtree(dst)
49+
shutil.copytree(src, dst)
50+
51+
52+
package_json = read_package_json()
53+
54+
setup(
55+
name="opencc-data",
56+
version=python_version(package_json["version"]),
57+
description="OpenCC dictionary data, configs, and test data",
58+
long_description=(ROOT / "README.md").read_text(encoding="utf-8"),
59+
long_description_content_type="text/markdown",
60+
author="The Ngiox Khyen 2028 Project",
61+
license="Apache-2.0",
62+
url="https://github.com/nk2028/opencc-data",
63+
project_urls={
64+
"Homepage": "https://github.com/nk2028/opencc-data",
65+
"Bug Tracker": "https://github.com/nk2028/opencc-data/issues",
66+
"Source": "https://github.com/nk2028/opencc-data",
67+
},
68+
packages=["opencc_data"],
69+
include_package_data=True,
70+
python_requires=">=3.9",
71+
classifiers=[
72+
"Development Status :: 4 - Beta",
73+
"Intended Audience :: Developers",
74+
"Natural Language :: Chinese (Simplified)",
75+
"Natural Language :: Chinese (Traditional)",
76+
"Programming Language :: Python :: 3",
77+
"Programming Language :: Python :: 3 :: Only",
78+
"Topic :: Text Processing :: Linguistic",
79+
],
80+
cmdclass={"build_py": build_py},
81+
)

0 commit comments

Comments
 (0)