From d43bae1f5ee075da4bbc3a4e76b4faa5156d35c4 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 17 Sep 2021 13:23:59 -0700 Subject: [PATCH 1/6] Support os.PathLike object in the C extension --- HISTORY.rst | 6 ++++++ extension/maxminddb.c | 21 ++++++++++++++++++--- tests/reader_test.py | 7 +++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 77b4ea4..889a81f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,12 @@ History ------- +2.1.0 +++++++++++++++++++ + +* The C extension now correctly supports objects that implement the + ``os.PathLike`` interface. + 2.0.3 (2020-10-16) ++++++++++++++++++ diff --git a/extension/maxminddb.c b/extension/maxminddb.c index 3965451..b835cea 100644 --- a/extension/maxminddb.c +++ b/extension/maxminddb.c @@ -48,16 +48,27 @@ static int ip_converter(PyObject *obj, struct sockaddr_storage *ip_address); #endif static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) { - char *filename; + PyObject *filepath = NULL; int mode = 0; static char *kwlist[] = {"database", "mode", NULL}; - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "s|i", kwlist, &filename, &mode)) { + if (!PyArg_ParseTupleAndKeywords(args, + kwds, + "O&|i", + kwlist, + PyUnicode_FSConverter, + &filepath, + &mode)) { + return -1; + } + + char *filename = PyBytes_AS_STRING(filepath); + if (filename == NULL) { return -1; } if (mode != 0 && mode != 1) { + Py_XDECREF(filepath); PyErr_Format( PyExc_ValueError, "Unsupported open mode (%i). Only " @@ -67,6 +78,7 @@ static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) { } if (0 != access(filename, R_OK)) { + Py_XDECREF(filepath); PyErr_Format(PyExc_FileNotFoundError, "No such file or directory: '%s'", filename); @@ -75,18 +87,21 @@ static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) { MMDB_s *mmdb = (MMDB_s *)malloc(sizeof(MMDB_s)); if (NULL == mmdb) { + Py_XDECREF(filepath); PyErr_NoMemory(); return -1; } Reader_obj *mmdb_obj = (Reader_obj *)self; if (!mmdb_obj) { + Py_XDECREF(filepath); free(mmdb); PyErr_NoMemory(); return -1; } uint16_t status = MMDB_open(filename, MMDB_MODE_MMAP, mmdb); + Py_XDECREF(filepath); if (MMDB_SUCCESS != status) { free(mmdb); diff --git a/tests/reader_test.py b/tests/reader_test.py index 61bf223..0c9efdc 100644 --- a/tests/reader_test.py +++ b/tests/reader_test.py @@ -3,6 +3,7 @@ import ipaddress import os +import pathlib import threading import unittest import unittest.mock as mock @@ -242,6 +243,12 @@ def test_ipv6_address_in_ipv4_database(self): reader.get(self.ipf("2001::")) reader.close() + def test_openning_path(self): + with open_database( + pathlib.Path("tests/data/test-data/MaxMind-DB-test-decoder.mmdb"), self.mode + ) as reader: + self.assertEqual(reader.metadata().database_type, "MaxMind DB Decoder Test") + def test_no_extension_exception(self): real_extension = maxminddb.extension maxminddb.extension = None From 326dec0a3377ab19b54f27487b4451b085bf21e8 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 17 Sep 2021 13:39:28 -0700 Subject: [PATCH 2/6] Use f-string as suggested by pylint --- maxminddb/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maxminddb/reader.py b/maxminddb/reader.py index 865dbca..e1d531d 100644 --- a/maxminddb/reader.py +++ b/maxminddb/reader.py @@ -331,5 +331,5 @@ def search_tree_size(self) -> int: return self.node_count * self.node_byte_size def __repr__(self): - args = ", ".join("%s=%r" % x for x in self.__dict__.items()) + args = ", ".join(f"{k}={v}" for k, v in self.__dict__.items()) return f"{self.__module__}.{self.__class__.__name__}({args})" From 6610678ac284ff262ccf216a23b1629b32d57e16 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 17 Sep 2021 14:18:40 -0700 Subject: [PATCH 3/6] Add the Metadata class to the C extension module under the correct name --- HISTORY.rst | 3 +++ extension/maxminddb.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 889a81f..aedb7ab 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -8,6 +8,9 @@ History * The C extension now correctly supports objects that implement the ``os.PathLike`` interface. +* The ``Metadata`` class object is now available from the C extension + module as ``maxminddb.extension.Metadata`` rather than + ``maxminddb.extension.extension``. 2.0.3 (2020-10-16) ++++++++++++++++++ diff --git a/extension/maxminddb.c b/extension/maxminddb.c index b835cea..065ef9b 100644 --- a/extension/maxminddb.c +++ b/extension/maxminddb.c @@ -738,7 +738,7 @@ PyMODINIT_FUNC PyInit_extension(void) { if (PyType_Ready(&Metadata_Type)) { return NULL; } - PyModule_AddObject(m, "extension", (PyObject *)&Metadata_Type); + PyModule_AddObject(m, "Metadata", (PyObject *)&Metadata_Type); PyObject *error_mod = PyImport_ImportModule("maxminddb.errors"); if (error_mod == NULL) { From 3348e693dd60ee35b5ddb1cad1504ac0247a3f89 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 17 Sep 2021 14:27:43 -0700 Subject: [PATCH 4/6] Add type stubs for maxminddb.extension --- HISTORY.rst | 1 + maxminddb/extension.pyi | 44 +++++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 maxminddb/extension.pyi diff --git a/HISTORY.rst b/HISTORY.rst index aedb7ab..feea2e0 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -11,6 +11,7 @@ History * The ``Metadata`` class object is now available from the C extension module as ``maxminddb.extension.Metadata`` rather than ``maxminddb.extension.extension``. +* Type stubs have been added for ``maxminddb.extension``. 2.0.3 (2020-10-16) ++++++++++++++++++ diff --git a/maxminddb/extension.pyi b/maxminddb/extension.pyi new file mode 100644 index 0000000..1345fa7 --- /dev/null +++ b/maxminddb/extension.pyi @@ -0,0 +1,44 @@ +from ipaddress import IPv4Address, IPv6Address +from os import PathLike +from typing import Any, AnyStr, IO, Mapping, Optional, Sequence, Text, Tuple, Union + +from maxminddb import MODE_AUTO +from maxminddb.errors import InvalidDatabaseError as InvalidDatabaseError +from maxminddb.types import Record + +class Reader: + closed: bool = ... + def __init__( + self, database: Union[AnyStr, int, PathLike, IO], mode: int = MODE_AUTO + ) -> None: ... + def close(self) -> None: ... + def get( + self, ip_address: Union[str, IPv6Address, IPv4Address] + ) -> Optional[Record]: ... + def get_with_prefix_len( + self, ip_address: Union[str, IPv6Address, IPv4Address] + ) -> Tuple[Optional[Record], int]: ... + def metadata(self) -> "Metadata": ... + def __enter__(self) -> "Reader": ... + def __exit__(self, *args) -> None: ... + +class Metadata: + @property + def node_count(self) -> int: ... + @property + def record_size(self) -> int: ... + @property + def ip_version(self) -> int: ... + @property + def database_type(self) -> Text: ... + @property + def languages(self) -> Sequence[Text]: ... + @property + def binary_format_major_version(self) -> int: ... + @property + def binary_format_minor_version(self) -> int: ... + @property + def build_epoch(self) -> int: ... + @property + def description(self) -> Mapping[Text, Text]: ... + def __init__(self, **kwargs: Any) -> None: ... diff --git a/setup.py b/setup.py index e23d9e0..40c1ea0 100644 --- a/setup.py +++ b/setup.py @@ -107,7 +107,7 @@ def run_setup(with_cext): long_description=README, url="http://www.maxmind.com/", packages=find_packages("."), - package_data={"": ["LICENSE"], "maxminddb": ["py.typed"]}, + package_data={"": ["LICENSE"], "maxminddb": ["extension.pyi", "py.typed"]}, package_dir={"maxminddb": "maxminddb"}, python_requires=">=3.6", include_package_data=True, From b19abb37ba7bceb436f012f775d99df51b4b3d02 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 17 Sep 2021 15:29:13 -0700 Subject: [PATCH 5/6] Use correct OSError on access issues --- HISTORY.rst | 2 ++ extension/maxminddb.c | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index feea2e0..d71cc95 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -8,6 +8,8 @@ History * The C extension now correctly supports objects that implement the ``os.PathLike`` interface. +* When opening a database fails due to an access issue, the correct + ``OSError`` subclass will now be thrown. * The ``Metadata`` class object is now available from the C extension module as ``maxminddb.extension.Metadata`` rather than ``maxminddb.extension.extension``. diff --git a/extension/maxminddb.c b/extension/maxminddb.c index 065ef9b..8321c5d 100644 --- a/extension/maxminddb.c +++ b/extension/maxminddb.c @@ -78,10 +78,9 @@ static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) { } if (0 != access(filename, R_OK)) { + + PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, filepath); Py_XDECREF(filepath); - PyErr_Format(PyExc_FileNotFoundError, - "No such file or directory: '%s'", - filename); return -1; } From c1e1241368d00e8f3228d8e1562e7fac00420bac Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 17 Sep 2021 15:29:43 -0700 Subject: [PATCH 6/6] Correct spelling --- tests/reader_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reader_test.py b/tests/reader_test.py index 0c9efdc..30acdbc 100644 --- a/tests/reader_test.py +++ b/tests/reader_test.py @@ -243,7 +243,7 @@ def test_ipv6_address_in_ipv4_database(self): reader.get(self.ipf("2001::")) reader.close() - def test_openning_path(self): + def test_opening_path(self): with open_database( pathlib.Path("tests/data/test-data/MaxMind-DB-test-decoder.mmdb"), self.mode ) as reader: