Skip to content

Commit d1ad836

Browse files
committed
v0.1.2
1 parent 35be2ac commit d1ad836

File tree

7 files changed

+133
-17
lines changed

7 files changed

+133
-17
lines changed

.github/scripts/repack_wheels.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import shutil
2+
import subprocess
3+
import sys
4+
import tempfile
5+
import zipfile
6+
from pathlib import Path
7+
8+
9+
def _read_version(cargo_toml: Path) -> str:
10+
for line in cargo_toml.read_text(encoding="utf-8").splitlines():
11+
if line.strip().startswith("version"):
12+
return line.split("=", 1)[1].strip().strip('"')
13+
raise SystemExit("Version not found in Cargo.toml")
14+
15+
16+
def _load_setup_template(template_path: Path, version: str) -> str:
17+
template = template_path.read_text(encoding="utf-8")
18+
if "__VERSION__" not in template:
19+
raise SystemExit("setup.py template missing __VERSION__ placeholder")
20+
return template.replace("__VERSION__", version)
21+
22+
23+
def main() -> None:
24+
repo_root = Path.cwd()
25+
dist_dir = repo_root / "dist"
26+
cargo_toml = repo_root / "Cargo.toml"
27+
build_src = repo_root / "build_bin.py"
28+
ipsearcher_src = repo_root / "example_production.py"
29+
setup_template = repo_root / ".github" / "scripts" / "setup.py"
30+
31+
if not cargo_toml.exists():
32+
raise SystemExit("Cargo.toml not found")
33+
if not build_src.exists():
34+
raise SystemExit("build_bin.py not found")
35+
if not ipsearcher_src.exists():
36+
raise SystemExit("example_production.py not found")
37+
if not setup_template.exists():
38+
raise SystemExit("setup.py template not found")
39+
40+
version = _read_version(cargo_toml)
41+
setup_py = _load_setup_template(setup_template, version)
42+
43+
wheels = sorted(dist_dir.glob("*.whl"))
44+
if not wheels:
45+
raise SystemExit("No wheels found in dist")
46+
47+
for wheel in wheels:
48+
temp_dir = Path(tempfile.mkdtemp())
49+
with zipfile.ZipFile(wheel, "r") as zf:
50+
zf.extractall(temp_dir)
51+
package_dir = temp_dir / "poptrie"
52+
if not package_dir.exists():
53+
raise SystemExit(f"poptrie package not found in {wheel.name}")
54+
55+
shutil.copy2(build_src, package_dir / "build.py")
56+
shutil.copy2(ipsearcher_src, package_dir / "ipsearcher.py")
57+
(temp_dir / "setup.py").write_text(setup_py, encoding="utf-8")
58+
59+
wheel.unlink()
60+
subprocess.run(
61+
[sys.executable, "setup.py", "bdist_wheel"],
62+
cwd=temp_dir,
63+
check=True,
64+
)
65+
built_wheels = sorted((temp_dir / "dist").glob("*.whl"))
66+
if not built_wheels:
67+
raise SystemExit("bdist_wheel did not produce a wheel")
68+
for built_wheel in built_wheels:
69+
shutil.copy2(built_wheel, dist_dir / built_wheel.name)
70+
shutil.rmtree(temp_dir)
71+
72+
73+
if __name__ == "__main__":
74+
main()

.github/scripts/setup.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from setuptools import find_packages, setup
2+
3+
4+
setup(
5+
name="poptrie",
6+
version="__VERSION__",
7+
description="Fast IP lookup using poptrie",
8+
author="HarmonSir",
9+
author_email="[email protected]",
10+
license="Apache-2.0",
11+
packages=find_packages(),
12+
)

.github/workflows/release.yml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,15 @@ jobs:
8181
echo "PYTHON_EXEC=$(pwd)/.venv/Scripts/python.exe" >> $GITHUB_ENV
8282
8383
- name: Install maturin
84-
run: $PYTHON_EXEC -m pip install --upgrade pip maturin
84+
run: $PYTHON_EXEC -m pip install --upgrade pip maturin setuptools wheel
8585

8686
- name: Build wheels
8787
run: $PYTHON_EXEC -m maturin build --release --out dist
8888

89+
- name: Repack wheels
90+
run: |
91+
$PYTHON_EXEC .github/scripts/repack_wheels.py
92+
8993
- name: Install wheel
9094
run: $PYTHON_EXEC -m pip install dist/*.whl
9195

@@ -129,6 +133,31 @@ jobs:
129133
throw error;
130134
}
131135
}
136+
- name: Remove latest release
137+
uses: actions/github-script@v7
138+
with:
139+
script: |
140+
try {
141+
const release = await github.rest.repos.getReleaseByTag({
142+
owner: context.repo.owner,
143+
repo: context.repo.repo,
144+
tag: "latest",
145+
});
146+
await github.rest.repos.deleteRelease({
147+
owner: context.repo.owner,
148+
repo: context.repo.repo,
149+
release_id: release.data.id,
150+
});
151+
await github.rest.git.deleteRef({
152+
owner: context.repo.owner,
153+
repo: context.repo.repo,
154+
ref: "tags/latest",
155+
});
156+
} catch (error) {
157+
if (error.status !== 404) {
158+
throw error;
159+
}
160+
}
132161
133162
- name: Download wheels
134163
uses: actions/download-artifact@v4

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "poptrie"
3-
version = "0.1.1"
3+
version = "0.1.2"
44
edition = "2021"
55

66
[lib]

build_bin.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,12 @@ def save(self, output_path):
125125
print(f"构建完成!压缩后大小: {len(final_data) / 1024:.2f} KB")
126126

127127

128-
# --- 使用方式 ---
129-
builder = BinBuilder()
130-
# 这里放入你收集的中国 IP CIDR 列表
131-
china_cidrs = ["1.0.1.0/24", "110.16.0.0/12", "240e::/18"]
132-
for c in china_cidrs:
133-
builder.add_cidr(c)
134-
135-
builder.save("china_ip.bin")
128+
if __name__ == '__main__':
129+
# --- 使用方式 ---
130+
builder = BinBuilder()
131+
# 这里放入你收集的中国 IP CIDR 列表
132+
china_cidrs = ["1.0.1.0/24", "110.16.0.0/12", "240e::/18"]
133+
for c in china_cidrs:
134+
builder.add_cidr(c)
135+
136+
builder.save("china_ip.bin")

example_production.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ def __init__(self, bin_path: str | Path) -> None:
2424
"""
2525
self._searcher = poptrie.IpSearcher(str(bin_path))
2626

27-
def is_china_ip(self, ip_bytes: bytes) -> bool:
27+
def is_matched_ip(self, ip_bytes: bytes) -> bool:
2828
"""Check a single IPv4/IPv6 address in packed bytes.
2929
查询单个 IP(IPv4/IPv6)对应的 bytes。
3030
3131
:param ip_bytes: 4-byte IPv4 or 16-byte IPv6.
3232
:return: True if matched, otherwise False.
3333
"""
34-
return self._searcher.is_china_ip(ip_bytes)
34+
return self._searcher.is_matched_ip(ip_bytes)
3535

3636
def batch_check_strings(self, ips: Iterable[str]) -> list[bool]:
3737
"""Check a list of IP strings, preserving input order.
@@ -131,7 +131,7 @@ def main() -> None:
131131
searcher = IpSearcher(bin_path)
132132

133133
ip_bytes = socket.inet_pton(socket.AF_INET, "1.0.1.1")
134-
print(searcher.is_china_ip(ip_bytes))
134+
print(searcher.is_matched_ip(ip_bytes))
135135

136136
ips = ["1.0.1.1", "8.8.8.8", "240e::1", "2001:db8::"]
137137
print(searcher.check_ips(ips))

src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ impl IpSearcher {
2424
}
2525

2626
/// 核心查询逻辑:支持 IPv4 (4字节) 和 IPv6 (16字节)
27-
fn is_china_ip(&self, ip_bytes: &[u8]) -> bool {
27+
fn is_matched_ip(&self, ip_bytes: &[u8]) -> bool {
2828
let mut cursor: usize = 0;
2929

3030
for &byte in ip_bytes {
@@ -63,16 +63,16 @@ impl IpSearcher {
6363
// 这是极致性能的关键:内存完全连续,没有 Python 对象开销
6464
packed_ips
6565
.chunks_exact(ip_stride)
66-
.map(|ip_chunk| self.is_china_ip(ip_chunk))
66+
.map(|ip_chunk| self.is_matched_ip(ip_chunk))
6767
.collect()
6868
}
6969

7070
fn batch_check_strings(&self, py: Python<'_>, ips: Vec<String>) -> Vec<bool> {
7171
py.allow_threads(|| {
7272
ips.into_par_iter()
7373
.map(|ip_str| match ip_str.parse::<IpAddr>() {
74-
Ok(IpAddr::V4(v4)) => self.is_china_ip(&v4.octets()),
75-
Ok(IpAddr::V6(v6)) => self.is_china_ip(&v6.octets()),
74+
Ok(IpAddr::V4(v4)) => self.is_matched_ip(&v4.octets()),
75+
Ok(IpAddr::V6(v6)) => self.is_matched_ip(&v6.octets()),
7676
Err(_) => false,
7777
})
7878
.collect()

0 commit comments

Comments
 (0)