maxminddb-rust 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/.github/workflows/codeql.yml +3 -3
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/.github/workflows/lint.yml +7 -7
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/.github/workflows/release.yml +7 -7
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/.github/workflows/test.yml +3 -3
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/.github/workflows/zizmor.yml +1 -1
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/CHANGELOG.md +16 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/Cargo.lock +32 -25
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/Cargo.toml +3 -3
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/PKG-INFO +29 -3
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/README.md +28 -2
- maxminddb_rust-0.3.0/examples/benchmark_path.py +56 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/maxminddb_rust.pyi +27 -1
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/pyproject.toml +1 -1
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/src/lib.rs +124 -1
- maxminddb_rust-0.3.0/tests/path_test.py +91 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/uv.lock +1 -1
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/.githooks/pre-commit +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/.github/dependabot.yml +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/.gitignore +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/.gitmodules +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/CONTRIBUTING.md +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/LICENSE +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/MIGRATION.md +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/dev-bin/release.sh +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/examples/basic_usage.py +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/examples/batch_processing.py +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/examples/benchmark.py +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/examples/benchmark_batch.py +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0/examples}/benchmark_comprehensive.py +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/examples/context_manager.py +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/examples/iterator_demo.py +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/precious.toml +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/py.typed +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/tests/__init__.py +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/tests/maxmind/README.md +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/tests/maxmind/UPSTREAM_LICENSE +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/tests/maxmind/__init__.py +0 -0
- {maxminddb_rust-0.2.0 → maxminddb_rust-0.3.0}/tests/maxmind/reader_test.py +0 -0
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
|
|
22
22
|
steps:
|
|
23
23
|
- name: Checkout repository
|
|
24
|
-
uses: actions/checkout@
|
|
24
|
+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
25
25
|
with:
|
|
26
26
|
fetch-depth: 2
|
|
27
27
|
persist-credentials: false
|
|
@@ -31,12 +31,12 @@ jobs:
|
|
|
31
31
|
run: git checkout HEAD^2
|
|
32
32
|
|
|
33
33
|
- name: Set up Python
|
|
34
|
-
uses: actions/setup-python@
|
|
34
|
+
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
|
35
35
|
with:
|
|
36
36
|
python-version: "3.x"
|
|
37
37
|
|
|
38
38
|
- name: Install Rust
|
|
39
|
-
uses: dtolnay/rust-toolchain@
|
|
39
|
+
uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
|
|
40
40
|
with:
|
|
41
41
|
toolchain: stable
|
|
42
42
|
|
|
@@ -13,12 +13,12 @@ jobs:
|
|
|
13
13
|
name: Clippy
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
16
|
+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
17
17
|
with:
|
|
18
18
|
persist-credentials: false
|
|
19
19
|
|
|
20
20
|
- name: Install Rust
|
|
21
|
-
uses: dtolnay/rust-toolchain@
|
|
21
|
+
uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
|
|
22
22
|
with:
|
|
23
23
|
toolchain: stable
|
|
24
24
|
components: clippy
|
|
@@ -30,12 +30,12 @@ jobs:
|
|
|
30
30
|
name: Rustfmt
|
|
31
31
|
runs-on: ubuntu-latest
|
|
32
32
|
steps:
|
|
33
|
-
- uses: actions/checkout@
|
|
33
|
+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
34
34
|
with:
|
|
35
35
|
persist-credentials: false
|
|
36
36
|
|
|
37
37
|
- name: Install Rust
|
|
38
|
-
uses: dtolnay/rust-toolchain@
|
|
38
|
+
uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
|
|
39
39
|
with:
|
|
40
40
|
toolchain: stable
|
|
41
41
|
components: rustfmt
|
|
@@ -47,7 +47,7 @@ jobs:
|
|
|
47
47
|
name: Ruff
|
|
48
48
|
runs-on: ubuntu-latest
|
|
49
49
|
steps:
|
|
50
|
-
- uses: actions/checkout@
|
|
50
|
+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
51
51
|
with:
|
|
52
52
|
persist-credentials: false
|
|
53
53
|
|
|
@@ -64,12 +64,12 @@ jobs:
|
|
|
64
64
|
name: Prettier
|
|
65
65
|
runs-on: ubuntu-latest
|
|
66
66
|
steps:
|
|
67
|
-
- uses: actions/checkout@
|
|
67
|
+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
68
68
|
with:
|
|
69
69
|
persist-credentials: false
|
|
70
70
|
|
|
71
71
|
- name: Setup Node.js
|
|
72
|
-
uses: actions/setup-node@
|
|
72
|
+
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
|
73
73
|
with:
|
|
74
74
|
node-version: "20"
|
|
75
75
|
|
|
@@ -37,12 +37,12 @@ jobs:
|
|
|
37
37
|
python_versions: "3.11 3.12 3.13 3.14"
|
|
38
38
|
|
|
39
39
|
steps:
|
|
40
|
-
- uses: actions/checkout@
|
|
40
|
+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
41
41
|
with:
|
|
42
42
|
persist-credentials: false
|
|
43
43
|
|
|
44
44
|
- name: Set up Python
|
|
45
|
-
uses: actions/setup-python@
|
|
45
|
+
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
|
46
46
|
with:
|
|
47
47
|
python-version: "3.x"
|
|
48
48
|
|
|
@@ -51,7 +51,7 @@ jobs:
|
|
|
51
51
|
|
|
52
52
|
- name: Set up Zig
|
|
53
53
|
if: runner.os == 'Windows'
|
|
54
|
-
uses: mlugg/setup-zig@
|
|
54
|
+
uses: mlugg/setup-zig@fa65c4058643678a4e4a9a60513944a7d8d35440 # v2.1.0
|
|
55
55
|
|
|
56
56
|
- name: Set up QEMU
|
|
57
57
|
if: runner.os == 'Linux' && matrix.target == 'aarch64'
|
|
@@ -68,7 +68,7 @@ jobs:
|
|
|
68
68
|
manylinux: auto
|
|
69
69
|
|
|
70
70
|
- name: Upload wheels
|
|
71
|
-
uses: actions/upload-artifact@
|
|
71
|
+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
|
72
72
|
with:
|
|
73
73
|
name: maxminddb-rust-whl-${{ matrix.os }}-${{ matrix.target }}
|
|
74
74
|
path: dist
|
|
@@ -77,7 +77,7 @@ jobs:
|
|
|
77
77
|
name: Build source distribution
|
|
78
78
|
runs-on: ubuntu-latest
|
|
79
79
|
steps:
|
|
80
|
-
- uses: actions/checkout@
|
|
80
|
+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
81
81
|
with:
|
|
82
82
|
persist-credentials: false
|
|
83
83
|
|
|
@@ -88,7 +88,7 @@ jobs:
|
|
|
88
88
|
args: --out dist
|
|
89
89
|
|
|
90
90
|
- name: Upload sdist
|
|
91
|
-
uses: actions/upload-artifact@
|
|
91
|
+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
|
92
92
|
with:
|
|
93
93
|
name: maxminddb-rust-sdist
|
|
94
94
|
path: dist
|
|
@@ -103,7 +103,7 @@ jobs:
|
|
|
103
103
|
id-token: write
|
|
104
104
|
steps:
|
|
105
105
|
- name: Download artifacts
|
|
106
|
-
uses: actions/download-artifact@
|
|
106
|
+
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
|
107
107
|
with:
|
|
108
108
|
pattern: maxminddb-rust-*
|
|
109
109
|
path: dist
|
|
@@ -21,18 +21,18 @@ jobs:
|
|
|
21
21
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
22
22
|
|
|
23
23
|
steps:
|
|
24
|
-
- uses: actions/checkout@
|
|
24
|
+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
25
25
|
with:
|
|
26
26
|
persist-credentials: false
|
|
27
27
|
submodules: recursive
|
|
28
28
|
|
|
29
29
|
- name: Set up Python ${{ matrix.python-version }}
|
|
30
|
-
uses: actions/setup-python@
|
|
30
|
+
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
|
31
31
|
with:
|
|
32
32
|
python-version: ${{ matrix.python-version }}
|
|
33
33
|
|
|
34
34
|
- name: Install Rust
|
|
35
|
-
uses: dtolnay/rust-toolchain@
|
|
35
|
+
uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
|
|
36
36
|
with:
|
|
37
37
|
toolchain: stable
|
|
38
38
|
|
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
security-events: write
|
|
18
18
|
steps:
|
|
19
19
|
- name: Checkout repository
|
|
20
|
-
uses: actions/checkout@
|
|
20
|
+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
21
21
|
with:
|
|
22
22
|
persist-credentials: false
|
|
23
23
|
|
|
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] - 2025-12-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added `get_path()` method to `Reader` class. This allows high-performance
|
|
13
|
+
retrieval of specific fields (e.g.,
|
|
14
|
+
`reader.get_path(ip, ('country', 'iso_code'))`) without decoding the entire
|
|
15
|
+
record. This can be over 6x faster than `get()` when only partial data is
|
|
16
|
+
needed.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Upgraded maxminddb crate from 0.27.0 to 0.27.1.
|
|
21
|
+
- Enabled `simdutf8` feature in `maxminddb` crate. This improves lookup
|
|
22
|
+
performance by using SIMD instructions for faster UTF-8 validation.
|
|
23
|
+
|
|
8
24
|
## [0.2.0] - 2025-11-28
|
|
9
25
|
|
|
10
26
|
### Changed
|
|
@@ -10,9 +10,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
|
10
10
|
|
|
11
11
|
[[package]]
|
|
12
12
|
name = "cc"
|
|
13
|
-
version = "1.2.
|
|
13
|
+
version = "1.2.49"
|
|
14
14
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
-
checksum = "
|
|
15
|
+
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
|
|
16
16
|
dependencies = [
|
|
17
17
|
"find-msvc-tools",
|
|
18
18
|
"shlex",
|
|
@@ -20,9 +20,9 @@ dependencies = [
|
|
|
20
20
|
|
|
21
21
|
[[package]]
|
|
22
22
|
name = "find-msvc-tools"
|
|
23
|
-
version = "0.1.
|
|
23
|
+
version = "0.1.5"
|
|
24
24
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
25
|
-
checksum = "
|
|
25
|
+
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
|
26
26
|
|
|
27
27
|
[[package]]
|
|
28
28
|
name = "heck"
|
|
@@ -47,32 +47,33 @@ checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763"
|
|
|
47
47
|
|
|
48
48
|
[[package]]
|
|
49
49
|
name = "libc"
|
|
50
|
-
version = "0.2.
|
|
50
|
+
version = "0.2.178"
|
|
51
51
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
52
|
-
checksum = "
|
|
52
|
+
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
|
53
53
|
|
|
54
54
|
[[package]]
|
|
55
55
|
name = "log"
|
|
56
|
-
version = "0.4.
|
|
56
|
+
version = "0.4.29"
|
|
57
57
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
58
|
-
checksum = "
|
|
58
|
+
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|
59
59
|
|
|
60
60
|
[[package]]
|
|
61
61
|
name = "maxminddb"
|
|
62
|
-
version = "0.27.
|
|
62
|
+
version = "0.27.1"
|
|
63
63
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
64
|
-
checksum = "
|
|
64
|
+
checksum = "99681a80368084e68fff1a4ec657b09ae6a04f1107762ec6346a82b8cc19d8eb"
|
|
65
65
|
dependencies = [
|
|
66
66
|
"ipnetwork",
|
|
67
67
|
"log",
|
|
68
68
|
"memchr",
|
|
69
69
|
"serde",
|
|
70
|
+
"simdutf8",
|
|
70
71
|
"thiserror",
|
|
71
72
|
]
|
|
72
73
|
|
|
73
74
|
[[package]]
|
|
74
75
|
name = "maxminddb-rust"
|
|
75
|
-
version = "0.
|
|
76
|
+
version = "0.3.0"
|
|
76
77
|
dependencies = [
|
|
77
78
|
"ipnetwork",
|
|
78
79
|
"maxminddb",
|
|
@@ -128,9 +129,9 @@ dependencies = [
|
|
|
128
129
|
|
|
129
130
|
[[package]]
|
|
130
131
|
name = "pyo3"
|
|
131
|
-
version = "0.27.
|
|
132
|
+
version = "0.27.2"
|
|
132
133
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
133
|
-
checksum = "
|
|
134
|
+
checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d"
|
|
134
135
|
dependencies = [
|
|
135
136
|
"indoc",
|
|
136
137
|
"libc",
|
|
@@ -145,9 +146,9 @@ dependencies = [
|
|
|
145
146
|
|
|
146
147
|
[[package]]
|
|
147
148
|
name = "pyo3-build-config"
|
|
148
|
-
version = "0.27.
|
|
149
|
+
version = "0.27.2"
|
|
149
150
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
150
|
-
checksum = "
|
|
151
|
+
checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6"
|
|
151
152
|
dependencies = [
|
|
152
153
|
"python3-dll-a",
|
|
153
154
|
"target-lexicon",
|
|
@@ -155,9 +156,9 @@ dependencies = [
|
|
|
155
156
|
|
|
156
157
|
[[package]]
|
|
157
158
|
name = "pyo3-ffi"
|
|
158
|
-
version = "0.27.
|
|
159
|
+
version = "0.27.2"
|
|
159
160
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
160
|
-
checksum = "
|
|
161
|
+
checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089"
|
|
161
162
|
dependencies = [
|
|
162
163
|
"libc",
|
|
163
164
|
"pyo3-build-config",
|
|
@@ -165,9 +166,9 @@ dependencies = [
|
|
|
165
166
|
|
|
166
167
|
[[package]]
|
|
167
168
|
name = "pyo3-macros"
|
|
168
|
-
version = "0.27.
|
|
169
|
+
version = "0.27.2"
|
|
169
170
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
170
|
-
checksum = "
|
|
171
|
+
checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02"
|
|
171
172
|
dependencies = [
|
|
172
173
|
"proc-macro2",
|
|
173
174
|
"pyo3-macros-backend",
|
|
@@ -177,9 +178,9 @@ dependencies = [
|
|
|
177
178
|
|
|
178
179
|
[[package]]
|
|
179
180
|
name = "pyo3-macros-backend"
|
|
180
|
-
version = "0.27.
|
|
181
|
+
version = "0.27.2"
|
|
181
182
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
182
|
-
checksum = "
|
|
183
|
+
checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9"
|
|
183
184
|
dependencies = [
|
|
184
185
|
"heck",
|
|
185
186
|
"proc-macro2",
|
|
@@ -199,9 +200,9 @@ dependencies = [
|
|
|
199
200
|
|
|
200
201
|
[[package]]
|
|
201
202
|
name = "quote"
|
|
202
|
-
version = "1.0.
|
|
203
|
+
version = "1.0.42"
|
|
203
204
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
204
|
-
checksum = "
|
|
205
|
+
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
|
205
206
|
dependencies = [
|
|
206
207
|
"proc-macro2",
|
|
207
208
|
]
|
|
@@ -248,11 +249,17 @@ version = "1.3.0"
|
|
|
248
249
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
249
250
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|
250
251
|
|
|
252
|
+
[[package]]
|
|
253
|
+
name = "simdutf8"
|
|
254
|
+
version = "0.1.5"
|
|
255
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
256
|
+
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
|
257
|
+
|
|
251
258
|
[[package]]
|
|
252
259
|
name = "syn"
|
|
253
|
-
version = "2.0.
|
|
260
|
+
version = "2.0.111"
|
|
254
261
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
255
|
-
checksum = "
|
|
262
|
+
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
|
256
263
|
dependencies = [
|
|
257
264
|
"proc-macro2",
|
|
258
265
|
"quote",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "maxminddb-rust"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["Gregory Oschwald <oschwald@gmail.com>"]
|
|
6
6
|
description = "Rust-based Python binding for MaxMind DB - high-performance alternative to maxminddb"
|
|
@@ -14,9 +14,9 @@ crate-type = ["cdylib"]
|
|
|
14
14
|
|
|
15
15
|
[dependencies]
|
|
16
16
|
ipnetwork = "0.21"
|
|
17
|
-
maxminddb = "0.27.0"
|
|
17
|
+
maxminddb = { version = "0.27.0", features = ["simdutf8"] }
|
|
18
18
|
memmap2 = "0.9"
|
|
19
|
-
pyo3 = { version = "0.27.
|
|
19
|
+
pyo3 = { version = "0.27.2", features = ["extension-module", "generate-import-lib"] }
|
|
20
20
|
serde = "1.0"
|
|
21
21
|
|
|
22
22
|
[features]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maxminddb-rust
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: ISC License (ISCL)
|
|
@@ -72,6 +72,9 @@ This package provides **100% API compatibility** with the official [`maxminddb`]
|
|
|
72
72
|
**Extensions (not in original):**
|
|
73
73
|
|
|
74
74
|
- ⭐ `get_many()` - Batch lookup method for processing multiple IPs efficiently
|
|
75
|
+
- ⭐ `get_path()` - Efficiently retrieve a specific field from a record (e.g.,
|
|
76
|
+
`('country', 'iso_code')`) without decoding the entire record. This can be
|
|
77
|
+
over 6x faster than `get()` when only partial data is needed.
|
|
75
78
|
|
|
76
79
|
**Not Yet Implemented:**
|
|
77
80
|
|
|
@@ -143,6 +146,24 @@ for ip, result in zip(ips, results):
|
|
|
143
146
|
print(f"{ip}: {result}")
|
|
144
147
|
```
|
|
145
148
|
|
|
149
|
+
### Selective Field Lookup (Extension)
|
|
150
|
+
|
|
151
|
+
The `get_path()` method allows you to efficiently retrieve a specific field from a record:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
import maxminddb_rust
|
|
155
|
+
|
|
156
|
+
reader = maxminddb_rust.open_database("/var/lib/GeoIP/GeoIP2-City.mmdb")
|
|
157
|
+
|
|
158
|
+
# Retrieve specific field without decoding the entire record
|
|
159
|
+
# Path elements can be strings (map keys) or integers (array indices)
|
|
160
|
+
iso_code = reader.get_path("8.8.8.8", ("country", "iso_code"))
|
|
161
|
+
print(f"ISO Code: {iso_code}")
|
|
162
|
+
|
|
163
|
+
# Accessing arrays by index
|
|
164
|
+
# e.g., reader.get_path("8.8.8.8", ("subdivisions", 0, "iso_code"))
|
|
165
|
+
```
|
|
166
|
+
|
|
146
167
|
### Iterator Support
|
|
147
168
|
|
|
148
169
|
Iterate over all networks in the database:
|
|
@@ -170,8 +191,11 @@ reader = maxminddb_rust.open_database("/var/lib/GeoIP/GeoIP2-City.mmdb", mode=ma
|
|
|
170
191
|
# MODE_MMAP: Explicitly use memory-mapped files
|
|
171
192
|
reader = maxminddb_rust.open_database("/var/lib/GeoIP/GeoIP2-City.mmdb", mode=maxminddb_rust.MODE_MMAP)
|
|
172
193
|
|
|
173
|
-
# MODE_MEMORY: Load entire database into memory (useful for embedded systems
|
|
174
|
-
|
|
194
|
+
# MODE_MEMORY: Load entire database into memory (useful for embedded systems
|
|
195
|
+
# or when file handle limits are a concern)
|
|
196
|
+
reader = maxminddb_rust.open_database(
|
|
197
|
+
"/var/lib/GeoIP/GeoIP2-City.mmdb", mode=maxminddb_rust.MODE_MEMORY
|
|
198
|
+
)
|
|
175
199
|
```
|
|
176
200
|
|
|
177
201
|
## Examples
|
|
@@ -182,6 +206,8 @@ The `examples/` directory contains complete working examples demonstrating vario
|
|
|
182
206
|
- **[context_manager.py](https://github.com/oschwald/maxminddb-rust-python/blob/main/examples/context_manager.py)** - Using `with` statement for automatic resource cleanup
|
|
183
207
|
- **[iterator_demo.py](https://github.com/oschwald/maxminddb-rust-python/blob/main/examples/iterator_demo.py)** - Iterating over all networks in the database
|
|
184
208
|
- **[batch_processing.py](https://github.com/oschwald/maxminddb-rust-python/blob/main/examples/batch_processing.py)** - High-performance batch lookups with `get_many()`
|
|
209
|
+
- **[benchmark_path.py](https://github.com/oschwald/maxminddb-rust-python/blob/main/examples/benchmark_path.py)**
|
|
210
|
+
- Performance comparison between `get()` and `get_path()`
|
|
185
211
|
|
|
186
212
|
Run any example:
|
|
187
213
|
|
|
@@ -37,6 +37,9 @@ This package provides **100% API compatibility** with the official [`maxminddb`]
|
|
|
37
37
|
**Extensions (not in original):**
|
|
38
38
|
|
|
39
39
|
- ⭐ `get_many()` - Batch lookup method for processing multiple IPs efficiently
|
|
40
|
+
- ⭐ `get_path()` - Efficiently retrieve a specific field from a record (e.g.,
|
|
41
|
+
`('country', 'iso_code')`) without decoding the entire record. This can be
|
|
42
|
+
over 6x faster than `get()` when only partial data is needed.
|
|
40
43
|
|
|
41
44
|
**Not Yet Implemented:**
|
|
42
45
|
|
|
@@ -108,6 +111,24 @@ for ip, result in zip(ips, results):
|
|
|
108
111
|
print(f"{ip}: {result}")
|
|
109
112
|
```
|
|
110
113
|
|
|
114
|
+
### Selective Field Lookup (Extension)
|
|
115
|
+
|
|
116
|
+
The `get_path()` method allows you to efficiently retrieve a specific field from a record:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import maxminddb_rust
|
|
120
|
+
|
|
121
|
+
reader = maxminddb_rust.open_database("/var/lib/GeoIP/GeoIP2-City.mmdb")
|
|
122
|
+
|
|
123
|
+
# Retrieve specific field without decoding the entire record
|
|
124
|
+
# Path elements can be strings (map keys) or integers (array indices)
|
|
125
|
+
iso_code = reader.get_path("8.8.8.8", ("country", "iso_code"))
|
|
126
|
+
print(f"ISO Code: {iso_code}")
|
|
127
|
+
|
|
128
|
+
# Accessing arrays by index
|
|
129
|
+
# e.g., reader.get_path("8.8.8.8", ("subdivisions", 0, "iso_code"))
|
|
130
|
+
```
|
|
131
|
+
|
|
111
132
|
### Iterator Support
|
|
112
133
|
|
|
113
134
|
Iterate over all networks in the database:
|
|
@@ -135,8 +156,11 @@ reader = maxminddb_rust.open_database("/var/lib/GeoIP/GeoIP2-City.mmdb", mode=ma
|
|
|
135
156
|
# MODE_MMAP: Explicitly use memory-mapped files
|
|
136
157
|
reader = maxminddb_rust.open_database("/var/lib/GeoIP/GeoIP2-City.mmdb", mode=maxminddb_rust.MODE_MMAP)
|
|
137
158
|
|
|
138
|
-
# MODE_MEMORY: Load entire database into memory (useful for embedded systems
|
|
139
|
-
|
|
159
|
+
# MODE_MEMORY: Load entire database into memory (useful for embedded systems
|
|
160
|
+
# or when file handle limits are a concern)
|
|
161
|
+
reader = maxminddb_rust.open_database(
|
|
162
|
+
"/var/lib/GeoIP/GeoIP2-City.mmdb", mode=maxminddb_rust.MODE_MEMORY
|
|
163
|
+
)
|
|
140
164
|
```
|
|
141
165
|
|
|
142
166
|
## Examples
|
|
@@ -147,6 +171,8 @@ The `examples/` directory contains complete working examples demonstrating vario
|
|
|
147
171
|
- **[context_manager.py](https://github.com/oschwald/maxminddb-rust-python/blob/main/examples/context_manager.py)** - Using `with` statement for automatic resource cleanup
|
|
148
172
|
- **[iterator_demo.py](https://github.com/oschwald/maxminddb-rust-python/blob/main/examples/iterator_demo.py)** - Iterating over all networks in the database
|
|
149
173
|
- **[batch_processing.py](https://github.com/oschwald/maxminddb-rust-python/blob/main/examples/batch_processing.py)** - High-performance batch lookups with `get_many()`
|
|
174
|
+
- **[benchmark_path.py](https://github.com/oschwald/maxminddb-rust-python/blob/main/examples/benchmark_path.py)**
|
|
175
|
+
- Performance comparison between `get()` and `get_path()`
|
|
150
176
|
|
|
151
177
|
Run any example:
|
|
152
178
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import random
|
|
5
|
+
import socket
|
|
6
|
+
import struct
|
|
7
|
+
import timeit
|
|
8
|
+
|
|
9
|
+
import maxminddb_rust
|
|
10
|
+
|
|
11
|
+
parser = argparse.ArgumentParser(description="Benchmark maxminddb get vs get_path.")
|
|
12
|
+
parser.add_argument("--count", default=250000, type=int, help="number of lookups")
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
"--file", default="/var/lib/GeoIP/GeoLite2-City.mmdb", help="path to mmdb file"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
args = parser.parse_args()
|
|
18
|
+
|
|
19
|
+
random.seed(0)
|
|
20
|
+
reader = maxminddb_rust.open_database(args.file)
|
|
21
|
+
|
|
22
|
+
# Pre-generate IPs to ensure fair comparison (though random lookup overhead is small)
|
|
23
|
+
ips = [
|
|
24
|
+
socket.inet_ntoa(struct.pack("!L", random.getrandbits(32)))
|
|
25
|
+
for _ in range(args.count)
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def lookup_full():
|
|
30
|
+
for ip in ips:
|
|
31
|
+
try:
|
|
32
|
+
res = reader.get(ip)
|
|
33
|
+
if res:
|
|
34
|
+
res.get("country", {}).get("iso_code")
|
|
35
|
+
except ValueError:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def lookup_path():
|
|
40
|
+
path = ("country", "iso_code")
|
|
41
|
+
for ip in ips:
|
|
42
|
+
try:
|
|
43
|
+
reader.get_path(ip, path)
|
|
44
|
+
except ValueError:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
print(f"Benchmarking with {args.count:,} lookups...")
|
|
49
|
+
|
|
50
|
+
time_full = timeit.timeit(lookup_full, number=1)
|
|
51
|
+
print(f"Full record decode: {int(args.count / time_full):,} lookups per second")
|
|
52
|
+
|
|
53
|
+
time_path = timeit.timeit(lookup_path, number=1)
|
|
54
|
+
print(f"Path decode (get_path): {int(args.count / time_path):,} lookups per second")
|
|
55
|
+
|
|
56
|
+
print(f"Speedup: {time_full / time_path:.2f}x")
|
|
@@ -8,7 +8,7 @@ implemented in Rust using PyO3 with 100% API compatibility.
|
|
|
8
8
|
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
|
|
9
9
|
from os import PathLike
|
|
10
10
|
from types import TracebackType
|
|
11
|
-
from typing import Any, Iterator, Literal, Optional, Union
|
|
11
|
+
from typing import Any, Iterator, Literal, Optional, Sequence, Union
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
14
14
|
"Reader",
|
|
@@ -152,6 +152,32 @@ class Reader:
|
|
|
152
152
|
"""
|
|
153
153
|
...
|
|
154
154
|
|
|
155
|
+
def get_path(
|
|
156
|
+
self,
|
|
157
|
+
ip_address: Union[str, IPv4Address, IPv6Address],
|
|
158
|
+
path: Sequence[Union[str, int]],
|
|
159
|
+
) -> Optional[Any]:
|
|
160
|
+
"""
|
|
161
|
+
Query the database for a specific path within the record.
|
|
162
|
+
|
|
163
|
+
This method is more efficient than get() when you only need a specific field
|
|
164
|
+
(e.g., country code) from the record, as it avoids decoding the entire record.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
ip_address: The IP address to look up.
|
|
168
|
+
path: A sequence (tuple or list) of strings or integers representing the path to the data.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
The value at the specified path, or None if the IP address or path is not found.
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
>>> reader.get_path('8.8.8.8', ('country', 'iso_code'))
|
|
175
|
+
'US'
|
|
176
|
+
>>> reader.get_path('8.8.8.8', ('subdivisions', 0, 'iso_code'))
|
|
177
|
+
'CA'
|
|
178
|
+
"""
|
|
179
|
+
...
|
|
180
|
+
|
|
155
181
|
def get_many(self, ips: list[str]) -> list[Optional[dict[str, Any]]]:
|
|
156
182
|
"""
|
|
157
183
|
Query the database for multiple IP addresses in a single batch operation.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
use ::maxminddb as maxminddb_crate;
|
|
2
|
-
use maxminddb_crate::{
|
|
2
|
+
use maxminddb_crate::{
|
|
3
|
+
MaxMindDbError, PathElement, Reader as MaxMindReader, Within, WithinOptions,
|
|
4
|
+
};
|
|
3
5
|
use memmap2::Mmap;
|
|
4
6
|
use pyo3::{
|
|
5
7
|
conversion::IntoPyObjectExt,
|
|
@@ -240,6 +242,11 @@ const ERR_CLOSED_DB: &str = "Attempt to read from a closed MaxMind DB.";
|
|
|
240
242
|
const ERR_BAD_DATA: &str =
|
|
241
243
|
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)";
|
|
242
244
|
|
|
245
|
+
enum OwnedPathElement {
|
|
246
|
+
Key(String),
|
|
247
|
+
Index(usize),
|
|
248
|
+
}
|
|
249
|
+
|
|
243
250
|
/// Enum to handle different reader source types
|
|
244
251
|
enum ReaderSource {
|
|
245
252
|
Mmap(MaxMindReader<Mmap>),
|
|
@@ -287,6 +294,36 @@ impl ReaderSource {
|
|
|
287
294
|
}
|
|
288
295
|
}
|
|
289
296
|
|
|
297
|
+
#[inline]
|
|
298
|
+
fn lookup_path(
|
|
299
|
+
&self,
|
|
300
|
+
ip: IpAddr,
|
|
301
|
+
path: &[OwnedPathElement],
|
|
302
|
+
) -> Result<Option<PyDecodedValue>, maxminddb_crate::MaxMindDbError> {
|
|
303
|
+
let path_elements: Vec<PathElement> = path
|
|
304
|
+
.iter()
|
|
305
|
+
.map(|e| match e {
|
|
306
|
+
OwnedPathElement::Key(s) => PathElement::Key(s.as_str()),
|
|
307
|
+
|
|
308
|
+
OwnedPathElement::Index(i) => PathElement::Index(*i),
|
|
309
|
+
})
|
|
310
|
+
.collect();
|
|
311
|
+
|
|
312
|
+
match self {
|
|
313
|
+
ReaderSource::Mmap(reader) => {
|
|
314
|
+
let result = reader.lookup(ip)?;
|
|
315
|
+
|
|
316
|
+
result.decode_path(&path_elements)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
ReaderSource::Memory(reader) => {
|
|
320
|
+
let result = reader.lookup(ip)?;
|
|
321
|
+
|
|
322
|
+
result.decode_path(&path_elements)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
290
327
|
#[inline]
|
|
291
328
|
fn metadata(&self) -> &maxminddb_crate::Metadata {
|
|
292
329
|
match self {
|
|
@@ -490,6 +527,64 @@ impl Reader {
|
|
|
490
527
|
}
|
|
491
528
|
}
|
|
492
529
|
|
|
530
|
+
/// Query the database for a specific path within the record.
|
|
531
|
+
///
|
|
532
|
+
/// This method is more efficient than get() when you only need a specific field
|
|
533
|
+
/// (e.g., country code) from the record, as it avoids decoding the entire record.
|
|
534
|
+
///
|
|
535
|
+
/// Args:
|
|
536
|
+
/// ip_address: The IP address to look up. May be a string (e.g., '1.2.3.4')
|
|
537
|
+
/// or an ipaddress.IPv4Address or ipaddress.IPv6Address object.
|
|
538
|
+
/// path: A sequence (tuple or list) of strings or integers representing the
|
|
539
|
+
/// path to the data.
|
|
540
|
+
///
|
|
541
|
+
/// Returns:
|
|
542
|
+
/// The value at the specified path, or None if the IP address or path is
|
|
543
|
+
/// not found.
|
|
544
|
+
///
|
|
545
|
+
/// Example:
|
|
546
|
+
/// >>> reader.get_path('8.8.8.8', ('country', 'iso_code'))
|
|
547
|
+
/// 'US'
|
|
548
|
+
fn get_path(
|
|
549
|
+
&self,
|
|
550
|
+
py: Python,
|
|
551
|
+
ip_address: &Bound<'_, PyAny>,
|
|
552
|
+
path: &Bound<'_, PyAny>,
|
|
553
|
+
) -> PyResult<Py<PyAny>> {
|
|
554
|
+
// Quick check if database is closed
|
|
555
|
+
if self.closed.load(Ordering::Acquire) {
|
|
556
|
+
return Err(PyValueError::new_err(ERR_CLOSED_DB));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Parse IP address
|
|
560
|
+
let parsed_ip = parse_ip_address(ip_address)?;
|
|
561
|
+
|
|
562
|
+
if self.ip_version == 4 && matches!(parsed_ip.addr, IpAddr::V6(_)) {
|
|
563
|
+
return Err(PyValueError::new_err(ipv6_in_ipv4_error(&parsed_ip.addr)));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Parse path
|
|
567
|
+
let owned_path = parse_path(path)?;
|
|
568
|
+
|
|
569
|
+
let reader = self.get_reader()?;
|
|
570
|
+
|
|
571
|
+
// We need to keep the Strings alive while we use them as &str
|
|
572
|
+
// This is a bit tricky because we're crossing the thread boundary with py.detach
|
|
573
|
+
// But since we are passing ownership of `path` (Vec<String>) into the closure,
|
|
574
|
+
// and constructing the Vec<&str> inside, it should work.
|
|
575
|
+
|
|
576
|
+
let result = py.detach(move || reader.lookup_path(parsed_ip.addr, &owned_path));
|
|
577
|
+
|
|
578
|
+
match result {
|
|
579
|
+
Ok(Some(data)) => Ok(data.into_py()),
|
|
580
|
+
Ok(None) => Ok(py.None()),
|
|
581
|
+
Err(MaxMindDbError::InvalidDatabase { .. } | MaxMindDbError::Decoding { .. }) => {
|
|
582
|
+
Err(InvalidDatabaseError::new_err(ERR_BAD_DATA))
|
|
583
|
+
}
|
|
584
|
+
Err(err) => Err(PyValueError::new_err(format!("Database error: {err}"))),
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
493
588
|
/// Query the database for information about an IP address and return the network prefix length.
|
|
494
589
|
///
|
|
495
590
|
/// Args:
|
|
@@ -895,6 +990,34 @@ impl ReaderWithin {
|
|
|
895
990
|
}
|
|
896
991
|
}
|
|
897
992
|
|
|
993
|
+
fn parse_path(path: &Bound<'_, PyAny>) -> PyResult<Vec<OwnedPathElement>> {
|
|
994
|
+
let mut owned_path = Vec::new();
|
|
995
|
+
if path.is_instance_of::<PyString>() {
|
|
996
|
+
return Err(PyTypeError::new_err(
|
|
997
|
+
"Path must be a sequence (list or tuple)",
|
|
998
|
+
));
|
|
999
|
+
}
|
|
1000
|
+
if let Ok(iterator) = path.try_iter() {
|
|
1001
|
+
for item in iterator {
|
|
1002
|
+
let item = item?;
|
|
1003
|
+
if let Ok(s) = item.extract::<String>() {
|
|
1004
|
+
owned_path.push(OwnedPathElement::Key(s));
|
|
1005
|
+
} else if let Ok(i) = item.extract::<usize>() {
|
|
1006
|
+
owned_path.push(OwnedPathElement::Index(i));
|
|
1007
|
+
} else {
|
|
1008
|
+
return Err(PyTypeError::new_err(
|
|
1009
|
+
"Path elements must be strings or integers",
|
|
1010
|
+
));
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
} else {
|
|
1014
|
+
return Err(PyTypeError::new_err(
|
|
1015
|
+
"Path must be a sequence (list or tuple)",
|
|
1016
|
+
));
|
|
1017
|
+
}
|
|
1018
|
+
Ok(owned_path)
|
|
1019
|
+
}
|
|
1020
|
+
|
|
898
1021
|
struct ParsedIp {
|
|
899
1022
|
addr: IpAddr,
|
|
900
1023
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
import maxminddb_rust
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_get_path():
|
|
7
|
+
db_path = os.path.join(
|
|
8
|
+
os.path.dirname(__file__), "data", "test-data", "GeoIP2-City-Test.mmdb"
|
|
9
|
+
)
|
|
10
|
+
with maxminddb_rust.open_database(db_path) as reader:
|
|
11
|
+
ip = "81.2.69.142"
|
|
12
|
+
|
|
13
|
+
# Verify full record first
|
|
14
|
+
record = reader.get(ip)
|
|
15
|
+
assert record["country"]["iso_code"] == "GB"
|
|
16
|
+
assert record["city"]["names"]["en"] == "London"
|
|
17
|
+
|
|
18
|
+
# Test get_path for various depths
|
|
19
|
+
assert reader.get_path(ip, ("country", "iso_code")) == "GB"
|
|
20
|
+
assert reader.get_path(ip, ("city", "names", "en")) == "London"
|
|
21
|
+
assert (
|
|
22
|
+
reader.get_path(ip, ("location", "latitude"))
|
|
23
|
+
== record["location"]["latitude"]
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Test non-existent path
|
|
27
|
+
assert reader.get_path(ip, ("non", "existent")) is None
|
|
28
|
+
|
|
29
|
+
# Test non-existent IP
|
|
30
|
+
assert reader.get_path("1.1.1.1", ("country", "iso_code")) is None
|
|
31
|
+
|
|
32
|
+
# Test array indexing (if subdivisions exist)
|
|
33
|
+
if "subdivisions" in record and len(record["subdivisions"]) > 0:
|
|
34
|
+
assert (
|
|
35
|
+
reader.get_path(ip, ("subdivisions", 0, "iso_code"))
|
|
36
|
+
== record["subdivisions"][0]["iso_code"]
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_get_path_ipv6():
|
|
41
|
+
db_path = os.path.join(
|
|
42
|
+
os.path.dirname(__file__), "data", "test-data", "GeoIP2-City-Test.mmdb"
|
|
43
|
+
)
|
|
44
|
+
with maxminddb_rust.open_database(db_path) as reader:
|
|
45
|
+
ip = "2001:2b8::"
|
|
46
|
+
|
|
47
|
+
assert reader.get_path(ip, ("country", "iso_code")) == "KR"
|
|
48
|
+
assert reader.get_path(ip, ("continent", "names", "en")) == "Asia"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_get_path_invalid_types():
|
|
52
|
+
db_path = os.path.join(
|
|
53
|
+
os.path.dirname(__file__), "data", "test-data", "GeoIP2-City-Test.mmdb"
|
|
54
|
+
)
|
|
55
|
+
with maxminddb_rust.open_database(db_path) as reader:
|
|
56
|
+
ip = "81.2.69.142"
|
|
57
|
+
|
|
58
|
+
# Invalid path element type (float)
|
|
59
|
+
with pytest.raises(
|
|
60
|
+
TypeError, match="Path elements must be strings or integers"
|
|
61
|
+
):
|
|
62
|
+
reader.get_path(ip, ("country", 3.14))
|
|
63
|
+
|
|
64
|
+
# Invalid path argument type (not a sequence)
|
|
65
|
+
with pytest.raises(TypeError, match="Path must be a sequence"):
|
|
66
|
+
reader.get_path(ip, "country") # type: ignore
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_get_path_closed_db():
|
|
70
|
+
db_path = os.path.join(
|
|
71
|
+
os.path.dirname(__file__), "data", "test-data", "GeoIP2-City-Test.mmdb"
|
|
72
|
+
)
|
|
73
|
+
reader = maxminddb_rust.open_database(db_path)
|
|
74
|
+
reader.close()
|
|
75
|
+
|
|
76
|
+
with pytest.raises(ValueError, match="closed"):
|
|
77
|
+
reader.get_path("81.2.69.142", ("country", "iso_code"))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_get_path_mixed_invalid():
|
|
81
|
+
db_path = os.path.join(
|
|
82
|
+
os.path.dirname(__file__), "data", "test-data", "GeoIP2-City-Test.mmdb"
|
|
83
|
+
)
|
|
84
|
+
with maxminddb_rust.open_database(db_path) as reader:
|
|
85
|
+
ip = "81.2.69.142"
|
|
86
|
+
|
|
87
|
+
# Mixed valid and invalid types
|
|
88
|
+
with pytest.raises(
|
|
89
|
+
TypeError, match="Path elements must be strings or integers"
|
|
90
|
+
):
|
|
91
|
+
reader.get_path(ip, ("country", 3.14, "iso_code"))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|