Skip to content

Commit 845eaca

Browse files
authored
AddressNotFoundError: add .network (#130)
1 parent 115d1ed commit 845eaca

File tree

4 files changed

+77
-2
lines changed

4 files changed

+77
-2
lines changed

README.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,31 @@ or there is a bug in the reader, a ``maxminddb.InvalidDatabaseError`` will be
400400
raised with a description of the problem. If an IP address is not in the
401401
database, a ``AddressNotFoundError`` will be raised.
402402

403+
``AddressNotFoundError`` references the largest subnet where no address would be
404+
found. This can be used to efficiently enumerate entire subnets:
405+
406+
.. code-block:: python
407+
408+
import geoip2.database
409+
import geoip2.errors
410+
import ipaddress
411+
412+
# This creates a Reader object. You should use the same object
413+
# across multiple requests as creation of it is expensive.
414+
with geoip2.database.Reader('/path/to/GeoLite2-ASN.mmdb') as reader:
415+
network = ipaddress.ip_network("192.128.0.0/15")
416+
417+
ip_address = network[0]
418+
while ip_address in network:
419+
try:
420+
response = reader.asn(ip_address)
421+
response_network = response.network
422+
except geoip2.errors.AddressNotFoundError as e:
423+
response = None
424+
response_network = e.network
425+
print(f"{response_network}: {response!r}")
426+
ip_address = response_network[-1] + 1 # move to next subnet
427+
403428
Values to use for Database or Dictionary Keys
404429
---------------------------------------------
405430

geoip2/database.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ def _get(self, database_type: str, ip_address: IPAddress) -> Any:
239239
if record is None:
240240
raise geoip2.errors.AddressNotFoundError(
241241
f"The address {ip_address} is not in the database.",
242+
str(ip_address),
243+
prefix_len,
242244
)
243245
return record, prefix_len
244246

geoip2/errors.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
55
"""
66

7-
from typing import Optional
7+
import ipaddress
8+
9+
from typing import Optional, Union
810

911

1012
class GeoIP2Error(RuntimeError):
@@ -17,7 +19,40 @@ class GeoIP2Error(RuntimeError):
1719

1820

1921
class AddressNotFoundError(GeoIP2Error):
20-
"""The address you were looking up was not found."""
22+
"""The address you were looking up was not found.
23+
24+
.. attribute:: ip_address
25+
26+
The IP address used in the lookup.
27+
28+
:type: str
29+
30+
.. attribute:: network
31+
32+
The network associated with the error. In particular, this is the
33+
largest network where no address would be found.
34+
35+
:type: ipaddress.IPv4Network or ipaddress.IPv6Network
36+
37+
"""
38+
39+
def __init__(
40+
self,
41+
message: str,
42+
ip_address: Optional[str] = None,
43+
prefix_len: Optional[int] = None,
44+
) -> None:
45+
super().__init__(message)
46+
self.ip_address = ip_address
47+
self._prefix_len = prefix_len
48+
49+
@property
50+
def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]:
51+
"""The network for the error"""
52+
53+
if self.ip_address is None or self._prefix_len is None:
54+
return None
55+
return ipaddress.ip_network(f"{self.ip_address}/{self._prefix_len}", False)
2156

2257

2358
class AuthenticationError(GeoIP2Error):

tests/database_test.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
sys.path.append("..")
1111

1212
import geoip2.database
13+
import geoip2.errors
1314
import maxminddb
1415

1516
try:
@@ -38,6 +39,18 @@ def test_unknown_address(self) -> None:
3839
reader.city("10.10.10.10")
3940
reader.close()
4041

42+
def test_unknown_address_network(self) -> None:
43+
reader = geoip2.database.Reader("tests/data/test-data/GeoIP2-City-Test.mmdb")
44+
try:
45+
reader.city("10.10.10.10")
46+
self.fail("Expected AddressNotFoundError")
47+
except geoip2.errors.AddressNotFoundError as e:
48+
self.assertEqual(e.network, ipaddress.ip_network("10.0.0.0/8"))
49+
except Exception as e:
50+
self.fail(f"Expected AddressNotFoundError, got {type(e)}: {str(e)}")
51+
finally:
52+
reader.close()
53+
4154
def test_wrong_database(self) -> None:
4255
reader = geoip2.database.Reader("tests/data/test-data/GeoIP2-City-Test.mmdb")
4356
with self.assertRaisesRegex(

0 commit comments

Comments
 (0)