Skip to content

Commit 724e605

Browse files
committed
Make ip_address property more similar to network
Previously, it wasn't even always a string. This makes it consistent.
1 parent b2a27fd commit 724e605

File tree

7 files changed

+60
-28
lines changed

7 files changed

+60
-28
lines changed

HISTORY.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ History
1010
* BREAKING: The ``raw`` attribute on the model classes has been replaced
1111
with a ``to_dict()`` method. This can be used to get a representation of
1212
the object that is suitable for serialization.
13+
* BREAKING: The ``ip_address`` property on the model classes now always returns
14+
a ``ipaddress.IPv4Address`` or ``ipaddress.IPv6Address``.
1315
* BREAKING: The model and record classes now require all arguments other than
14-
``locales`` to be keyword arguments.
16+
``locales`` and ``ip_address`` to be keyword arguments.
1517
* BREAKING: ``geoip2.mixins`` has been made internal. This normally would not
1618
have been used by external code.
1719
* IMPORTANT: Python 3.9 or greater is required. If you are using an older

geoip2/_internal.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class Model(metaclass=ABCMeta):
99
"""Shared methods for MaxMind model classes"""
1010

1111
def __eq__(self, other: Any) -> bool:
12-
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
12+
return isinstance(other, self.__class__) and self.to_dict() == other.to_dict()
1313

1414
def __ne__(self, other):
1515
return not self.__eq__(other)
@@ -42,8 +42,10 @@ def to_dict(self):
4242
elif value is not None and value is not False:
4343
result[key] = value
4444

45-
# network is a property for performance reasons
45+
# network and ip_address properties for performance reasons
4646
# pylint: disable=no-member
47+
if hasattr(self, "ip_address") and self.ip_address is not None:
48+
result["ip_address"] = str(self.ip_address)
4749
if hasattr(self, "network") and self.network is not None:
4850
result["network"] = str(self.network)
4951

geoip2/database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ def _flat_model_for(
265265
ip_address: IPAddress,
266266
) -> Union[ConnectionType, ISP, AnonymousIP, Domain, ASN]:
267267
(record, prefix_len) = self._get(types, ip_address)
268-
return model_class(ip_address=ip_address, prefix_len=prefix_len, **record)
268+
return model_class(ip_address, prefix_len=prefix_len, **record)
269269

270270
def metadata(
271271
self,

geoip2/models.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def __init__(
112112

113113
def __repr__(self) -> str:
114114
return (
115-
f"{self.__module__}.{self.__class__.__name__}({self._locales}, "
115+
f"{self.__module__}.{self.__class__.__name__}({repr(self._locales)}, "
116116
f"{', '.join(f'{k}={repr(v)}' for k, v in self.to_dict().items())})"
117117
)
118118

@@ -359,13 +359,13 @@ class Enterprise(City):
359359
class SimpleModel(Model, metaclass=ABCMeta):
360360
"""Provides basic methods for non-location models"""
361361

362-
ip_address: str
362+
_ip_address: IPAddress
363363
_network: Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]
364-
_prefix_len: int
364+
_prefix_len: Optional[int]
365365

366366
def __init__(
367367
self,
368-
ip_address: Optional[str],
368+
ip_address: IPAddress,
369369
network: Optional[str],
370370
prefix_len: Optional[int],
371371
) -> None:
@@ -378,14 +378,26 @@ def __init__(
378378
# used.
379379
self._network = None
380380
self._prefix_len = prefix_len
381-
self.ip_address = ip_address
381+
self._ip_address = ip_address
382382

383383
def __repr__(self) -> str:
384+
d = self.to_dict()
385+
d.pop("ip_address", None)
384386
return (
385-
f"{self.__module__}.{self.__class__.__name__}"
386-
f"({', '.join(f'{k}={repr(v)}' for k, v in self.to_dict().items())})"
387+
f"{self.__module__}.{self.__class__.__name__}({repr(str(self._ip_address))}, "
388+
+ ", ".join(f"{k}={repr(v)}" for k, v in d.items())
389+
+ ")"
387390
)
388391

392+
@property
393+
def ip_address(self):
394+
"""The IP address for the record"""
395+
if not isinstance(
396+
self._ip_address, (ipaddress.IPv4Address, ipaddress.IPv6Address)
397+
):
398+
self._ip_address = ipaddress.ip_address(self._ip_address)
399+
return self._ip_address
400+
389401
@property
390402
def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]:
391403
"""The network for the record"""
@@ -475,14 +487,14 @@ class AnonymousIP(SimpleModel):
475487

476488
def __init__(
477489
self,
490+
ip_address: IPAddress,
478491
*,
479492
is_anonymous: bool = False,
480493
is_anonymous_vpn: bool = False,
481494
is_hosting_provider: bool = False,
482495
is_public_proxy: bool = False,
483496
is_residential_proxy: bool = False,
484497
is_tor_exit_node: bool = False,
485-
ip_address: Optional[str] = None,
486498
network: Optional[str] = None,
487499
prefix_len: Optional[int] = None,
488500
**_,
@@ -535,10 +547,10 @@ class ASN(SimpleModel):
535547
# pylint:disable=too-many-arguments,too-many-positional-arguments
536548
def __init__(
537549
self,
550+
ip_address: IPAddress,
538551
*,
539552
autonomous_system_number: Optional[int] = None,
540553
autonomous_system_organization: Optional[str] = None,
541-
ip_address: Optional[str] = None,
542554
network: Optional[str] = None,
543555
prefix_len: Optional[int] = None,
544556
**_,
@@ -586,9 +598,9 @@ class ConnectionType(SimpleModel):
586598

587599
def __init__(
588600
self,
601+
ip_address: IPAddress,
589602
*,
590603
connection_type: Optional[str] = None,
591-
ip_address: Optional[str] = None,
592604
network: Optional[str] = None,
593605
prefix_len: Optional[int] = None,
594606
**_,
@@ -628,9 +640,9 @@ class Domain(SimpleModel):
628640

629641
def __init__(
630642
self,
643+
ip_address: IPAddress,
631644
*,
632645
domain: Optional[str] = None,
633-
ip_address: Optional[str] = None,
634646
network: Optional[str] = None,
635647
prefix_len: Optional[int] = None,
636648
**_,
@@ -708,14 +720,14 @@ class ISP(ASN):
708720
# pylint:disable=too-many-arguments,too-many-positional-arguments
709721
def __init__(
710722
self,
723+
ip_address: IPAddress,
711724
*,
712725
autonomous_system_number: Optional[int] = None,
713726
autonomous_system_organization: Optional[str] = None,
714727
isp: Optional[str] = None,
715728
mobile_country_code: Optional[str] = None,
716729
mobile_network_code: Optional[str] = None,
717730
organization: Optional[str] = None,
718-
ip_address: Optional[str] = None,
719731
network: Optional[str] = None,
720732
prefix_len: Optional[int] = None,
721733
**_,

geoip2/records.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -838,7 +838,7 @@ class Traits(Record):
838838
autonomous_system_organization: Optional[str]
839839
connection_type: Optional[str]
840840
domain: Optional[str]
841-
ip_address: Optional[str]
841+
_ip_address: Optional[str]
842842
is_anonymous: bool
843843
is_anonymous_proxy: bool
844844
is_anonymous_vpn: bool
@@ -909,7 +909,7 @@ def __init__(
909909
self.static_ip_score = static_ip_score
910910
self.user_type = user_type
911911
self.user_count = user_count
912-
self.ip_address = ip_address
912+
self._ip_address = ip_address
913913
if network is None:
914914
self._network = None
915915
else:
@@ -919,6 +919,15 @@ def __init__(
919919
# much more performance sensitive than web service users.
920920
self._prefix_len = prefix_len
921921

922+
@property
923+
def ip_address(self):
924+
"""The IP address for the record"""
925+
if not isinstance(
926+
self._ip_address, (ipaddress.IPv4Address, ipaddress.IPv6Address)
927+
):
928+
self._ip_address = ipaddress.ip_address(self._ip_address)
929+
return self._ip_address
930+
922931
@property
923932
def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]:
924933
"""The network for the record"""

tests/database_test.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def test_anonymous_ip(self) -> None:
8282
self.assertEqual(record.is_public_proxy, False)
8383
self.assertEqual(record.is_residential_proxy, False)
8484
self.assertEqual(record.is_tor_exit_node, False)
85-
self.assertEqual(record.ip_address, ip_address)
85+
self.assertEqual(record.ip_address, ipaddress.ip_address(ip_address))
8686
self.assertEqual(record.network, ipaddress.ip_network("1.2.0.0/16"))
8787
reader.close()
8888

@@ -99,7 +99,7 @@ def test_anonymous_ip_all_set(self) -> None:
9999
self.assertEqual(record.is_public_proxy, True)
100100
self.assertEqual(record.is_residential_proxy, True)
101101
self.assertEqual(record.is_tor_exit_node, True)
102-
self.assertEqual(record.ip_address, ip_address)
102+
self.assertEqual(record.ip_address, ipaddress.ip_address(ip_address))
103103
self.assertEqual(record.network, ipaddress.ip_network("81.2.69.0/24"))
104104
reader.close()
105105

@@ -113,7 +113,7 @@ def test_asn(self) -> None:
113113

114114
self.assertEqual(record.autonomous_system_number, 1221)
115115
self.assertEqual(record.autonomous_system_organization, "Telstra Pty Ltd")
116-
self.assertEqual(record.ip_address, ip_address)
116+
self.assertEqual(record.ip_address, ipaddress.ip_address(ip_address))
117117
self.assertEqual(record.network, ipaddress.ip_network("1.128.0.0/11"))
118118

119119
self.assertRegex(
@@ -156,7 +156,7 @@ def test_connection_type(self) -> None:
156156
)
157157

158158
self.assertEqual(record.connection_type, "Cellular")
159-
self.assertEqual(record.ip_address, ip_address)
159+
self.assertEqual(record.ip_address, ipaddress.ip_address(ip_address))
160160
self.assertEqual(record.network, ipaddress.ip_network("1.0.1.0/24"))
161161

162162
self.assertRegex(
@@ -171,7 +171,9 @@ def test_country(self) -> None:
171171
reader = geoip2.database.Reader("tests/data/test-data/GeoIP2-Country-Test.mmdb")
172172
record = reader.country("81.2.69.160")
173173
self.assertEqual(
174-
record.traits.ip_address, "81.2.69.160", "IP address is added to model"
174+
record.traits.ip_address,
175+
ipaddress.ip_address("81.2.69.160"),
176+
"IP address is added to model",
175177
)
176178
self.assertEqual(record.traits.network, ipaddress.ip_network("81.2.69.160/27"))
177179
self.assertEqual(record.country.is_in_european_union, False)
@@ -192,7 +194,7 @@ def test_domain(self) -> None:
192194
self.assertEqual(record, eval(repr(record)), "Domain repr can be eval'd")
193195

194196
self.assertEqual(record.domain, "maxmind.com")
195-
self.assertEqual(record.ip_address, ip_address)
197+
self.assertEqual(record.ip_address, ipaddress.ip_address(ip_address))
196198
self.assertEqual(record.network, ipaddress.ip_network("1.2.0.0/16"))
197199

198200
self.assertRegex(
@@ -217,7 +219,7 @@ def test_enterprise(self) -> None:
217219
self.assertEqual(record.registered_country.is_in_european_union, False)
218220
self.assertEqual(record.traits.connection_type, "Cable/DSL")
219221
self.assertTrue(record.traits.is_legitimate_proxy)
220-
self.assertEqual(record.traits.ip_address, ip_address)
222+
self.assertEqual(record.traits.ip_address, ipaddress.ip_address(ip_address))
221223
self.assertEqual(
222224
record.traits.network, ipaddress.ip_network("74.209.16.0/20")
223225
)
@@ -242,7 +244,7 @@ def test_isp(self) -> None:
242244
self.assertEqual(record.autonomous_system_organization, "Telstra Pty Ltd")
243245
self.assertEqual(record.isp, "Telstra Internet")
244246
self.assertEqual(record.organization, "Telstra Internet")
245-
self.assertEqual(record.ip_address, ip_address)
247+
self.assertEqual(record.ip_address, ipaddress.ip_address(ip_address))
246248
self.assertEqual(record.network, ipaddress.ip_network("1.128.0.0/11"))
247249

248250
self.assertRegex(
@@ -261,7 +263,9 @@ def test_context_manager(self) -> None:
261263
"tests/data/test-data/GeoIP2-Country-Test.mmdb"
262264
) as reader:
263265
record = reader.country("81.2.69.160")
264-
self.assertEqual(record.traits.ip_address, "81.2.69.160")
266+
self.assertEqual(
267+
record.traits.ip_address, ipaddress.ip_address("81.2.69.160")
268+
)
265269

266270
@patch("maxminddb.open_database")
267271
def test_modes(self, mock_open) -> None:

tests/models_test.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from __future__ import unicode_literals
55

66
import sys
7+
import ipaddress
78
from typing import Dict
89
import unittest
910

@@ -393,7 +394,9 @@ def test_unknown_keys(self) -> None:
393394
model.unk_base # type: ignore
394395
with self.assertRaises(AttributeError):
395396
model.traits.invalid # type: ignore
396-
self.assertEqual(model.traits.ip_address, "1.2.3.4", "correct ip")
397+
self.assertEqual(
398+
model.traits.ip_address, ipaddress.ip_address("1.2.3.4"), "correct ip"
399+
)
397400

398401

399402
class TestNames(unittest.TestCase):

0 commit comments

Comments
 (0)