maxminddb 2.6.3__tar.gz → 2.7.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-2.6.3 → maxminddb-2.7.0}/HISTORY.rst +11 -0
- {maxminddb-2.6.3/maxminddb.egg-info → maxminddb-2.7.0}/PKG-INFO +9 -10
- {maxminddb-2.6.3 → maxminddb-2.7.0}/README.rst +4 -4
- {maxminddb-2.6.3 → maxminddb-2.7.0}/docs/conf.py +1 -2
- {maxminddb-2.6.3 → maxminddb-2.7.0}/docs/index.rst +11 -3
- {maxminddb-2.6.3 → maxminddb-2.7.0}/examples/benchmark.py +5 -5
- {maxminddb-2.6.3 → maxminddb-2.7.0}/extension/libmaxminddb/src/data-pool.c +6 -2
- {maxminddb-2.6.3 → maxminddb-2.7.0}/extension/libmaxminddb/src/maxminddb.c +2 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/extension/maxminddb.c +10 -9
- {maxminddb-2.6.3 → maxminddb-2.7.0}/maxminddb/__init__.py +7 -4
- {maxminddb-2.6.3 → maxminddb-2.7.0}/maxminddb/const.py +1 -1
- {maxminddb-2.6.3 → maxminddb-2.7.0}/maxminddb/decoder.py +29 -30
- maxminddb-2.7.0/maxminddb/errors.py +5 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/maxminddb/extension.pyi +19 -20
- {maxminddb-2.6.3 → maxminddb-2.7.0}/maxminddb/file.py +6 -6
- {maxminddb-2.6.3 → maxminddb-2.7.0}/maxminddb/reader.py +43 -41
- maxminddb-2.7.0/maxminddb/types.py +14 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0/maxminddb.egg-info}/PKG-INFO +9 -10
- {maxminddb-2.6.3 → maxminddb-2.7.0}/maxminddb.egg-info/SOURCES.txt +1 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/pyproject.toml +48 -9
- {maxminddb-2.6.3 → maxminddb-2.7.0}/setup.cfg +3 -4
- {maxminddb-2.6.3 → maxminddb-2.7.0}/setup.py +26 -22
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/decoder_test.py +34 -36
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/reader_test.py +153 -114
- maxminddb-2.7.0/uv.lock +359 -0
- maxminddb-2.6.3/maxminddb/errors.py +0 -10
- maxminddb-2.6.3/maxminddb/types.py +0 -23
- {maxminddb-2.6.3 → maxminddb-2.7.0}/LICENSE +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/MANIFEST.in +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/docs/Makefile +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/extension/libmaxminddb/include/maxminddb.h +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/extension/libmaxminddb/src/data-pool.h +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/extension/libmaxminddb/src/maxminddb-compat-util.h +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/extension/maxminddb_config.h +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/maxminddb/py.typed +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/maxminddb.egg-info/dependency_links.txt +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/maxminddb.egg-info/top_level.txt +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/bad-data/libmaxminddb/libmaxminddb-offset-integer-overflow.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/bad-data/maxminddb-golang/cyclic-data-structure.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/bad-data/maxminddb-golang/invalid-bytes-length.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/bad-data/maxminddb-golang/invalid-data-record-offset.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/bad-data/maxminddb-golang/invalid-map-key-length.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/bad-data/maxminddb-golang/invalid-string-length.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/bad-data/maxminddb-golang/metadata-is-an-uint128.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/bad-data/maxminddb-golang/unexpected-bytes.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/bad-data/maxminddb-python/bad-unicode-in-map-key.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-City-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-Connection-Type-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-Country-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-DensityIncome-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-Domain-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-Enterprise-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-ISP-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-Precision-Enterprise-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-Static-IP-Score-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoIP2-User-Count-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoLite2-ASN-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoLite2-City-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/GeoLite2-Country-Test.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-string-value-entries.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-broken-search-tree-24.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-decoder.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-ipv4-24.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-ipv4-28.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-ipv4-32.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-ipv6-24.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-ipv6-28.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-ipv6-32.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-mixed-24.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-mixed-28.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-mixed-32.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-nested.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/MaxMind-DB-test-pointer-decoder.mmdb +0 -0
- {maxminddb-2.6.3 → maxminddb-2.7.0}/tests/data/test-data/maps-with-pointers.raw +0 -0
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
History
|
|
4
4
|
-------
|
|
5
5
|
|
|
6
|
+
2.7.0 (2025-05-05)
|
|
7
|
+
++++++++++++++++++
|
|
8
|
+
|
|
9
|
+
* IMPORTANT: Python 3.9 or greater is required. If you are using an older
|
|
10
|
+
version, please use an earlier release.
|
|
11
|
+
* The vendored ``libmaxminddb`` has been updated to 1.12.2.
|
|
12
|
+
* The C extension now checks that the database metadata lookup was
|
|
13
|
+
successful.
|
|
14
|
+
* A theoretical segmentation fault with the C extension when doing lookups
|
|
15
|
+
on a corrupt or invalid database was fixed.
|
|
16
|
+
|
|
6
17
|
2.6.3 (2025-01-09)
|
|
7
18
|
++++++++++++++++++
|
|
8
19
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: maxminddb
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.0
|
|
4
4
|
Summary: Reader for the MaxMind DB format
|
|
5
5
|
Author-email: Gregory Oschwald <goschwald@maxmind.com>
|
|
6
|
-
License: Apache
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://www.maxmind.com/
|
|
8
8
|
Project-URL: Documentation, https://maxminddb.readthedocs.org/
|
|
9
9
|
Project-URL: Source Code, https://github.com/maxmind/MaxMind-DB-Reader-python
|
|
@@ -12,10 +12,8 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
12
12
|
Classifier: Environment :: Web Environment
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Intended Audience :: System Administrators
|
|
15
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
15
|
Classifier: Programming Language :: Python
|
|
17
16
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -23,9 +21,10 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
24
22
|
Classifier: Topic :: Internet
|
|
25
23
|
Classifier: Topic :: Internet :: Proxy Servers
|
|
26
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.9
|
|
27
25
|
Description-Content-Type: text/x-rst
|
|
28
26
|
License-File: LICENSE
|
|
27
|
+
Dynamic: license-file
|
|
29
28
|
|
|
30
29
|
========================
|
|
31
30
|
MaxMind DB Python Module
|
|
@@ -43,18 +42,18 @@ subnets (IPv4 or IPv6).
|
|
|
43
42
|
Installation
|
|
44
43
|
------------
|
|
45
44
|
|
|
46
|
-
To install maxminddb
|
|
45
|
+
To install ``maxminddb```, type:
|
|
47
46
|
|
|
48
47
|
.. code-block:: bash
|
|
49
48
|
|
|
50
49
|
$ pip install maxminddb
|
|
51
50
|
|
|
52
|
-
If you are not able to
|
|
51
|
+
If you are not able to install from PyPI, you may also use ``pip`` from the
|
|
53
52
|
source directory:
|
|
54
53
|
|
|
55
54
|
.. code-block:: bash
|
|
56
55
|
|
|
57
|
-
$
|
|
56
|
+
$ python -m pip install .
|
|
58
57
|
|
|
59
58
|
The installer will attempt to build the C extension. If this fails, the
|
|
60
59
|
module will fall-back to the pure Python implementation.
|
|
@@ -126,7 +125,7 @@ invalid IP address or an IPv6 address in an IPv4 database.
|
|
|
126
125
|
Requirements
|
|
127
126
|
------------
|
|
128
127
|
|
|
129
|
-
This code requires Python 3.
|
|
128
|
+
This code requires Python 3.9+. Older versions are not supported. The C
|
|
130
129
|
extension requires CPython.
|
|
131
130
|
|
|
132
131
|
Versioning
|
|
@@ -14,18 +14,18 @@ subnets (IPv4 or IPv6).
|
|
|
14
14
|
Installation
|
|
15
15
|
------------
|
|
16
16
|
|
|
17
|
-
To install maxminddb
|
|
17
|
+
To install ``maxminddb```, type:
|
|
18
18
|
|
|
19
19
|
.. code-block:: bash
|
|
20
20
|
|
|
21
21
|
$ pip install maxminddb
|
|
22
22
|
|
|
23
|
-
If you are not able to
|
|
23
|
+
If you are not able to install from PyPI, you may also use ``pip`` from the
|
|
24
24
|
source directory:
|
|
25
25
|
|
|
26
26
|
.. code-block:: bash
|
|
27
27
|
|
|
28
|
-
$
|
|
28
|
+
$ python -m pip install .
|
|
29
29
|
|
|
30
30
|
The installer will attempt to build the C extension. If this fails, the
|
|
31
31
|
module will fall-back to the pure Python implementation.
|
|
@@ -97,7 +97,7 @@ invalid IP address or an IPv6 address in an IPv4 database.
|
|
|
97
97
|
Requirements
|
|
98
98
|
------------
|
|
99
99
|
|
|
100
|
-
This code requires Python 3.
|
|
100
|
+
This code requires Python 3.9+. Older versions are not supported. The C
|
|
101
101
|
extension requires CPython.
|
|
102
102
|
|
|
103
103
|
Versioning
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
#
|
|
4
3
|
# maxminddb documentation build configuration file, created by
|
|
5
4
|
# sphinx-quickstart on Tue Apr 9 13:34:57 2013.
|
|
@@ -12,8 +11,8 @@
|
|
|
12
11
|
# All configuration values have a default; values that are commented out
|
|
13
12
|
# serve to show the default.
|
|
14
13
|
|
|
15
|
-
import sys
|
|
16
14
|
import os
|
|
15
|
+
import sys
|
|
17
16
|
|
|
18
17
|
sys.path.insert(0, os.path.abspath(".."))
|
|
19
18
|
import maxminddb
|
|
@@ -8,20 +8,28 @@
|
|
|
8
8
|
|
|
9
9
|
.. include:: ../README.rst
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
======
|
|
12
|
+
Module
|
|
13
|
+
======
|
|
14
14
|
|
|
15
15
|
.. automodule:: maxminddb
|
|
16
16
|
:members:
|
|
17
17
|
:undoc-members:
|
|
18
18
|
:show-inheritance:
|
|
19
19
|
|
|
20
|
+
======
|
|
21
|
+
Errors
|
|
22
|
+
======
|
|
23
|
+
|
|
20
24
|
.. automodule:: maxminddb.errors
|
|
21
25
|
:members:
|
|
22
26
|
:undoc-members:
|
|
23
27
|
:show-inheritance:
|
|
24
28
|
|
|
29
|
+
===============
|
|
30
|
+
Database Reader
|
|
31
|
+
===============
|
|
32
|
+
|
|
25
33
|
.. automodule:: maxminddb.reader
|
|
26
34
|
:members:
|
|
27
35
|
:undoc-members:
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/python
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
|
|
4
3
|
import argparse
|
|
5
|
-
import maxminddb
|
|
6
4
|
import random
|
|
7
5
|
import socket
|
|
8
6
|
import struct
|
|
9
7
|
import timeit
|
|
10
8
|
|
|
9
|
+
import maxminddb
|
|
10
|
+
|
|
11
11
|
parser = argparse.ArgumentParser(description="Benchmark maxminddb.")
|
|
12
12
|
parser.add_argument("--count", default=250000, type=int, help="number of lookups")
|
|
13
13
|
parser.add_argument("--mode", default=0, type=int, help="reader mode to use")
|
|
@@ -19,9 +19,9 @@ random.seed(0)
|
|
|
19
19
|
reader = maxminddb.open_database(args.file, args.mode)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def lookup_ip_address():
|
|
22
|
+
def lookup_ip_address() -> None:
|
|
23
23
|
ip = socket.inet_ntoa(struct.pack("!L", random.getrandbits(32)))
|
|
24
|
-
|
|
24
|
+
reader.get(str(ip))
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
elapsed = timeit.timeit(
|
|
@@ -30,4 +30,4 @@ elapsed = timeit.timeit(
|
|
|
30
30
|
number=args.count,
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
print("{
|
|
33
|
+
print(f"{int(args.count / elapsed):,}", "lookups per second")
|
|
@@ -158,9 +158,13 @@ int main(void) {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
static void test_can_multiply(void) {
|
|
161
|
-
{
|
|
161
|
+
{
|
|
162
|
+
ok(can_multiply(SIZE_MAX, 1, SIZE_MAX), "1*SIZE_MAX is ok");
|
|
163
|
+
}
|
|
162
164
|
|
|
163
|
-
{
|
|
165
|
+
{
|
|
166
|
+
ok(!can_multiply(SIZE_MAX, 2, SIZE_MAX), "2*SIZE_MAX is not ok");
|
|
167
|
+
}
|
|
164
168
|
|
|
165
169
|
{
|
|
166
170
|
ok(can_multiply(SIZE_MAX, 10240, sizeof(MMDB_entry_data_list_s)),
|
|
@@ -1636,6 +1636,8 @@ int MMDB_get_metadata_as_entry_data_list(
|
|
|
1636
1636
|
|
|
1637
1637
|
int MMDB_get_entry_data_list(MMDB_entry_s *start,
|
|
1638
1638
|
MMDB_entry_data_list_s **const entry_data_list) {
|
|
1639
|
+
*entry_data_list = NULL;
|
|
1640
|
+
|
|
1639
1641
|
MMDB_data_pool_s *const pool = data_pool_new(MMDB_POOL_INIT_SIZE);
|
|
1640
1642
|
if (!pool) {
|
|
1641
1643
|
return MMDB_OUT_OF_MEMORY_ERROR;
|
|
@@ -351,7 +351,14 @@ static PyObject *Reader_metadata(PyObject *self, PyObject *UNUSED(args)) {
|
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
MMDB_entry_data_list_s *entry_data_list;
|
|
354
|
-
|
|
354
|
+
int status =
|
|
355
|
+
MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list);
|
|
356
|
+
if (status != MMDB_SUCCESS) {
|
|
357
|
+
PyErr_Format(MaxMindDB_error,
|
|
358
|
+
"Error decoding metadata. %s",
|
|
359
|
+
MMDB_strerror(status));
|
|
360
|
+
return NULL;
|
|
361
|
+
}
|
|
355
362
|
MMDB_entry_data_list_s *original_entry_data_list = entry_data_list;
|
|
356
363
|
|
|
357
364
|
PyObject *metadata_dict = from_entry_data_list(&entry_data_list);
|
|
@@ -744,10 +751,7 @@ static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list) {
|
|
|
744
751
|
const uint32_t map_size = (*entry_data_list)->entry_data.data_size;
|
|
745
752
|
|
|
746
753
|
uint32_t i;
|
|
747
|
-
|
|
748
|
-
// check it in the loop because it may become NULL.
|
|
749
|
-
// coverity[check_after_deref]
|
|
750
|
-
for (i = 0; i < map_size && entry_data_list; i++) {
|
|
754
|
+
for (i = 0; i < map_size && *entry_data_list; i++) {
|
|
751
755
|
*entry_data_list = (*entry_data_list)->next;
|
|
752
756
|
|
|
753
757
|
PyObject *key = PyUnicode_FromStringAndSize(
|
|
@@ -785,10 +789,7 @@ static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list) {
|
|
|
785
789
|
}
|
|
786
790
|
|
|
787
791
|
uint32_t i;
|
|
788
|
-
|
|
789
|
-
// check it in the loop because it may become NULL.
|
|
790
|
-
// coverity[check_after_deref]
|
|
791
|
-
for (i = 0; i < size && entry_data_list; i++) {
|
|
792
|
+
for (i = 0; i < size && *entry_data_list; i++) {
|
|
792
793
|
*entry_data_list = (*entry_data_list)->next;
|
|
793
794
|
PyObject *value = from_entry_data_list(entry_data_list);
|
|
794
795
|
if (value == NULL) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Module for reading MaxMind DB files."""
|
|
2
|
+
|
|
1
3
|
# pylint:disable=C0111
|
|
2
4
|
import os
|
|
3
5
|
from typing import IO, AnyStr, Union, cast
|
|
@@ -21,13 +23,13 @@ except ImportError:
|
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
__all__ = [
|
|
24
|
-
"InvalidDatabaseError",
|
|
25
26
|
"MODE_AUTO",
|
|
26
27
|
"MODE_FD",
|
|
27
28
|
"MODE_FILE",
|
|
28
29
|
"MODE_MEMORY",
|
|
29
30
|
"MODE_MMAP",
|
|
30
31
|
"MODE_MMAP_EXT",
|
|
32
|
+
"InvalidDatabaseError",
|
|
31
33
|
"Reader",
|
|
32
34
|
"open_database",
|
|
33
35
|
]
|
|
@@ -37,7 +39,7 @@ def open_database(
|
|
|
37
39
|
database: Union[AnyStr, int, os.PathLike, IO],
|
|
38
40
|
mode: int = MODE_AUTO,
|
|
39
41
|
) -> Reader:
|
|
40
|
-
"""Open a MaxMind DB database
|
|
42
|
+
"""Open a MaxMind DB database.
|
|
41
43
|
|
|
42
44
|
Arguments:
|
|
43
45
|
database -- A path to a valid MaxMind DB file such as a GeoIP2 database
|
|
@@ -51,6 +53,7 @@ def open_database(
|
|
|
51
53
|
a path. This mode implies MODE_MEMORY.
|
|
52
54
|
* MODE_AUTO - tries MODE_MMAP_EXT, MODE_MMAP, MODE_FILE in that
|
|
53
55
|
order. Default mode.
|
|
56
|
+
|
|
54
57
|
"""
|
|
55
58
|
if mode not in (
|
|
56
59
|
MODE_AUTO,
|
|
@@ -70,7 +73,7 @@ def open_database(
|
|
|
70
73
|
|
|
71
74
|
if not has_extension:
|
|
72
75
|
raise ValueError(
|
|
73
|
-
"MODE_MMAP_EXT requires the maxminddb.extension module to be available"
|
|
76
|
+
"MODE_MMAP_EXT requires the maxminddb.extension module to be available",
|
|
74
77
|
)
|
|
75
78
|
|
|
76
79
|
# The C type exposes the same API as the Python Reader, so for type
|
|
@@ -81,7 +84,7 @@ def open_database(
|
|
|
81
84
|
|
|
82
85
|
|
|
83
86
|
__title__ = "maxminddb"
|
|
84
|
-
__version__ = "2.
|
|
87
|
+
__version__ = "2.7.0"
|
|
85
88
|
__author__ = "Gregory Oschwald"
|
|
86
89
|
__license__ = "Apache License, Version 2.0"
|
|
87
90
|
__copyright__ = "Copyright 2013-2025 MaxMind, Inc."
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
"""
|
|
2
|
-
maxminddb.decoder
|
|
3
|
-
~~~~~~~~~~~~~~~~~
|
|
4
|
-
|
|
5
|
-
This package contains code for decoding the MaxMind DB data section.
|
|
6
|
-
|
|
7
|
-
"""
|
|
1
|
+
"""Decoder for the MaxMind DB data section."""
|
|
8
2
|
|
|
9
3
|
import struct
|
|
10
|
-
from typing import
|
|
4
|
+
from typing import Union, cast
|
|
11
5
|
|
|
12
6
|
try:
|
|
13
7
|
# pylint: disable=unused-import
|
|
@@ -23,7 +17,7 @@ from maxminddb.types import Record
|
|
|
23
17
|
|
|
24
18
|
|
|
25
19
|
class Decoder: # pylint: disable=too-few-public-methods
|
|
26
|
-
"""Decoder for the data section of the MaxMind DB"""
|
|
20
|
+
"""Decoder for the data section of the MaxMind DB."""
|
|
27
21
|
|
|
28
22
|
def __init__(
|
|
29
23
|
self,
|
|
@@ -31,46 +25,47 @@ class Decoder: # pylint: disable=too-few-public-methods
|
|
|
31
25
|
pointer_base: int = 0,
|
|
32
26
|
pointer_test: bool = False,
|
|
33
27
|
) -> None:
|
|
34
|
-
"""Created a Decoder for a MaxMind DB
|
|
28
|
+
"""Created a Decoder for a MaxMind DB.
|
|
35
29
|
|
|
36
30
|
Arguments:
|
|
37
31
|
database_buffer -- an mmap'd MaxMind DB file.
|
|
38
32
|
pointer_base -- the base number to use when decoding a pointer
|
|
39
33
|
pointer_test -- used for internal unit testing of pointer code
|
|
34
|
+
|
|
40
35
|
"""
|
|
41
36
|
self._pointer_test = pointer_test
|
|
42
37
|
self._buffer = database_buffer
|
|
43
38
|
self._pointer_base = pointer_base
|
|
44
39
|
|
|
45
|
-
def _decode_array(self, size: int, offset: int) ->
|
|
40
|
+
def _decode_array(self, size: int, offset: int) -> tuple[list[Record], int]:
|
|
46
41
|
array = []
|
|
47
42
|
for _ in range(size):
|
|
48
43
|
(value, offset) = self.decode(offset)
|
|
49
44
|
array.append(value)
|
|
50
45
|
return array, offset
|
|
51
46
|
|
|
52
|
-
def _decode_boolean(self, size: int, offset: int) ->
|
|
47
|
+
def _decode_boolean(self, size: int, offset: int) -> tuple[bool, int]:
|
|
53
48
|
return size != 0, offset
|
|
54
49
|
|
|
55
|
-
def _decode_bytes(self, size: int, offset: int) ->
|
|
50
|
+
def _decode_bytes(self, size: int, offset: int) -> tuple[bytes, int]:
|
|
56
51
|
new_offset = offset + size
|
|
57
52
|
return self._buffer[offset:new_offset], new_offset
|
|
58
53
|
|
|
59
|
-
def _decode_double(self, size: int, offset: int) ->
|
|
54
|
+
def _decode_double(self, size: int, offset: int) -> tuple[float, int]:
|
|
60
55
|
self._verify_size(size, 8)
|
|
61
56
|
new_offset = offset + size
|
|
62
57
|
packed_bytes = self._buffer[offset:new_offset]
|
|
63
58
|
(value,) = struct.unpack(b"!d", packed_bytes)
|
|
64
59
|
return value, new_offset
|
|
65
60
|
|
|
66
|
-
def _decode_float(self, size: int, offset: int) ->
|
|
61
|
+
def _decode_float(self, size: int, offset: int) -> tuple[float, int]:
|
|
67
62
|
self._verify_size(size, 4)
|
|
68
63
|
new_offset = offset + size
|
|
69
64
|
packed_bytes = self._buffer[offset:new_offset]
|
|
70
65
|
(value,) = struct.unpack(b"!f", packed_bytes)
|
|
71
66
|
return value, new_offset
|
|
72
67
|
|
|
73
|
-
def _decode_int32(self, size: int, offset: int) ->
|
|
68
|
+
def _decode_int32(self, size: int, offset: int) -> tuple[int, int]:
|
|
74
69
|
if size == 0:
|
|
75
70
|
return 0, offset
|
|
76
71
|
new_offset = offset + size
|
|
@@ -81,15 +76,15 @@ class Decoder: # pylint: disable=too-few-public-methods
|
|
|
81
76
|
(value,) = struct.unpack(b"!i", packed_bytes)
|
|
82
77
|
return value, new_offset
|
|
83
78
|
|
|
84
|
-
def _decode_map(self, size: int, offset: int) ->
|
|
85
|
-
container:
|
|
79
|
+
def _decode_map(self, size: int, offset: int) -> tuple[dict[str, Record], int]:
|
|
80
|
+
container: dict[str, Record] = {}
|
|
86
81
|
for _ in range(size):
|
|
87
82
|
(key, offset) = self.decode(offset)
|
|
88
83
|
(value, offset) = self.decode(offset)
|
|
89
|
-
container[cast(str, key)] = value
|
|
84
|
+
container[cast("str", key)] = value
|
|
90
85
|
return container, offset
|
|
91
86
|
|
|
92
|
-
def _decode_pointer(self, size: int, offset: int) ->
|
|
87
|
+
def _decode_pointer(self, size: int, offset: int) -> tuple[Record, int]:
|
|
93
88
|
pointer_size = (size >> 3) + 1
|
|
94
89
|
|
|
95
90
|
buf = self._buffer[offset : offset + pointer_size]
|
|
@@ -112,12 +107,12 @@ class Decoder: # pylint: disable=too-few-public-methods
|
|
|
112
107
|
(value, _) = self.decode(pointer)
|
|
113
108
|
return value, new_offset
|
|
114
109
|
|
|
115
|
-
def _decode_uint(self, size: int, offset: int) ->
|
|
110
|
+
def _decode_uint(self, size: int, offset: int) -> tuple[int, int]:
|
|
116
111
|
new_offset = offset + size
|
|
117
112
|
uint_bytes = self._buffer[offset:new_offset]
|
|
118
113
|
return int.from_bytes(uint_bytes, "big"), new_offset
|
|
119
114
|
|
|
120
|
-
def _decode_utf8_string(self, size: int, offset: int) ->
|
|
115
|
+
def _decode_utf8_string(self, size: int, offset: int) -> tuple[str, int]:
|
|
121
116
|
new_offset = offset + size
|
|
122
117
|
return self._buffer[offset:new_offset].decode("utf-8"), new_offset
|
|
123
118
|
|
|
@@ -137,11 +132,12 @@ class Decoder: # pylint: disable=too-few-public-methods
|
|
|
137
132
|
15: _decode_float,
|
|
138
133
|
}
|
|
139
134
|
|
|
140
|
-
def decode(self, offset: int) ->
|
|
141
|
-
"""Decode a section of the data section starting at offset
|
|
135
|
+
def decode(self, offset: int) -> tuple[Record, int]:
|
|
136
|
+
"""Decode a section of the data section starting at offset.
|
|
142
137
|
|
|
143
138
|
Arguments:
|
|
144
139
|
offset -- the location of the data structure to decode
|
|
140
|
+
|
|
145
141
|
"""
|
|
146
142
|
new_offset = offset + 1
|
|
147
143
|
ctrl_byte = self._buffer[offset]
|
|
@@ -154,19 +150,19 @@ class Decoder: # pylint: disable=too-few-public-methods
|
|
|
154
150
|
decoder = self._type_decoder[type_num]
|
|
155
151
|
except KeyError as ex:
|
|
156
152
|
raise InvalidDatabaseError(
|
|
157
|
-
f"Unexpected type number ({type_num}) encountered"
|
|
153
|
+
f"Unexpected type number ({type_num}) encountered",
|
|
158
154
|
) from ex
|
|
159
155
|
|
|
160
156
|
(size, new_offset) = self._size_from_ctrl_byte(ctrl_byte, new_offset, type_num)
|
|
161
157
|
return decoder(self, size, new_offset)
|
|
162
158
|
|
|
163
|
-
def _read_extended(self, offset: int) ->
|
|
159
|
+
def _read_extended(self, offset: int) -> tuple[int, int]:
|
|
164
160
|
next_byte = self._buffer[offset]
|
|
165
161
|
type_num = next_byte + 7
|
|
166
162
|
if type_num < 7:
|
|
167
163
|
raise InvalidDatabaseError(
|
|
168
164
|
"Something went horribly wrong in the decoder. An "
|
|
169
|
-
f"extended type resolved to a type number < 8 ({type_num})"
|
|
165
|
+
f"extended type resolved to a type number < 8 ({type_num})",
|
|
170
166
|
)
|
|
171
167
|
return type_num, offset + 1
|
|
172
168
|
|
|
@@ -175,12 +171,15 @@ class Decoder: # pylint: disable=too-few-public-methods
|
|
|
175
171
|
if expected != actual:
|
|
176
172
|
raise InvalidDatabaseError(
|
|
177
173
|
"The MaxMind DB file's data section contains bad data "
|
|
178
|
-
"(unknown data type or corrupt data)"
|
|
174
|
+
"(unknown data type or corrupt data)",
|
|
179
175
|
)
|
|
180
176
|
|
|
181
177
|
def _size_from_ctrl_byte(
|
|
182
|
-
self,
|
|
183
|
-
|
|
178
|
+
self,
|
|
179
|
+
ctrl_byte: int,
|
|
180
|
+
offset: int,
|
|
181
|
+
type_num: int,
|
|
182
|
+
) -> tuple[int, int]:
|
|
184
183
|
size = ctrl_byte & 0x1F
|
|
185
184
|
if type_num == 1 or size < 29:
|
|
186
185
|
return size, offset
|
|
@@ -1,27 +1,24 @@
|
|
|
1
|
-
"""
|
|
2
|
-
maxminddb.extension
|
|
3
|
-
~~~~~~~~~~~~~~~~
|
|
4
|
-
|
|
5
|
-
This module contains the C extension database reader and related classes.
|
|
6
|
-
|
|
7
|
-
"""
|
|
1
|
+
"""C extension database reader and related classes."""
|
|
8
2
|
|
|
3
|
+
# pylint: disable=E0601,E0602
|
|
9
4
|
from ipaddress import IPv4Address, IPv6Address
|
|
10
5
|
from os import PathLike
|
|
11
|
-
from typing import Any, AnyStr,
|
|
12
|
-
|
|
6
|
+
from typing import IO, Any, AnyStr, Optional, Union
|
|
7
|
+
|
|
8
|
+
from maxminddb.const import MODE_AUTO
|
|
13
9
|
from maxminddb.types import Record
|
|
14
10
|
|
|
15
11
|
class Reader:
|
|
16
|
-
"""
|
|
17
|
-
A C extension implementation of a reader for the MaxMind DB format. IP
|
|
12
|
+
"""A C extension implementation of a reader for the MaxMind DB format. IP
|
|
18
13
|
addresses can be looked up using the ``get`` method.
|
|
19
14
|
"""
|
|
20
15
|
|
|
21
16
|
closed: bool = ...
|
|
22
17
|
|
|
23
18
|
def __init__(
|
|
24
|
-
self,
|
|
19
|
+
self,
|
|
20
|
+
database: Union[AnyStr, int, PathLike, IO],
|
|
21
|
+
mode: int = MODE_AUTO,
|
|
25
22
|
) -> None:
|
|
26
23
|
"""Reader for the MaxMind DB file format
|
|
27
24
|
|
|
@@ -30,6 +27,7 @@ class Reader:
|
|
|
30
27
|
file, or a file descriptor in the case of MODE_FD.
|
|
31
28
|
mode -- mode to open the database with. The only supported modes are
|
|
32
29
|
MODE_AUTO and MODE_MMAP_EXT.
|
|
30
|
+
|
|
33
31
|
"""
|
|
34
32
|
|
|
35
33
|
def close(self) -> None:
|
|
@@ -38,25 +36,26 @@ class Reader:
|
|
|
38
36
|
def get(self, ip_address: Union[str, IPv6Address, IPv4Address]) -> Optional[Record]:
|
|
39
37
|
"""Return the record for the ip_address in the MaxMind DB
|
|
40
38
|
|
|
41
|
-
|
|
42
39
|
Arguments:
|
|
43
40
|
ip_address -- an IP address in the standard string notation
|
|
41
|
+
|
|
44
42
|
"""
|
|
45
43
|
|
|
46
44
|
def get_with_prefix_len(
|
|
47
|
-
self,
|
|
48
|
-
|
|
45
|
+
self,
|
|
46
|
+
ip_address: Union[str, IPv6Address, IPv4Address],
|
|
47
|
+
) -> tuple[Optional[Record], int]:
|
|
49
48
|
"""Return a tuple with the record and the associated prefix length
|
|
50
49
|
|
|
51
|
-
|
|
52
50
|
Arguments:
|
|
53
51
|
ip_address -- an IP address in the standard string notation
|
|
52
|
+
|
|
54
53
|
"""
|
|
55
54
|
|
|
56
|
-
def metadata(self) ->
|
|
55
|
+
def metadata(self) -> Metadata:
|
|
57
56
|
"""Return the metadata associated with the MaxMind DB file"""
|
|
58
57
|
|
|
59
|
-
def __enter__(self) ->
|
|
58
|
+
def __enter__(self) -> Reader: ...
|
|
60
59
|
def __exit__(self, *args) -> None: ...
|
|
61
60
|
|
|
62
61
|
# pylint: disable=too-few-public-methods
|
|
@@ -85,7 +84,7 @@ class Metadata:
|
|
|
85
84
|
A string identifying the database type, e.g., "GeoIP2-City".
|
|
86
85
|
"""
|
|
87
86
|
|
|
88
|
-
description:
|
|
87
|
+
description: dict[str, str]
|
|
89
88
|
"""
|
|
90
89
|
A map from locales to text descriptions of the database.
|
|
91
90
|
"""
|
|
@@ -97,7 +96,7 @@ class Metadata:
|
|
|
97
96
|
both IPv4 and IPv6 lookups.
|
|
98
97
|
"""
|
|
99
98
|
|
|
100
|
-
languages:
|
|
99
|
+
languages: list[str]
|
|
101
100
|
"""
|
|
102
101
|
A list of locale codes supported by the databse.
|
|
103
102
|
"""
|
|
@@ -11,7 +11,7 @@ except ImportError:
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class FileBuffer:
|
|
14
|
-
"""A slice-able file reader"""
|
|
14
|
+
"""A slice-able file reader."""
|
|
15
15
|
|
|
16
16
|
def __init__(self, database: str) -> None:
|
|
17
17
|
# pylint: disable=consider-using-with
|
|
@@ -28,31 +28,31 @@ class FileBuffer:
|
|
|
28
28
|
raise TypeError("Invalid argument type.")
|
|
29
29
|
|
|
30
30
|
def rfind(self, needle: bytes, start: int) -> int:
|
|
31
|
-
"""Reverse find needle from start"""
|
|
31
|
+
"""Reverse find needle from start."""
|
|
32
32
|
pos = self._read(self._size - start - 1, start).rfind(needle)
|
|
33
33
|
if pos == -1:
|
|
34
34
|
return pos
|
|
35
35
|
return start + pos
|
|
36
36
|
|
|
37
37
|
def size(self) -> int:
|
|
38
|
-
"""Size of file"""
|
|
38
|
+
"""Size of file."""
|
|
39
39
|
return self._size
|
|
40
40
|
|
|
41
41
|
def close(self) -> None:
|
|
42
|
-
"""Close file"""
|
|
42
|
+
"""Close file."""
|
|
43
43
|
self._handle.close()
|
|
44
44
|
|
|
45
45
|
if hasattr(os, "pread"):
|
|
46
46
|
|
|
47
47
|
def _read(self, buffersize: int, offset: int) -> bytes:
|
|
48
|
-
"""
|
|
48
|
+
"""Read that uses pread."""
|
|
49
49
|
# pylint: disable=no-member
|
|
50
50
|
return os.pread(self._handle.fileno(), buffersize, offset) # type: ignore
|
|
51
51
|
|
|
52
52
|
else:
|
|
53
53
|
|
|
54
54
|
def _read(self, buffersize: int, offset: int) -> bytes:
|
|
55
|
-
"""
|
|
55
|
+
"""Read with a lock.
|
|
56
56
|
|
|
57
57
|
This lock is necessary as after a fork, the different processes
|
|
58
58
|
will share the same file table entry, even if we dup the fd, and
|