Skip to content

Commit 50078a7

Browse files
committed
add docs
1 parent b545943 commit 50078a7

File tree

4 files changed

+405
-0
lines changed

4 files changed

+405
-0
lines changed

README-CN.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# poptrie
2+
3+
基于 Rust 的高性能 IP 查询库,通过 PyO3 暴露给 Python。
4+
5+
## 环境要求
6+
7+
- Python 3.8+
8+
- Rust 工具链
9+
- `maturin`
10+
11+
## 构建与安装
12+
13+
创建虚拟环境并以开发模式安装:
14+
15+
```bash
16+
python -m venv .venv
17+
source .venv/bin/activate
18+
python -m pip install --upgrade pip maturin
19+
maturin develop --release
20+
```
21+
22+
构建 wheel:
23+
24+
```bash
25+
maturin build --release --out dist
26+
```
27+
28+
## 生成二进制数据
29+
30+
搜索器读取 `build_bin.py` 生成的二进制文件:
31+
32+
```bash
33+
python build_bin.py
34+
```
35+
36+
如果你修改了构建逻辑,需要重新生成 bin 文件(当前节点对齐为 72 字节)。
37+
38+
## Python 使用示例
39+
40+
```python
41+
import socket
42+
from pathlib import Path
43+
44+
import poptrie
45+
46+
47+
bin_path = Path("china_ip.bin")
48+
searcher = poptrie.IpSearcher(str(bin_path))
49+
50+
ip_bytes = socket.inet_pton(socket.AF_INET, "1.0.1.1")
51+
print(searcher.is_china_ip(ip_bytes))
52+
53+
ips = ["1.0.1.1", "8.8.8.8", "240e::1", "2001:db8::"]
54+
print(searcher.batch_check_strings(ips))
55+
56+
v4_ips = ["1.0.1.1", "8.8.8.8", "110.16.0.1", "127.0.0.1"]
57+
packed_v4 = b"".join(socket.inet_pton(socket.AF_INET, ip) for ip in v4_ips)
58+
print(searcher.batch_check_packed(packed_v4, is_v6=False))
59+
```
60+
61+
## API 说明
62+
63+
- `is_china_ip` 接收 IPv4/IPv6 的 bytes(长度 4 或 16)。
64+
- `batch_check_strings` 直接接收字符串列表,顺序稳定。
65+
- `batch_check_packed` 接收扁平化字节流,按固定步长 4/16 解析。
66+
67+
## 测试
68+
69+
```bash
70+
python -m unittest discover tests
71+
```
72+
73+
## 示例
74+
75+
运行内置示例:
76+
77+
```bash
78+
python example.py
79+
python example_production.py
80+
```

README.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# poptrie
2+
3+
Fast IP membership lookup backed by a Rust implementation and exposed to Python via PyO3.
4+
5+
Chinese version: `README-CN.md`.
6+
7+
## Requirements
8+
9+
- Python 3.8+
10+
- Rust toolchain
11+
- `maturin`
12+
13+
## Build and Install
14+
15+
Create a virtual environment and install the module in dev mode:
16+
17+
```bash
18+
python -m venv .venv
19+
source .venv/bin/activate
20+
python -m pip install --upgrade pip maturin
21+
maturin develop --release
22+
```
23+
24+
Build wheels:
25+
26+
```bash
27+
maturin build --release --out dist
28+
```
29+
30+
## Build the Binary Data
31+
32+
The searcher reads a binary file produced by `build_bin.py`.
33+
34+
```bash
35+
python build_bin.py
36+
```
37+
38+
If you change the builder logic, rebuild the bin file to match the 72-byte node alignment.
39+
40+
## Python Usage
41+
42+
```python
43+
import socket
44+
from pathlib import Path
45+
46+
import poptrie
47+
48+
49+
bin_path = Path("china_ip.bin")
50+
searcher = poptrie.IpSearcher(str(bin_path))
51+
52+
ip_bytes = socket.inet_pton(socket.AF_INET, "1.0.1.1")
53+
print(searcher.is_china_ip(ip_bytes))
54+
55+
ips = ["1.0.1.1", "8.8.8.8", "240e::1", "2001:db8::"]
56+
print(searcher.batch_check_strings(ips))
57+
58+
v4_ips = ["1.0.1.1", "8.8.8.8", "110.16.0.1", "127.0.0.1"]
59+
packed_v4 = b"".join(socket.inet_pton(socket.AF_INET, ip) for ip in v4_ips)
60+
print(searcher.batch_check_packed(packed_v4, is_v6=False))
61+
```
62+
63+
## Python Helper Class
64+
65+
If you want a lightweight Python-facing wrapper with docstrings, you can use this class:
66+
67+
```python
68+
from __future__ import annotations
69+
70+
from pathlib import Path
71+
from typing import Iterable
72+
73+
import poptrie
74+
75+
76+
class IpSearcher:
77+
"""Poptrie IP lookup wrapper backed by Rust.
78+
79+
:param bin_path: Path to the binary data file.
80+
"""
81+
82+
def __init__(self, bin_path: str | Path) -> None:
83+
self._searcher = poptrie.IpSearcher(str(bin_path))
84+
85+
def is_china_ip(self, ip_bytes: bytes) -> bool:
86+
"""Check a single IPv4/IPv6 address in packed bytes.
87+
88+
:param ip_bytes: 4-byte IPv4 or 16-byte IPv6.
89+
:return: True if matched, otherwise False.
90+
"""
91+
return self._searcher.is_china_ip(ip_bytes)
92+
93+
def batch_check_strings(self, ips: Iterable[str]) -> list[bool]:
94+
"""Check a list of IP strings, preserving input order.
95+
96+
:param ips: IP strings (IPv4 or IPv6).
97+
:return: Match results aligned to input order.
98+
"""
99+
return self._searcher.batch_check_strings(list(ips))
100+
101+
def batch_check_packed(self, packed_ips: bytes, is_v6: bool) -> list[bool]:
102+
"""Check a packed byte buffer containing IPv4 or IPv6 addresses.
103+
104+
:param packed_ips: Flat buffer of IP bytes.
105+
:param is_v6: True when each IP is 16 bytes, False for 4 bytes.
106+
:return: Match results aligned to the packed order.
107+
"""
108+
return self._searcher.batch_check_packed(packed_ips, is_v6=is_v6)
109+
```
110+
111+
## API Notes
112+
113+
- `is_china_ip` accepts IPv4/IPv6 bytes (`len == 4` or `len == 16`).
114+
- `batch_check_strings` keeps input order and parses strings in Rust.
115+
- `batch_check_packed` expects a flat byte buffer with a fixed stride of 4 or 16.
116+
117+
## Tests
118+
119+
```bash
120+
python -m unittest discover tests
121+
```
122+
123+
## Example
124+
125+
Run the included example:
126+
127+
```bash
128+
python example.py
129+
python example_production.py
130+
```

example.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from __future__ import annotations
2+
3+
import socket
4+
import subprocess
5+
import sys
6+
from pathlib import Path
7+
8+
import poptrie
9+
10+
11+
BIN_PATH = Path("china_ip.bin")
12+
13+
14+
def ensure_bin(path: Path) -> None:
15+
"""Ensure the bin file exists by running build_bin.py when missing.
16+
17+
:param path: Path to the bin file.
18+
:return: None.
19+
"""
20+
if path.exists():
21+
return
22+
subprocess.run([sys.executable, "build_bin.py"], check=True)
23+
24+
25+
def main() -> None:
26+
"""Run a few examples for the Rust-backed searcher.
27+
28+
:return: None.
29+
"""
30+
ensure_bin(BIN_PATH)
31+
searcher = poptrie.IpSearcher(str(BIN_PATH))
32+
33+
ip_str = "1.0.1.1"
34+
ip_bytes = socket.inet_pton(socket.AF_INET, ip_str)
35+
print(f"{ip_str} -> {searcher.is_china_ip(ip_bytes)}")
36+
37+
ips = ["1.0.1.1", "8.8.8.8", "240e::1", "2001:db8::"]
38+
results = searcher.batch_check_strings(ips)
39+
for ip, matched in zip(ips, results):
40+
print(f"{ip} -> {matched}")
41+
42+
v4_ips = ["1.0.1.1", "8.8.8.8", "110.16.0.1", "127.0.0.1"]
43+
packed_v4 = b"".join(socket.inet_pton(socket.AF_INET, ip) for ip in v4_ips)
44+
packed_results = searcher.batch_check_packed(packed_v4, is_v6=False)
45+
for ip, matched in zip(v4_ips, packed_results):
46+
print(f"{ip} -> {matched}")
47+
48+
49+
if __name__ == "__main__":
50+
main()

0 commit comments

Comments
 (0)