Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-geoip2 for openSUSE:Factory checked in at 2026-07-23 23:08:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-geoip2 (Old) and /work/SRC/openSUSE:Factory/.python-geoip2.new.2004 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-geoip2" Thu Jul 23 23:08:51 2026 rev:25 rq:1366937 version:5.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-geoip2/python-geoip2.changes 2026-02-02 14:55:59.964560008 +0100 +++ /work/SRC/openSUSE:Factory/.python-geoip2.new.2004/python-geoip2.changes 2026-07-23 23:09:24.230309370 +0200 @@ -1,0 +2,19 @@ +Tue Jul 21 11:32:46 UTC 2026 - Paolo Stivanin <[email protected]> + +- Update to 5.3.0: + * The version is now retrieved from package metadata at runtime using + importlib.metadata. This reduces the chance of version inconsistencies + during releases. + * The async client now builds its Authorization header with + aiohttp.encode_basic_auth() instead of the aiohttp.BasicAuth / + auth= parameter, which are deprecated as of aiohttp 3.14.0. As a result, + the minimum required aiohttp version is now 3.14.0. + * A new residential attribute has been added to + geoip2.records.Anonymizer. This is a geoip2.records.AnonymizerFeed + object providing residential proxy data for the network and contains the + following fields: confidence, network_last_seen, and + provider_name. This attribute may be populated even when no other + anonymizer attributes are set, so the anonymizer object may now + contain only this attribute. + +------------------------------------------------------------------- Old: ---- python-geoip2-5.2.0.tar.gz New: ---- python-geoip2-5.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-geoip2.spec ++++++ --- /var/tmp/diff_new_pack.QP1Kst/_old 2026-07-23 23:09:24.790329035 +0200 +++ /var/tmp/diff_new_pack.QP1Kst/_new 2026-07-23 23:09:24.794329175 +0200 @@ -18,14 +18,14 @@ %{?sle15_python_module_pythons} Name: python-geoip2 -Version: 5.2.0 +Version: 5.3.0 Release: 0 Summary: MaxMind GeoIP2 Python API License: Apache-2.0 Group: Development/Languages/Python URL: https://github.com/maxmind/GeoIP2-python Source: https://files.pythonhosted.org/packages/source/g/geoip2/geoip2-%{version}.tar.gz#/%{name}-%{version}.tar.gz -%if 0%{?suse_version} <= 1600 +%if 0%{?suse_version} <= 1700 # partial reversion of 83825a8 ("Switch to uv build") Patch0: pyproject-setuptools.patch BuildRequires: %{python_module setuptools} ++++++ pyproject-setuptools.patch ++++++ --- /var/tmp/diff_new_pack.QP1Kst/_old 2026-07-23 23:09:24.842330861 +0200 +++ /var/tmp/diff_new_pack.QP1Kst/_new 2026-07-23 23:09:24.846331002 +0200 @@ -1,14 +1,14 @@ ---- a/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 -+++ b/pyproject.toml 2026-01-29 19:40:59.478833382 +0100 -@@ -43,19 +43,11 @@ +Index: geoip2-5.3.0/pyproject.toml +=================================================================== +--- geoip2-5.3.0.orig/pyproject.toml ++++ geoip2-5.3.0/pyproject.toml +@@ -42,19 +42,11 @@ lint = [ ] [build-system] --requires = ["uv_build>=0.7.19,<0.8.0"] +-requires = ["uv_build>=0.11.26,<0.12.0"] -build-backend = "uv_build" -+requires = ["setuptools"] -+build-backend = "setuptools.build_meta" - +- -[tool.uv.build-backend] -source-include = [ - "HISTORY.rst", @@ -19,9 +19,12 @@ - "tests/*.py", - "tests/data/test-data/*.mmdb" -] ++requires = ["setuptools"] ++build-backend = "setuptools.build_meta" ++ +[tool.setuptools.package-data] +geoip2 = ["py.typed"] [project.urls] - Homepage = "https://www.maxmind.com/" + Homepage = "https://www.maxmind.com/en/home" ++++++ python-geoip2-5.2.0.tar.gz -> python-geoip2-5.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/geoip2-5.2.0/HISTORY.rst new/geoip2-5.3.0/HISTORY.rst --- old/geoip2-5.2.0/HISTORY.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/geoip2-5.3.0/HISTORY.rst 1970-01-01 01:00:00.000000000 +0100 @@ -4,6 +4,24 @@ History ------- +5.3.0 (2026-07-16) +++++++++++++++++++ + +* The version is now retrieved from package metadata at runtime using + ``importlib.metadata``. This reduces the chance of version inconsistencies + during releases. +* The async client now builds its ``Authorization`` header with + ``aiohttp.encode_basic_auth()`` instead of the ``aiohttp.BasicAuth`` / + ``auth=`` parameter, which are deprecated as of aiohttp 3.14.0. As a result, + the minimum required ``aiohttp`` version is now 3.14.0. +* A new ``residential`` attribute has been added to + ``geoip2.records.Anonymizer``. This is a ``geoip2.records.AnonymizerFeed`` + object providing residential proxy data for the network and contains the + following fields: ``confidence``, ``network_last_seen``, and + ``provider_name``. This attribute may be populated even when no other + anonymizer attributes are set, so the ``anonymizer`` object may now + contain only this attribute. + 5.2.0 (2025-11-20) ++++++++++++++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/geoip2-5.2.0/PKG-INFO new/geoip2-5.3.0/PKG-INFO --- old/geoip2-5.2.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/geoip2-5.3.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,7 +1,7 @@ Metadata-Version: 2.4 Name: geoip2 -Version: 5.2.0 -Summary: MaxMind GeoIP2 API +Version: 5.3.0 +Summary: MaxMind GeoIP API Author: Gregory Oschwald Author-email: Gregory Oschwald <[email protected]> License-Expression: Apache-2.0 @@ -19,26 +19,26 @@ Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Internet Classifier: Topic :: Internet :: Proxy Servers -Requires-Dist: aiohttp>=3.6.2,<4.0.0 +Requires-Dist: aiohttp>=3.14.1,<4.0.0 Requires-Dist: maxminddb>=3.0.0,<4.0.0 Requires-Dist: requests>=2.24.0,<3.0.0 Requires-Python: >=3.10 -Project-URL: Documentation, https://geoip2.readthedocs.org/ -Project-URL: Homepage, https://www.maxmind.com/ -Project-URL: Issue Tracker, https://github.com/maxmind/GeoIP2-python/issues +Project-URL: Homepage, https://www.maxmind.com/en/home +Project-URL: Documentation, https://geoip2.readthedocs.io/en/latest/ Project-URL: Source Code, https://github.com/maxmind/GeoIP2-python +Project-URL: Issue Tracker, https://github.com/maxmind/GeoIP2-python/issues Description-Content-Type: text/x-rst -========================= -MaxMind GeoIP2 Python API -========================= +======================== +MaxMind GeoIP Python API +======================== Description ----------- -This package provides an API for the GeoIP2 and GeoLite2 `web services -<https://dev.maxmind.com/geoip/docs/web-services?lang=en>`_ and `databases -<https://dev.maxmind.com/geoip/docs/databases?lang=en>`_. +This package provides an API for the GeoIP and GeoLite `web services +<https://dev.maxmind.com/geoip/docs/web-services/?lang=en>`_ and `databases +<https://dev.maxmind.com/geoip/docs/databases/?lang=en>`_. Installation ------------ @@ -68,7 +68,7 @@ -------------------- IP geolocation is inherently imprecise. Locations are often near the center of -the population. Any location provided by a GeoIP2 database or web service +the population. Any location provided by a GeoIP database or web service should not be used to identify a particular address or household. Web Service Usage @@ -76,10 +76,10 @@ To use this API, you first construct either a ``geoip2.webservice.Client`` or ``geoip2.webservice.AsyncClient``, passing your MaxMind ``account_id`` and -``license_key`` to the constructor. To use the GeoLite2 web service instead of -the GeoIP2 web service, set the optional ``host`` keyword argument to -``geolite.info``. To use the Sandbox GeoIP2 web service instead of the -production GeoIP2 web service, set the optional ``host`` keyword argument to +``license_key`` to the constructor. To use the GeoLite web service instead of +the GeoIP web service, set the optional ``host`` keyword argument to +``geolite.info``. To use the Sandbox GeoIP web service instead of the +production GeoIP web service, set the optional ``host`` keyword argument to ``sandbox.maxmind.com``. After doing this, you may call the method corresponding to request type @@ -101,14 +101,14 @@ >>> # This creates a Client object that can be reused across requests. >>> # Replace "42" with your account ID and "license_key" with your license >>> # key. Set the "host" keyword argument to "geolite.info" to use the - >>> # GeoLite2 web service instead of the GeoIP2 web service. Set the + >>> # GeoLite web service instead of the GeoIP web service. Set the >>> # "host" keyword argument to "sandbox.maxmind.com" to use the Sandbox - >>> # GeoIP2 web service instead of the production GeoIP2 web service. + >>> # GeoIP web service instead of the production GeoIP web service. >>> with geoip2.webservice.Client(42, 'license_key') as client: >>> >>> # Replace "city" with the method corresponding to the web service >>> # that you are using, i.e., "country", "city", or "insights". Please - >>> # note that Insights is not supported by the GeoLite2 web service. + >>> # note that Insights is not supported by the GeoLite web service. >>> response = client.city('203.0.113.0') >>> >>> response.country.iso_code @@ -153,14 +153,14 @@ >>> # >>> # Replace "42" with your account ID and "license_key" with your license >>> # key. Set the "host" keyword argument to "geolite.info" to use the - >>> # GeoLite2 web service instead of the GeoIP2 web service. Set the + >>> # GeoLite web service instead of the GeoIP web service. Set the >>> # "host" keyword argument to "sandbox.maxmind.com" to use the Sandbox - >>> # GeoIP2 web service instead of the production GeoIP2 web service. + >>> # GeoIP web service instead of the production GeoIP web service. >>> async with geoip2.webservice.AsyncClient(42, 'license_key') as client: >>> >>> # Replace "city" with the method corresponding to the web service >>> # that you are using, i.e., "country", "city", or "insights". Please - >>> # note that Insights is not supported by the GeoLite2 web service. + >>> # note that Insights is not supported by the GeoLite web service. >>> response = await client.city('203.0.113.0') >>> >>> response.country.iso_code @@ -195,7 +195,7 @@ ----------------------------- For details on the possible errors returned by the web service itself, see -https://dev.maxmind.com/geoip/docs/web-services?lang=en for the GeoIP2 web +https://dev.maxmind.com/geoip/docs/web-services/?lang=en for the GeoIP web service docs. If the web service returns an explicit error document, this is thrown as a @@ -546,7 +546,8 @@ ----------------------- If the problem you find is that an IP address is incorrectly mapped, please -`submit your correction to MaxMind <https://www.maxmind.com/en/correction>`_. +`submit your correction to MaxMind +<https://www.maxmind.com/en/geoip-data-correction-request>`_. If you find some other sort of mistake, like an incorrect spelling, please check the `GeoNames site <https://www.geonames.org/>`_ first. Once you've @@ -557,20 +558,12 @@ If you are a paying MaxMind customer and you're not sure where to submit a correction, please `contact MaxMind support -<https://www.maxmind.com/en/support>`_ for help. - -Requirements ------------- - -Python 3.9 or greater is required. Older versions are not supported. - -The Requests HTTP library is also required. See -<https://pypi.org/project/requests/> for details. +<https://support.maxmind.com/knowledge-base>`_ for help. Versioning ---------- -The GeoIP2 Python API uses `Semantic Versioning <https://semver.org/>`_. +The GeoIP Python API uses `Semantic Versioning <https://semver.org/>`_. Support ------- @@ -580,4 +573,4 @@ If you are having an issue with a MaxMind service that is not specific to the client API, please contact `MaxMind support -<https://www.maxmind.com/en/support>`_ for assistance. +<https://support.maxmind.com/knowledge-base>`_ for assistance. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/geoip2-5.2.0/README.rst new/geoip2-5.3.0/README.rst --- old/geoip2-5.2.0/README.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/geoip2-5.3.0/README.rst 1970-01-01 01:00:00.000000000 +0100 @@ -1,13 +1,13 @@ -========================= -MaxMind GeoIP2 Python API -========================= +======================== +MaxMind GeoIP Python API +======================== Description ----------- -This package provides an API for the GeoIP2 and GeoLite2 `web services -<https://dev.maxmind.com/geoip/docs/web-services?lang=en>`_ and `databases -<https://dev.maxmind.com/geoip/docs/databases?lang=en>`_. +This package provides an API for the GeoIP and GeoLite `web services +<https://dev.maxmind.com/geoip/docs/web-services/?lang=en>`_ and `databases +<https://dev.maxmind.com/geoip/docs/databases/?lang=en>`_. Installation ------------ @@ -37,7 +37,7 @@ -------------------- IP geolocation is inherently imprecise. Locations are often near the center of -the population. Any location provided by a GeoIP2 database or web service +the population. Any location provided by a GeoIP database or web service should not be used to identify a particular address or household. Web Service Usage @@ -45,10 +45,10 @@ To use this API, you first construct either a ``geoip2.webservice.Client`` or ``geoip2.webservice.AsyncClient``, passing your MaxMind ``account_id`` and -``license_key`` to the constructor. To use the GeoLite2 web service instead of -the GeoIP2 web service, set the optional ``host`` keyword argument to -``geolite.info``. To use the Sandbox GeoIP2 web service instead of the -production GeoIP2 web service, set the optional ``host`` keyword argument to +``license_key`` to the constructor. To use the GeoLite web service instead of +the GeoIP web service, set the optional ``host`` keyword argument to +``geolite.info``. To use the Sandbox GeoIP web service instead of the +production GeoIP web service, set the optional ``host`` keyword argument to ``sandbox.maxmind.com``. After doing this, you may call the method corresponding to request type @@ -70,14 +70,14 @@ >>> # This creates a Client object that can be reused across requests. >>> # Replace "42" with your account ID and "license_key" with your license >>> # key. Set the "host" keyword argument to "geolite.info" to use the - >>> # GeoLite2 web service instead of the GeoIP2 web service. Set the + >>> # GeoLite web service instead of the GeoIP web service. Set the >>> # "host" keyword argument to "sandbox.maxmind.com" to use the Sandbox - >>> # GeoIP2 web service instead of the production GeoIP2 web service. + >>> # GeoIP web service instead of the production GeoIP web service. >>> with geoip2.webservice.Client(42, 'license_key') as client: >>> >>> # Replace "city" with the method corresponding to the web service >>> # that you are using, i.e., "country", "city", or "insights". Please - >>> # note that Insights is not supported by the GeoLite2 web service. + >>> # note that Insights is not supported by the GeoLite web service. >>> response = client.city('203.0.113.0') >>> >>> response.country.iso_code @@ -122,14 +122,14 @@ >>> # >>> # Replace "42" with your account ID and "license_key" with your license >>> # key. Set the "host" keyword argument to "geolite.info" to use the - >>> # GeoLite2 web service instead of the GeoIP2 web service. Set the + >>> # GeoLite web service instead of the GeoIP web service. Set the >>> # "host" keyword argument to "sandbox.maxmind.com" to use the Sandbox - >>> # GeoIP2 web service instead of the production GeoIP2 web service. + >>> # GeoIP web service instead of the production GeoIP web service. >>> async with geoip2.webservice.AsyncClient(42, 'license_key') as client: >>> >>> # Replace "city" with the method corresponding to the web service >>> # that you are using, i.e., "country", "city", or "insights". Please - >>> # note that Insights is not supported by the GeoLite2 web service. + >>> # note that Insights is not supported by the GeoLite web service. >>> response = await client.city('203.0.113.0') >>> >>> response.country.iso_code @@ -164,7 +164,7 @@ ----------------------------- For details on the possible errors returned by the web service itself, see -https://dev.maxmind.com/geoip/docs/web-services?lang=en for the GeoIP2 web +https://dev.maxmind.com/geoip/docs/web-services/?lang=en for the GeoIP web service docs. If the web service returns an explicit error document, this is thrown as a @@ -515,7 +515,8 @@ ----------------------- If the problem you find is that an IP address is incorrectly mapped, please -`submit your correction to MaxMind <https://www.maxmind.com/en/correction>`_. +`submit your correction to MaxMind +<https://www.maxmind.com/en/geoip-data-correction-request>`_. If you find some other sort of mistake, like an incorrect spelling, please check the `GeoNames site <https://www.geonames.org/>`_ first. Once you've @@ -526,20 +527,12 @@ If you are a paying MaxMind customer and you're not sure where to submit a correction, please `contact MaxMind support -<https://www.maxmind.com/en/support>`_ for help. - -Requirements ------------- - -Python 3.9 or greater is required. Older versions are not supported. - -The Requests HTTP library is also required. See -<https://pypi.org/project/requests/> for details. +<https://support.maxmind.com/knowledge-base>`_ for help. Versioning ---------- -The GeoIP2 Python API uses `Semantic Versioning <https://semver.org/>`_. +The GeoIP Python API uses `Semantic Versioning <https://semver.org/>`_. Support ------- @@ -549,4 +542,4 @@ If you are having an issue with a MaxMind service that is not specific to the client API, please contact `MaxMind support -<https://www.maxmind.com/en/support>`_ for assistance. +<https://support.maxmind.com/knowledge-base>`_ for assistance. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/geoip2-5.2.0/pyproject.toml new/geoip2-5.3.0/pyproject.toml --- old/geoip2-5.2.0/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/geoip2-5.3.0/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 @@ -1,12 +1,12 @@ [project] name = "geoip2" -version = "5.2.0" -description = "MaxMind GeoIP2 API" +version = "5.3.0" +description = "MaxMind GeoIP API" authors = [ {name = "Gregory Oschwald", email = "[email protected]"}, ] dependencies = [ - "aiohttp>=3.6.2,<4.0.0", + "aiohttp>=3.14.1,<4.0.0", "maxminddb>=3.0.0,<4.0.0", "requests>=2.24.0,<3.0.0", ] @@ -32,18 +32,17 @@ [dependency-groups] dev = [ - "pytest>=8.3.5", + "pytest>=9.1.1", "pytest-httpserver>=1.0.10", "tox-uv>=1.29.0", - "types-requests>=2.32.0.20250328", ] lint = [ "mypy>=1.15.0", - "ruff>=0.11.6", + "ruff>=0.15.20", ] [build-system] -requires = ["uv_build>=0.7.19,<0.8.0"] +requires = ["uv_build>=0.11.26,<0.12.0"] build-backend = "uv_build" [tool.uv.build-backend] @@ -58,8 +57,8 @@ ] [project.urls] -Homepage = "https://www.maxmind.com/" -Documentation = "https://geoip2.readthedocs.org/" +Homepage = "https://www.maxmind.com/en/home" +Documentation = "https://geoip2.readthedocs.io/en/latest/" "Source Code" = "https://github.com/maxmind/GeoIP2-python" "Issue Tracker" = "https://github.com/maxmind/GeoIP2-python/issues" @@ -88,7 +87,7 @@ [tool.ruff.lint.per-file-ignores] "docs/*" = ["ALL"] -"src/geoip2/{models,records}.py" = [ "ANN401", "D107", "PLR0913" ] +"src/geoip2/{models,records}.py" = [ "D107", "PLR0913" ] # FBT003: We use assertIs with boolean literals to verify values are actual # booleans (True/False), not just truthy/falsy values "tests/*" = ["ANN201", "D", "FBT003"] @@ -105,6 +104,7 @@ skip_missing_interpreters = false [tool.tox.env_run_base] +runner = "uv-venv-lock-runner" dependency_groups = [ "dev", ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/geoip2-5.2.0/src/geoip2/__init__.py new/geoip2-5.3.0/src/geoip2/__init__.py --- old/geoip2-5.2.0/src/geoip2/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ new/geoip2-5.3.0/src/geoip2/__init__.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,7 +1,5 @@ """geoip2 client library.""" -__title__ = "geoip2" -__version__ = "5.2.0" -__author__ = "Gregory Oschwald" -__license__ = "Apache License, Version 2.0" -__copyright__ = "Copyright (c) 2013-2025 MaxMind, Inc." +from importlib.metadata import version + +__version__ = version("geoip2") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/geoip2-5.2.0/src/geoip2/database.py new/geoip2-5.3.0/src/geoip2/database.py --- old/geoip2-5.2.0/src/geoip2/database.py 1970-01-01 01:00:00.000000000 +0100 +++ new/geoip2-5.3.0/src/geoip2/database.py 1970-01-01 01:00:00.000000000 +0100 @@ -51,9 +51,9 @@ class Reader: - """GeoIP2 database Reader object. + """GeoIP database Reader object. - Instances of this class provide a reader for the GeoIP2 database format. + Instances of this class provide a reader for the GeoIP database format. IP addresses can be looked up using the ``country`` and ``city`` methods. The basic API for this class is the same for every database. First, you @@ -81,9 +81,9 @@ locales: Sequence[str] | None = None, mode: int = MODE_AUTO, ) -> None: - """Create GeoIP2 Reader. + """Create GeoIP Reader. - :param fileish: A path to the GeoIP2 database or an existing file + :param fileish: A path to the GeoIP database or an existing file descriptor pointing to the database. Note that a file descriptor is only valid when mode is MODE_FD. :param locales: This is list of locale codes. This argument will be @@ -94,7 +94,7 @@ multiple names (country, city, etc.), its name property will return the name in the first locale that has one. - Note that the only locale which is always present in the GeoIP2 + Note that the only locale which is always present in the GeoIP data is "en". If you do not include this locale, the name property may end up returning None even when the record has an English name. @@ -314,5 +314,5 @@ return self._db_reader.metadata() def close(self) -> None: - """Close the GeoIP2 database.""" + """Close the GeoIP database.""" self._db_reader.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/geoip2-5.2.0/src/geoip2/models.py new/geoip2-5.3.0/src/geoip2/models.py --- old/geoip2-5.2.0/src/geoip2/models.py 1970-01-01 01:00:00.000000000 +0100 +++ new/geoip2-5.3.0/src/geoip2/models.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,8 +1,8 @@ -"""The models for response from th GeoIP2 web service and databases. +"""The models for response from the GeoIP web service and databases. The only difference between the City and Insights model classes is which fields in each record may be populated. See -https://dev.maxmind.com/geoip/docs/web-services?lang=en for more details. +https://dev.maxmind.com/geoip/docs/web-services/?lang=en for more details. """ from __future__ import annotations @@ -63,7 +63,7 @@ registered_country: dict[str, Any] | None = None, represented_country: dict[str, Any] | None = None, traits: dict[str, Any] | None = None, - **_: Any, + **_: object, ) -> None: self._locales = locales self.continent = geoip2.records.Continent(locales, **(continent or {})) @@ -127,7 +127,7 @@ represented_country: dict[str, Any] | None = None, subdivisions: list[dict[str, Any]] | None = None, traits: dict[str, Any] | None = None, - **_: Any, + **_: object, ) -> None: super().__init__( locales, @@ -147,7 +147,7 @@ class Insights(City): - """Model for the GeoIP2 Insights web service.""" + """Model for the GeoIP Insights web service.""" anonymizer: geoip2.records.Anonymizer """Anonymizer object for the requested IP address. This object contains @@ -171,7 +171,7 @@ represented_country: dict[str, Any] | None = None, subdivisions: list[dict[str, Any]] | None = None, traits: dict[str, Any] | None = None, - **_: Any, + **_: object, ) -> None: super().__init__( locales, @@ -192,7 +192,7 @@ class Enterprise(City): - """Model for the GeoIP2 Enterprise database.""" + """Model for the GeoIP Enterprise database.""" class SimpleModel(Model, metaclass=ABCMeta): @@ -259,7 +259,7 @@ class AnonymousIP(SimpleModel): - """Model class for the GeoIP2 Anonymous IP.""" + """Model class for the GeoIP Anonymous IP.""" is_anonymous: bool """This is true if the IP address belongs to any sort of anonymous network.""" @@ -301,7 +301,7 @@ is_tor_exit_node: bool = False, network: str | None = None, prefix_len: int | None = None, - **_: Any, + **_: object, ) -> None: super().__init__(ip_address, network, prefix_len) self.is_anonymous = is_anonymous @@ -345,7 +345,7 @@ network_last_seen: str | None = None, prefix_len: int | None = None, provider_name: str | None = None, - **_: Any, + **_: object, ) -> None: super().__init__( is_anonymous=is_anonymous, @@ -365,7 +365,7 @@ class ASN(SimpleModel): - """Model class for the GeoLite2 ASN.""" + """Model class for the GeoLite ASN.""" autonomous_system_number: int | None """The autonomous system number associated with the IP address.""" @@ -383,7 +383,7 @@ autonomous_system_organization: str | None = None, network: str | None = None, prefix_len: int | None = None, - **_: Any, + **_: object, ) -> None: super().__init__(ip_address, network, prefix_len) self.autonomous_system_number = autonomous_system_number @@ -391,7 +391,7 @@ class ConnectionType(SimpleModel): - """Model class for the GeoIP2 Connection-Type.""" + """Model class for the GeoIP Connection-Type.""" connection_type: str | None """The connection type may take the following values: @@ -412,14 +412,14 @@ connection_type: str | None = None, network: str | None = None, prefix_len: int | None = None, - **_: Any, + **_: object, ) -> None: super().__init__(ip_address, network, prefix_len) self.connection_type = connection_type class Domain(SimpleModel): - """Model class for the GeoIP2 Domain.""" + """Model class for the GeoIP Domain.""" domain: str | None """The domain associated with the IP address.""" @@ -431,14 +431,14 @@ domain: str | None = None, network: str | None = None, prefix_len: int | None = None, - **_: Any, + **_: object, ) -> None: super().__init__(ip_address, network, prefix_len) self.domain = domain class ISP(ASN): - """Model class for the GeoIP2 ISP.""" + """Model class for the GeoIP ISP.""" isp: str | None """The name of the ISP associated with the IP address.""" @@ -470,7 +470,7 @@ organization: str | None = None, network: str | None = None, prefix_len: int | None = None, - **_: Any, + **_: object, ) -> None: super().__init__( autonomous_system_number=autonomous_system_number, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/geoip2-5.2.0/src/geoip2/records.py new/geoip2-5.3.0/src/geoip2/records.py --- old/geoip2-5.2.0/src/geoip2/records.py 1970-01-01 01:00:00.000000000 +0100 +++ new/geoip2-5.3.0/src/geoip2/records.py 1970-01-01 01:00:00.000000000 +0100 @@ -74,7 +74,7 @@ confidence: int | None = None, geoname_id: int | None = None, names: dict[str, str] | None = None, - **_: Any, + **_: object, ) -> None: self.confidence = confidence self.geoname_id = geoname_id @@ -102,7 +102,7 @@ code: str | None = None, geoname_id: int | None = None, names: dict[str, str] | None = None, - **_: Any, + **_: object, ) -> None: self.code = code self.geoname_id = geoname_id @@ -139,7 +139,7 @@ is_in_european_union: bool = False, iso_code: str | None = None, names: dict[str, str] | None = None, - **_: Any, + **_: object, ) -> None: self.confidence = confidence self.geoname_id = geoname_id @@ -172,7 +172,7 @@ iso_code: str | None = None, names: dict[str, str] | None = None, type: str | None = None, # noqa: A002 - **_: Any, + **_: object, ) -> None: self.type = type super().__init__( @@ -239,7 +239,7 @@ metro_code: int | None = None, population_density: int | None = None, time_zone: str | None = None, - **_: Any, + **_: object, ) -> None: self.average_income = average_income self.accuracy_radius = accuracy_radius @@ -258,15 +258,63 @@ calling. """ - def __init__(self, *, queries_remaining: int | None = None, **_: Any) -> None: + def __init__(self, *, queries_remaining: int | None = None, **_: object) -> None: self.queries_remaining = queries_remaining +class AnonymizerFeed(Record): + """Contains data for one type of anonymizer detection. + + This class contains data for one type of anonymizer detection, + currently residential proxies. Additional feeds may be added in the + future. + + This record is returned by ``insights`` as the ``residential`` attribute + of :py:class:`Anonymizer`. + """ + + confidence: int | None + """A score ranging from 1 to 99 that represents our percent confidence + that the network is currently part of this anonymizer feed. This + attribute is only available from the Insights end point. + """ + + network_last_seen: datetime.date | None + """The last day that the network was sighted in our analysis of this + anonymizer feed. This attribute is only available from the Insights end + point. + """ + + provider_name: str | None + """The name of the provider associated with the network in this + anonymizer feed. This attribute is only available from the Insights end + point. + """ + + def __init__( + self, + *, + confidence: int | None = None, + network_last_seen: str | None = None, + provider_name: str | None = None, + **_: object, + ) -> None: + self.confidence = confidence + self.network_last_seen = ( + datetime.date.fromisoformat(network_last_seen) + if network_last_seen + else None + ) + self.provider_name = provider_name + + class Anonymizer(Record): """Contains data for the anonymizer record associated with an IP address. This class contains the anonymizer data associated with an IP address. + Note that this object may contain only the ``residential`` attribute. + This record is returned by ``insights``. """ @@ -288,6 +336,12 @@ point. """ + residential: AnonymizerFeed + """Residential proxy data for the network associated with the IP address. + This may be populated even when no other anonymizer attributes are set. + This attribute is only available from the Insights end point. + """ + is_anonymous: bool """This is true if the IP address belongs to any sort of anonymous network. This attribute is only available from the Insights end point. @@ -337,7 +391,8 @@ is_tor_exit_node: bool = False, network_last_seen: str | None = None, provider_name: str | None = None, - **_: Any, + residential: dict[str, Any] | None = None, + **_: object, ) -> None: self.confidence = confidence self.is_anonymous = is_anonymous @@ -352,6 +407,7 @@ else None ) self.provider_name = provider_name + self.residential = AnonymizerFeed(**(residential or {})) class Postal(Record): @@ -378,7 +434,7 @@ *, code: str | None = None, confidence: int | None = None, - **_: Any, + **_: object, ) -> None: self.code = code self.confidence = confidence @@ -412,7 +468,7 @@ geoname_id: int | None = None, iso_code: str | None = None, names: dict[str, str] | None = None, - **_: Any, + **_: object, ) -> None: self.confidence = confidence self.geoname_id = geoname_id @@ -527,6 +583,11 @@ responsive to traffic on your network. If you need realtime IP risk scoring based on behavioral signals on your own network, please use minFraud. + We do not provide an IP risk snapshot for low-risk networks. If this field + is not populated, we either do not have signals for the network or the + signals we have show that the network is low-risk. If you would like to get + signals for low-risk networks, please use the minFraud web services. + This attribute is only available from the Insights end point. """ _ip_address: IPAddress | None @@ -541,8 +602,8 @@ """This is true if the IP is an anonymous proxy. .. deprecated:: 2.2.0 - Use our `GeoIP2 Anonymous IP database - <https://www.maxmind.com/en/geoip2-anonymous-ip-database GeoIP2>`_ + Use our `GeoIP Anonymous IP database + <https://www.maxmind.com/en/geoip-anonymous-ip-database>`_ instead. """ is_anonymous_vpn: bool @@ -561,8 +622,8 @@ is_anycast: bool """This returns true if the IP address belongs to an `anycast network <https://en.wikipedia.org/wiki/Anycast>`_. - This is available for the GeoIP2 Country, City Plus, and Insights - web services and the GeoIP2 Country, City, and Enterprise databases. + This is available for the GeoIP Country, City Plus, and Insights + web services and the GeoIP Country, City, and Enterprise databases. """ is_hosting_provider: bool """This is true if the IP address belongs to a hosting or VPN provider @@ -703,7 +764,7 @@ mobile_country_code: str | None = None, mobile_network_code: str | None = None, is_anycast: bool = False, - **_: Any, + **_: object, ) -> None: self.autonomous_system_number = autonomous_system_number self.autonomous_system_organization = autonomous_system_organization diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/geoip2-5.2.0/src/geoip2/webservice.py new/geoip2-5.3.0/src/geoip2/webservice.py --- old/geoip2-5.2.0/src/geoip2/webservice.py 1970-01-01 01:00:00.000000000 +0100 +++ new/geoip2-5.3.0/src/geoip2/webservice.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,4 +1,4 @@ -"""Client for GeoIP2 and GeoLite2 web services. +"""Client for GeoIP and GeoLite web services. The web services are Country, City Plus, and Insights. Each service returns a different set of data about an IP address, with Country returning the least @@ -230,22 +230,22 @@ class AsyncClient(BaseClient): - """An async GeoIP2 client. + """An async GeoIP client. It accepts the following required arguments: :param account_id: Your MaxMind account ID. :param license_key: Your MaxMind license key. - Go to https://www.maxmind.com/en/my_license_key to see your MaxMind - account ID and license key. + Go to https://www.maxmind.com/en/accounts/current/license-key to see + your MaxMind account ID and license key. The following keyword arguments are also accepted: :param host: The hostname to make a request against. This defaults to - "geoip.maxmind.com". To use the GeoLite2 web service instead of the - GeoIP2 web service, set this to "geolite.info". To use the Sandbox - GeoIP2 web service instead of the production GeoIP2 web service, set + "geoip.maxmind.com". To use the GeoLite web service instead of the + GeoIP web service, set this to "geolite.info". To use the Sandbox + GeoIP web service instead of the production GeoIP web service, set this to "sandbox.maxmind.com". The sandbox allows you to experiment with the API without affecting your production data. :param locales: This is list of locale codes. This argument will be @@ -256,7 +256,7 @@ multiple names (country, city, etc.), its name property will return the name in the first locale that has one. - Note that the only locale which is always present in the GeoIP2 + Note that the only locale which is always present in the GeoIP data is "en". If you do not include this locale, the name property may end up returning None even when the record has an English name. @@ -319,7 +319,7 @@ ) async def country(self, ip_address: IPAddress = "me") -> Country: - """Call the GeoIP2 Country endpoint with the specified IP. + """Call the GeoIP Country endpoint with the specified IP. :param ip_address: IPv4 or IPv6 address as a string. If no address is provided, the address that the web service is called from will @@ -336,7 +336,7 @@ async def insights(self, ip_address: IPAddress = "me") -> Insights: """Call the Insights endpoint with the specified IP. - Insights is only supported by the GeoIP2 web service. The GeoLite2 web + Insights is only supported by the GeoIP web service. The GeoLite web service does not support it. :param ip_address: IPv4 or IPv6 address as a string. If no address @@ -354,8 +354,14 @@ async def _session(self) -> aiohttp.ClientSession: if not hasattr(self, "_existing_session"): self._existing_session = aiohttp.ClientSession( - auth=aiohttp.BasicAuth(self._account_id, self._license_key), - headers={"Accept": "application/json", "User-Agent": _AIOHTTP_UA}, + headers={ + "Accept": "application/json", + "Authorization": aiohttp.encode_basic_auth( + self._account_id, + self._license_key, + ), + "User-Agent": _AIOHTTP_UA, + }, timeout=aiohttp.ClientTimeout(total=self._timeout), ) @@ -399,22 +405,22 @@ class Client(BaseClient): - """A synchronous GeoIP2 client. + """A synchronous GeoIP client. It accepts the following required arguments: :param account_id: Your MaxMind account ID. :param license_key: Your MaxMind license key. - Go to https://www.maxmind.com/en/my_license_key to see your MaxMind - account ID and license key. + Go to https://www.maxmind.com/en/accounts/current/license-key to see + your MaxMind account ID and license key. The following keyword arguments are also accepted: :param host: The hostname to make a request against. This defaults to - "geoip.maxmind.com". To use the GeoLite2 web service instead of the - GeoIP2 web service, set this to "geolite.info". To use the Sandbox - GeoIP2 web service instead of the production GeoIP2 web service, set + "geoip.maxmind.com". To use the GeoLite web service instead of the + GeoIP web service, set this to "geolite.info". To use the Sandbox + GeoIP web service instead of the production GeoIP web service, set this to "sandbox.maxmind.com". The sandbox allows you to experiment with the API without affecting your production data. :param locales: This is list of locale codes. This argument will be @@ -425,7 +431,7 @@ multiple names (country, city, etc.), its name property will return the name in the first locale that has one. - Note that the only locale which is always present in the GeoIP2 + Note that the only locale which is always present in the GeoIP data is "en". If you do not include this locale, the name property may end up returning None even when the record has an English name. @@ -487,7 +493,7 @@ return cast("City", self._response_for("city", geoip2.models.City, ip_address)) def country(self, ip_address: IPAddress = "me") -> Country: - """Call the GeoIP2 Country endpoint with the specified IP. + """Call the GeoIP Country endpoint with the specified IP. :param ip_address: IPv4 or IPv6 address as a string. If no address is provided, the address that the web service is called from will @@ -504,7 +510,7 @@ def insights(self, ip_address: IPAddress = "me") -> Insights: """Call the Insights endpoint with the specified IP. - Insights is only supported by the GeoIP2 web service. The GeoLite2 web + Insights is only supported by the GeoIP web service. The GeoLite web service does not support it. :param ip_address: IPv4 or IPv6 address as a string. If no address diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/geoip2-5.2.0/tests/models_test.py new/geoip2-5.3.0/tests/models_test.py --- old/geoip2-5.2.0/tests/models_test.py 1970-01-01 01:00:00.000000000 +0100 +++ new/geoip2-5.3.0/tests/models_test.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,3 +1,4 @@ +import datetime import ipaddress import sys import unittest @@ -24,6 +25,11 @@ "is_tor_exit_node": True, "network_last_seen": "2025-04-14", "provider_name": "FooBar VPN", + "residential": { + "confidence": 82, + "network_last_seen": "2026-05-11", + "provider_name": "quickshift", + }, }, "city": { "confidence": 76, @@ -262,10 +268,32 @@ self.assertIs(model.anonymizer.is_tor_exit_node, True) self.assertEqual( model.anonymizer.network_last_seen, - __import__("datetime").date(2025, 4, 14), + datetime.date(2025, 4, 14), ) self.assertEqual(model.anonymizer.provider_name, "FooBar VPN") + # Test anonymizer.residential object + self.assertEqual( + type(model.anonymizer.residential), + geoip2.records.AnonymizerFeed, + "geoip2.records.AnonymizerFeed object", + ) + self.assertEqual(model.anonymizer.residential.confidence, 82) + self.assertEqual( + model.anonymizer.residential.network_last_seen, + datetime.date(2026, 5, 11), + ) + self.assertEqual(model.anonymizer.residential.provider_name, "quickshift") + self.assertEqual( + model.anonymizer.to_dict()["residential"], + { + "confidence": 82, + "network_last_seen": "2026-05-11", + "provider_name": "quickshift", + }, + "residential is serialized by to_dict", + ) + def test_insights_min(self) -> None: model = geoip2.models.Insights(["en"], traits={"ip_address": "5.6.7.8"}) self.assertEqual( @@ -324,6 +352,59 @@ self.assertIsNone(model.anonymizer.provider_name) self.assertFalse(model.anonymizer.is_anonymous) self.assertFalse(model.anonymizer.is_anonymous_vpn) + # Test that anonymizer.residential defaults correctly + self.assertEqual( + type(model.anonymizer.residential), + geoip2.records.AnonymizerFeed, + "geoip2.records.AnonymizerFeed object", + ) + self.assertIsNone(model.anonymizer.residential.confidence) + self.assertIsNone(model.anonymizer.residential.network_last_seen) + self.assertIsNone(model.anonymizer.residential.provider_name) + self.assertEqual( + model.to_dict(), + {"traits": {"ip_address": "5.6.7.8"}}, + "empty anonymizer is not serialized by to_dict", + ) + + def test_insights_anonymizer_residential_only(self) -> None: + # The residential attribute may be populated even when no other + # anonymizer attributes are set, so the anonymizer object may + # contain only the residential attribute. + model = geoip2.models.Insights( + ["en"], + anonymizer={ + "residential": { + "confidence": 82, + "network_last_seen": "2026-05-11", + "provider_name": "quickshift", + }, + }, + traits={"ip_address": "5.6.7.8"}, + ) + self.assertIsNone(model.anonymizer.confidence) + self.assertIsNone(model.anonymizer.provider_name) + self.assertFalse(model.anonymizer.is_anonymous) + self.assertEqual(model.anonymizer.residential.confidence, 82) + self.assertEqual( + model.anonymizer.residential.network_last_seen, + datetime.date(2026, 5, 11), + ) + self.assertEqual(model.anonymizer.residential.provider_name, "quickshift") + self.assertEqual( + model.to_dict(), + { + "anonymizer": { + "residential": { + "confidence": 82, + "network_last_seen": "2026-05-11", + "provider_name": "quickshift", + }, + }, + "traits": {"ip_address": "5.6.7.8"}, + }, + "anonymizer contains only residential in to_dict output", + ) def test_city_full(self) -> None: raw = {
