Skip to content

Commit c631155

Browse files
authored
Merge pull request #85 from maxmind/greg/path-lib
Support os.PathLike object in the C extension
2 parents c37eed5 + c1e1241 commit c631155

File tree

6 files changed

+86
-9
lines changed

6 files changed

+86
-9
lines changed

HISTORY.rst

+12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
History
44
-------
55

6+
2.1.0
7+
++++++++++++++++++
8+
9+
* The C extension now correctly supports objects that implement the
10+
``os.PathLike`` interface.
11+
* When opening a database fails due to an access issue, the correct
12+
``OSError`` subclass will now be thrown.
13+
* The ``Metadata`` class object is now available from the C extension
14+
module as ``maxminddb.extension.Metadata`` rather than
15+
``maxminddb.extension.extension``.
16+
* Type stubs have been added for ``maxminddb.extension``.
17+
618
2.0.3 (2020-10-16)
719
++++++++++++++++++
820

extension/maxminddb.c

+21-7
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,27 @@ static int ip_converter(PyObject *obj, struct sockaddr_storage *ip_address);
4848
#endif
4949

5050
static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) {
51-
char *filename;
51+
PyObject *filepath = NULL;
5252
int mode = 0;
5353

5454
static char *kwlist[] = {"database", "mode", NULL};
55-
if (!PyArg_ParseTupleAndKeywords(
56-
args, kwds, "s|i", kwlist, &filename, &mode)) {
55+
if (!PyArg_ParseTupleAndKeywords(args,
56+
kwds,
57+
"O&|i",
58+
kwlist,
59+
PyUnicode_FSConverter,
60+
&filepath,
61+
&mode)) {
62+
return -1;
63+
}
64+
65+
char *filename = PyBytes_AS_STRING(filepath);
66+
if (filename == NULL) {
5767
return -1;
5868
}
5969

6070
if (mode != 0 && mode != 1) {
71+
Py_XDECREF(filepath);
6172
PyErr_Format(
6273
PyExc_ValueError,
6374
"Unsupported open mode (%i). Only "
@@ -67,26 +78,29 @@ static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) {
6778
}
6879

6980
if (0 != access(filename, R_OK)) {
70-
PyErr_Format(PyExc_FileNotFoundError,
71-
"No such file or directory: '%s'",
72-
filename);
81+
82+
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, filepath);
83+
Py_XDECREF(filepath);
7384
return -1;
7485
}
7586

7687
MMDB_s *mmdb = (MMDB_s *)malloc(sizeof(MMDB_s));
7788
if (NULL == mmdb) {
89+
Py_XDECREF(filepath);
7890
PyErr_NoMemory();
7991
return -1;
8092
}
8193

8294
Reader_obj *mmdb_obj = (Reader_obj *)self;
8395
if (!mmdb_obj) {
96+
Py_XDECREF(filepath);
8497
free(mmdb);
8598
PyErr_NoMemory();
8699
return -1;
87100
}
88101

89102
uint16_t status = MMDB_open(filename, MMDB_MODE_MMAP, mmdb);
103+
Py_XDECREF(filepath);
90104

91105
if (MMDB_SUCCESS != status) {
92106
free(mmdb);
@@ -723,7 +737,7 @@ PyMODINIT_FUNC PyInit_extension(void) {
723737
if (PyType_Ready(&Metadata_Type)) {
724738
return NULL;
725739
}
726-
PyModule_AddObject(m, "extension", (PyObject *)&Metadata_Type);
740+
PyModule_AddObject(m, "Metadata", (PyObject *)&Metadata_Type);
727741

728742
PyObject *error_mod = PyImport_ImportModule("maxminddb.errors");
729743
if (error_mod == NULL) {

maxminddb/extension.pyi

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from ipaddress import IPv4Address, IPv6Address
2+
from os import PathLike
3+
from typing import Any, AnyStr, IO, Mapping, Optional, Sequence, Text, Tuple, Union
4+
5+
from maxminddb import MODE_AUTO
6+
from maxminddb.errors import InvalidDatabaseError as InvalidDatabaseError
7+
from maxminddb.types import Record
8+
9+
class Reader:
10+
closed: bool = ...
11+
def __init__(
12+
self, database: Union[AnyStr, int, PathLike, IO], mode: int = MODE_AUTO
13+
) -> None: ...
14+
def close(self) -> None: ...
15+
def get(
16+
self, ip_address: Union[str, IPv6Address, IPv4Address]
17+
) -> Optional[Record]: ...
18+
def get_with_prefix_len(
19+
self, ip_address: Union[str, IPv6Address, IPv4Address]
20+
) -> Tuple[Optional[Record], int]: ...
21+
def metadata(self) -> "Metadata": ...
22+
def __enter__(self) -> "Reader": ...
23+
def __exit__(self, *args) -> None: ...
24+
25+
class Metadata:
26+
@property
27+
def node_count(self) -> int: ...
28+
@property
29+
def record_size(self) -> int: ...
30+
@property
31+
def ip_version(self) -> int: ...
32+
@property
33+
def database_type(self) -> Text: ...
34+
@property
35+
def languages(self) -> Sequence[Text]: ...
36+
@property
37+
def binary_format_major_version(self) -> int: ...
38+
@property
39+
def binary_format_minor_version(self) -> int: ...
40+
@property
41+
def build_epoch(self) -> int: ...
42+
@property
43+
def description(self) -> Mapping[Text, Text]: ...
44+
def __init__(self, **kwargs: Any) -> None: ...

maxminddb/reader.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -331,5 +331,5 @@ def search_tree_size(self) -> int:
331331
return self.node_count * self.node_byte_size
332332

333333
def __repr__(self):
334-
args = ", ".join("%s=%r" % x for x in self.__dict__.items())
334+
args = ", ".join(f"{k}={v}" for k, v in self.__dict__.items())
335335
return f"{self.__module__}.{self.__class__.__name__}({args})"

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def run_setup(with_cext):
107107
long_description=README,
108108
url="http://www.maxmind.com/",
109109
packages=find_packages("."),
110-
package_data={"": ["LICENSE"], "maxminddb": ["py.typed"]},
110+
package_data={"": ["LICENSE"], "maxminddb": ["extension.pyi", "py.typed"]},
111111
package_dir={"maxminddb": "maxminddb"},
112112
python_requires=">=3.6",
113113
include_package_data=True,

tests/reader_test.py

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import ipaddress
55
import os
6+
import pathlib
67
import threading
78
import unittest
89
import unittest.mock as mock
@@ -242,6 +243,12 @@ def test_ipv6_address_in_ipv4_database(self):
242243
reader.get(self.ipf("2001::"))
243244
reader.close()
244245

246+
def test_opening_path(self):
247+
with open_database(
248+
pathlib.Path("tests/data/test-data/MaxMind-DB-test-decoder.mmdb"), self.mode
249+
) as reader:
250+
self.assertEqual(reader.metadata().database_type, "MaxMind DB Decoder Test")
251+
245252
def test_no_extension_exception(self):
246253
real_extension = maxminddb.extension
247254
maxminddb.extension = None

0 commit comments

Comments
 (0)