Skip to content

Commit 0e8790c

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 4518919 commit 0e8790c

File tree

7 files changed

+62
-28
lines changed

7 files changed

+62
-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: 26 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,28 @@ 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__}("
388+
+ repr(str(self._ip_address))
389+
+ ", "
390+
+ ", ".join(f"{k}={repr(v)}" for k, v in d.items())
391+
+ ")"
387392
)
388393

394+
@property
395+
def ip_address(self):
396+
"""The IP address for the record"""
397+
if not isinstance(
398+
self._ip_address, (ipaddress.IPv4Address, ipaddress.IPv6Address)
399+
):
400+
self._ip_address = ipaddress.ip_address(self._ip_address)
401+
return self._ip_address
402+
389403
@property
390404
def network(self) -> Optional[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]:
391405
"""The network for the record"""
@@ -475,14 +489,14 @@ class AnonymousIP(SimpleModel):
475489

476490
def __init__(
477491
self,
492+
ip_address: IPAddress,
478493
*,
479494
is_anonymous: bool = False,
480495
is_anonymous_vpn: bool = False,
481496
is_hosting_provider: bool = False,
482497
is_public_proxy: bool = False,
483498
is_residential_proxy: bool = False,
484499
is_tor_exit_node: bool = False,
485-
ip_address: Optional[str] = None,
486500
network: Optional[str] = None,
487501
prefix_len: Optional[int] = None,
488502
**_,
@@ -535,10 +549,10 @@ class ASN(SimpleModel):
535549
# pylint:disable=too-many-arguments,too-many-positional-arguments
536550
def __init__(
537551
self,
552+
ip_address: IPAddress,
538553
*,
539554
autonomous_system_number: Optional[int] = None,
540555
autonomous_system_organization: Optional[str] = None,
541-
ip_address: Optional[str] = None,
542556
network: Optional[str] = None,
543557
prefix_len: Optional[int] = None,
544558
**_,
@@ -586,9 +600,9 @@ class ConnectionType(SimpleModel):
586600

587601
def __init__(
588602
self,
603+
ip_address: IPAddress,
589604
*,
590605
connection_type: Optional[str] = None,
591-
ip_address: Optional[str] = None,
592606
network: Optional[str] = None,
593607
prefix_len: Optional[int] = None,
594608
**_,
@@ -628,9 +642,9 @@ class Domain(SimpleModel):
628642

629643
def __init__(
630644
self,
645+
ip_address: IPAddress,
631646
*,
632647
domain: Optional[str] = None,
633-
ip_address: Optional[str] = None,
634648
network: Optional[str] = None,
635649
prefix_len: Optional[int] = None,
636650
**_,
@@ -708,14 +722,14 @@ class ISP(ASN):
708722
# pylint:disable=too-many-arguments,too-many-positional-arguments
709723
def __init__(
710724
self,
725+
ip_address: IPAddress,
711726
*,
712727
autonomous_system_number: Optional[int] = None,
713728
autonomous_system_organization: Optional[str] = None,
714729
isp: Optional[str] = None,
715730
mobile_country_code: Optional[str] = None,
716731
mobile_network_code: Optional[str] = None,
717732
organization: Optional[str] = None,
718-
ip_address: Optional[str] = None,
719733
network: Optional[str] = None,
720734
prefix_len: Optional[int] = None,
721735
**_,

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

@@ -391,7 +392,9 @@ def test_unknown_keys(self) -> None:
391392
model.unk_base # type: ignore
392393
with self.assertRaises(AttributeError):
393394
model.traits.invalid # type: ignore
394-
self.assertEqual(model.traits.ip_address, "1.2.3.4", "correct ip")
395+
self.assertEqual(
396+
model.traits.ip_address, ipaddress.ip_address("1.2.3.4"), "correct ip"
397+
)
395398

396399

397400
class TestNames(unittest.TestCase):

0 commit comments

Comments
 (0)