Skip to content

Commit fdf7850

Browse files
authored
Merge pull request #200 from maxmind/greg/eng-1812
Add GeoIP Anonymous Plus and clean up docs
2 parents 45af2fd + 216f79c commit fdf7850

13 files changed

+1761
-1040
lines changed

HISTORY.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
History
55
-------
66

7+
5.1.0
8+
++++++++++++++++++
9+
10+
* Support for the GeoIP Anonymous Plus database has been added. To do a lookup
11+
in this database, use the ``anonymous_plus`` method on ``Reader``.
12+
13+
714
5.0.1 (2025-01-28)
815
++++++++++++++++++
916

README.rst

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,42 @@ Anonymous IP Database
268268
>>> response.network
269269
IPv4Network('203.0.113.0/24')
270270
271+
Anonymous Plus Database
272+
^^^^^^^^^^^^^^^^^^^^^^^
273+
274+
.. code-block:: pycon
275+
276+
>>> import geoip2.database
277+
>>>
278+
>>> # This creates a Reader object. You should use the same object
279+
>>> # across multiple requests as creation of it is expensive.
280+
>>> with geoip2.database.Reader('/path/to/GeoIP-Anonymous-Plus.mmdb') as reader:
281+
>>>
282+
>>> response = reader.anonymous_plus('203.0.113.0')
283+
>>>
284+
>>> response.anonymizer_confidence
285+
30
286+
>>> response.is_anonymous
287+
True
288+
>>> response.is_anonymous_vpn
289+
True
290+
>>> response.is_hosting_provider
291+
False
292+
>>> response.is_public_proxy
293+
False
294+
>>> response.is_residential_proxy
295+
False
296+
>>> response.is_tor_exit_node
297+
False
298+
>>> response.ip_address
299+
'203.0.113.0'
300+
>>> response.network
301+
IPv4Network('203.0.113.0/24')
302+
>>> response.network_last_seen
303+
datetime.date(2025, 4, 18)
304+
>>> response.provider_name
305+
FooBar VPNs
306+
271307
ASN Database
272308
^^^^^^^^^^^^
273309

docs/index.rst

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,55 @@
88

99
.. include:: ../README.rst
1010

11-
=======
12-
Modules
13-
=======
14-
1511
.. automodule:: geoip2
1612
:members:
13+
:inherited-members:
14+
:show-inheritance:
15+
16+
===============
17+
Database Reader
18+
===============
1719

1820
.. automodule:: geoip2.database
1921
:members:
22+
:inherited-members:
23+
:show-inheritance:
24+
25+
==================
26+
WebServices Client
27+
==================
2028

2129
.. automodule:: geoip2.webservice
2230
:members:
31+
:inherited-members:
32+
:show-inheritance:
33+
34+
======
35+
Models
36+
======
2337

2438
.. automodule:: geoip2.models
2539
:members:
40+
:inherited-members:
41+
:show-inheritance:
42+
43+
=======
44+
Records
45+
=======
2646

2747
.. automodule:: geoip2.records
2848
:members:
49+
:inherited-members:
50+
:show-inheritance:
51+
52+
======
53+
Errors
54+
======
2955

3056
.. automodule:: geoip2.errors
3157
:members:
58+
:inherited-members:
59+
:show-inheritance:
3260

3361
==================
3462
Indices and tables

geoip2/_internal.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""This package contains internal utilities."""
1+
"""Internal utilities."""
22

33
# pylint: disable=too-few-public-methods
44
from abc import ABCMeta
@@ -15,7 +15,7 @@ def __ne__(self, other) -> bool:
1515

1616
# pylint: disable=too-many-branches
1717
def to_dict(self) -> dict:
18-
"""Returns a dict of the object suitable for serialization."""
18+
"""Return a dict of the object suitable for serialization."""
1919
result = {}
2020
for key, value in self.__dict__.items():
2121
if key.startswith("_"):

geoip2/database.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
"""
2-
======================
3-
GeoIP2 Database Reader
4-
======================
5-
6-
"""
1+
"""The database reader for MaxMind MMDB files."""
72

83
import inspect
94
import os
@@ -27,6 +22,7 @@
2722
ASN,
2823
ISP,
2924
AnonymousIP,
25+
AnonymousPlus,
3026
City,
3127
ConnectionType,
3228
Country,
@@ -167,6 +163,23 @@ def anonymous_ip(self, ip_address: IPAddress) -> AnonymousIP:
167163
),
168164
)
169165

166+
def anonymous_plus(self, ip_address: IPAddress) -> AnonymousPlus:
167+
"""Get the AnonymousPlus object for the IP address.
168+
169+
:param ip_address: IPv4 or IPv6 address as a string.
170+
171+
:returns: :py:class:`geoip2.models.AnonymousPlus` object
172+
173+
"""
174+
return cast(
175+
AnonymousPlus,
176+
self._flat_model_for(
177+
geoip2.models.AnonymousPlus,
178+
"GeoIP-Anonymous-Plus",
179+
ip_address,
180+
),
181+
)
182+
170183
def asn(self, ip_address: IPAddress) -> ASN:
171184
"""Get the ASN object for the IP address.
172185

geoip2/errors.py

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
"""
2-
Errors
3-
======
4-
5-
"""
1+
"""Typed errors thrown by this library."""
62

73
import ipaddress
8-
94
from typing import Optional, Union
105

116

@@ -19,26 +14,12 @@ class GeoIP2Error(RuntimeError):
1914

2015

2116
class AddressNotFoundError(GeoIP2Error):
22-
"""The address you were looking up was not found.
23-
24-
.. attribute:: ip_address
25-
26-
The IP address used in the lookup. This is only available for database
27-
lookups.
28-
29-
:type: str
30-
31-
.. attribute:: network
32-
33-
The network associated with the error. In particular, this is the
34-
largest network where no address would be found. This is only
35-
available for database lookups.
36-
37-
:type: ipaddress.IPv4Network or ipaddress.IPv6Network
38-
39-
"""
17+
"""The address you were looking up was not found."""
4018

4119
ip_address: Optional[str]
20+
"""The IP address used in the lookup. This is only available for database
21+
lookups.
22+
"""
4223
_prefix_len: Optional[int]
4324

4425
def __init__(
@@ -53,10 +34,16 @@ def __init__(
5334

5435
@property
5536
def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]:
56-
"""The network for the error."""
37+
"""The network associated with the error.
38+
39+
In particular, this is the largest network where no address would be
40+
found. This is only available for database lookups.
41+
"""
5742
if self.ip_address is None or self._prefix_len is None:
5843
return None
59-
return ipaddress.ip_network(f"{self.ip_address}/{self._prefix_len}", False)
44+
return ipaddress.ip_network(
45+
f"{self.ip_address}/{self._prefix_len}", strict=False
46+
)
6047

6148

6249
class AuthenticationError(GeoIP2Error):
@@ -69,12 +56,15 @@ class HTTPError(GeoIP2Error):
6956
This class represents an HTTP transport error. It extends
7057
:py:exc:`GeoIP2Error` and adds attributes of its own.
7158
72-
:ivar http_status: The HTTP status code returned
73-
:ivar uri: The URI queried
74-
:ivar decoded_content: The decoded response content
75-
7659
"""
7760

61+
http_status: Optional[int]
62+
"""The HTTP status code returned"""
63+
uri: Optional[str]
64+
"""The URI queried"""
65+
decoded_content: Optional[str]
66+
"""The decoded response content"""
67+
7868
def __init__(
7969
self,
8070
message: str,

0 commit comments

Comments
 (0)