Skip to content

Commit c530ce1

Browse files
authored
gh-118710: Make IPv*Address.version & max_prefixlen available on the class (GH-120698)
1 parent 2daed5f commit c530ce1

File tree

4 files changed

+56
-64
lines changed

4 files changed

+56
-64
lines changed

Doc/library/ipaddress.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ write code that handles both IP versions correctly. Address objects are
131131

132132
The appropriate version number: ``4`` for IPv4, ``6`` for IPv6.
133133

134+
.. versionchanged:: 3.14
135+
136+
Made available on the class.
137+
134138
.. attribute:: max_prefixlen
135139

136140
The total number of bits in the address representation for this
@@ -140,6 +144,10 @@ write code that handles both IP versions correctly. Address objects are
140144
are compared to determine whether or not an address is part of a
141145
network.
142146

147+
.. versionchanged:: 3.14
148+
149+
Made available on the class.
150+
143151
.. attribute:: compressed
144152
.. attribute:: exploded
145153

Lib/ipaddress.py

Lines changed: 41 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def summarize_address_range(first, last):
239239
else:
240240
raise ValueError('unknown IP version')
241241

242-
ip_bits = first._max_prefixlen
242+
ip_bits = first.max_prefixlen
243243
first_int = first._ip
244244
last_int = last._ip
245245
while first_int <= last_int:
@@ -326,20 +326,20 @@ def collapse_addresses(addresses):
326326
# split IP addresses and networks
327327
for ip in addresses:
328328
if isinstance(ip, _BaseAddress):
329-
if ips and ips[-1]._version != ip._version:
329+
if ips and ips[-1].version != ip.version:
330330
raise TypeError("%s and %s are not of the same version" % (
331331
ip, ips[-1]))
332332
ips.append(ip)
333-
elif ip._prefixlen == ip._max_prefixlen:
334-
if ips and ips[-1]._version != ip._version:
333+
elif ip._prefixlen == ip.max_prefixlen:
334+
if ips and ips[-1].version != ip.version:
335335
raise TypeError("%s and %s are not of the same version" % (
336336
ip, ips[-1]))
337337
try:
338338
ips.append(ip.ip)
339339
except AttributeError:
340340
ips.append(ip.network_address)
341341
else:
342-
if nets and nets[-1]._version != ip._version:
342+
if nets and nets[-1].version != ip.version:
343343
raise TypeError("%s and %s are not of the same version" % (
344344
ip, nets[-1]))
345345
nets.append(ip)
@@ -407,26 +407,21 @@ def reverse_pointer(self):
407407
"""
408408
return self._reverse_pointer()
409409

410-
@property
411-
def version(self):
412-
msg = '%200s has no version specified' % (type(self),)
413-
raise NotImplementedError(msg)
414-
415410
def _check_int_address(self, address):
416411
if address < 0:
417412
msg = "%d (< 0) is not permitted as an IPv%d address"
418-
raise AddressValueError(msg % (address, self._version))
413+
raise AddressValueError(msg % (address, self.version))
419414
if address > self._ALL_ONES:
420415
msg = "%d (>= 2**%d) is not permitted as an IPv%d address"
421-
raise AddressValueError(msg % (address, self._max_prefixlen,
422-
self._version))
416+
raise AddressValueError(msg % (address, self.max_prefixlen,
417+
self.version))
423418

424419
def _check_packed_address(self, address, expected_len):
425420
address_len = len(address)
426421
if address_len != expected_len:
427422
msg = "%r (len %d != %d) is not permitted as an IPv%d address"
428423
raise AddressValueError(msg % (address, address_len,
429-
expected_len, self._version))
424+
expected_len, self.version))
430425

431426
@classmethod
432427
def _ip_int_from_prefix(cls, prefixlen):
@@ -455,12 +450,12 @@ def _prefix_from_ip_int(cls, ip_int):
455450
ValueError: If the input intermingles zeroes & ones
456451
"""
457452
trailing_zeroes = _count_righthand_zero_bits(ip_int,
458-
cls._max_prefixlen)
459-
prefixlen = cls._max_prefixlen - trailing_zeroes
453+
cls.max_prefixlen)
454+
prefixlen = cls.max_prefixlen - trailing_zeroes
460455
leading_ones = ip_int >> trailing_zeroes
461456
all_ones = (1 << prefixlen) - 1
462457
if leading_ones != all_ones:
463-
byteslen = cls._max_prefixlen // 8
458+
byteslen = cls.max_prefixlen // 8
464459
details = ip_int.to_bytes(byteslen, 'big')
465460
msg = 'Netmask pattern %r mixes zeroes & ones'
466461
raise ValueError(msg % details)
@@ -492,7 +487,7 @@ def _prefix_from_prefix_string(cls, prefixlen_str):
492487
prefixlen = int(prefixlen_str)
493488
except ValueError:
494489
cls._report_invalid_netmask(prefixlen_str)
495-
if not (0 <= prefixlen <= cls._max_prefixlen):
490+
if not (0 <= prefixlen <= cls.max_prefixlen):
496491
cls._report_invalid_netmask(prefixlen_str)
497492
return prefixlen
498493

@@ -542,7 +537,7 @@ def _split_addr_prefix(cls, address):
542537
"""
543538
# a packed address or integer
544539
if isinstance(address, (bytes, int)):
545-
return address, cls._max_prefixlen
540+
return address, cls.max_prefixlen
546541

547542
if not isinstance(address, tuple):
548543
# Assume input argument to be string or any object representation
@@ -552,7 +547,7 @@ def _split_addr_prefix(cls, address):
552547
# Constructing from a tuple (addr, [mask])
553548
if len(address) > 1:
554549
return address
555-
return address[0], cls._max_prefixlen
550+
return address[0], cls.max_prefixlen
556551

557552
def __reduce__(self):
558553
return self.__class__, (str(self),)
@@ -577,14 +572,14 @@ def __int__(self):
577572
def __eq__(self, other):
578573
try:
579574
return (self._ip == other._ip
580-
and self._version == other._version)
575+
and self.version == other.version)
581576
except AttributeError:
582577
return NotImplemented
583578

584579
def __lt__(self, other):
585580
if not isinstance(other, _BaseAddress):
586581
return NotImplemented
587-
if self._version != other._version:
582+
if self.version != other.version:
588583
raise TypeError('%s and %s are not of the same version' % (
589584
self, other))
590585
if self._ip != other._ip:
@@ -613,7 +608,7 @@ def __hash__(self):
613608
return hash(hex(int(self._ip)))
614609

615610
def _get_address_key(self):
616-
return (self._version, self)
611+
return (self.version, self)
617612

618613
def __reduce__(self):
619614
return self.__class__, (self._ip,)
@@ -649,15 +644,15 @@ def __format__(self, fmt):
649644

650645
# Set some defaults
651646
if fmt_base == 'n':
652-
if self._version == 4:
647+
if self.version == 4:
653648
fmt_base = 'b' # Binary is default for ipv4
654649
else:
655650
fmt_base = 'x' # Hex is default for ipv6
656651

657652
if fmt_base == 'b':
658-
padlen = self._max_prefixlen
653+
padlen = self.max_prefixlen
659654
else:
660-
padlen = self._max_prefixlen // 4
655+
padlen = self.max_prefixlen // 4
661656

662657
if grouping:
663658
padlen += padlen // 4 - 1
@@ -716,7 +711,7 @@ def __getitem__(self, n):
716711
def __lt__(self, other):
717712
if not isinstance(other, _BaseNetwork):
718713
return NotImplemented
719-
if self._version != other._version:
714+
if self.version != other.version:
720715
raise TypeError('%s and %s are not of the same version' % (
721716
self, other))
722717
if self.network_address != other.network_address:
@@ -727,7 +722,7 @@ def __lt__(self, other):
727722

728723
def __eq__(self, other):
729724
try:
730-
return (self._version == other._version and
725+
return (self.version == other.version and
731726
self.network_address == other.network_address and
732727
int(self.netmask) == int(other.netmask))
733728
except AttributeError:
@@ -738,7 +733,7 @@ def __hash__(self):
738733

739734
def __contains__(self, other):
740735
# always false if one is v4 and the other is v6.
741-
if self._version != other._version:
736+
if self.version != other.version:
742737
return False
743738
# dealing with another network.
744739
if isinstance(other, _BaseNetwork):
@@ -829,7 +824,7 @@ def address_exclude(self, other):
829824
ValueError: If other is not completely contained by self.
830825
831826
"""
832-
if not self._version == other._version:
827+
if not self.version == other.version:
833828
raise TypeError("%s and %s are not of the same version" % (
834829
self, other))
835830

@@ -901,10 +896,10 @@ def compare_networks(self, other):
901896
902897
"""
903898
# does this need to raise a ValueError?
904-
if self._version != other._version:
899+
if self.version != other.version:
905900
raise TypeError('%s and %s are not of the same type' % (
906901
self, other))
907-
# self._version == other._version below here:
902+
# self.version == other.version below here:
908903
if self.network_address < other.network_address:
909904
return -1
910905
if self.network_address > other.network_address:
@@ -924,7 +919,7 @@ def _get_networks_key(self):
924919
and list.sort().
925920
926921
"""
927-
return (self._version, self.network_address, self.netmask)
922+
return (self.version, self.network_address, self.netmask)
928923

929924
def subnets(self, prefixlen_diff=1, new_prefix=None):
930925
"""The subnets which join to make the current subnet.
@@ -952,7 +947,7 @@ def subnets(self, prefixlen_diff=1, new_prefix=None):
952947
number means a larger network)
953948
954949
"""
955-
if self._prefixlen == self._max_prefixlen:
950+
if self._prefixlen == self.max_prefixlen:
956951
yield self
957952
return
958953

@@ -967,7 +962,7 @@ def subnets(self, prefixlen_diff=1, new_prefix=None):
967962
raise ValueError('prefix length diff must be > 0')
968963
new_prefixlen = self._prefixlen + prefixlen_diff
969964

970-
if new_prefixlen > self._max_prefixlen:
965+
if new_prefixlen > self.max_prefixlen:
971966
raise ValueError(
972967
'prefix length diff %d is invalid for netblock %s' % (
973968
new_prefixlen, self))
@@ -1036,7 +1031,7 @@ def is_multicast(self):
10361031
def _is_subnet_of(a, b):
10371032
try:
10381033
# Always false if one is v4 and the other is v6.
1039-
if a._version != b._version:
1034+
if a.version != b.version:
10401035
raise TypeError(f"{a} and {b} are not of the same version")
10411036
return (b.network_address <= a.network_address and
10421037
b.broadcast_address >= a.broadcast_address)
@@ -1146,11 +1141,11 @@ class _BaseV4:
11461141
"""
11471142

11481143
__slots__ = ()
1149-
_version = 4
1144+
version = 4
11501145
# Equivalent to 255.255.255.255 or 32 bits of 1's.
11511146
_ALL_ONES = (2**IPV4LENGTH) - 1
11521147

1153-
_max_prefixlen = IPV4LENGTH
1148+
max_prefixlen = IPV4LENGTH
11541149
# There are only a handful of valid v4 netmasks, so we cache them all
11551150
# when constructed (see _make_netmask()).
11561151
_netmask_cache = {}
@@ -1170,7 +1165,7 @@ def _make_netmask(cls, arg):
11701165
if arg not in cls._netmask_cache:
11711166
if isinstance(arg, int):
11721167
prefixlen = arg
1173-
if not (0 <= prefixlen <= cls._max_prefixlen):
1168+
if not (0 <= prefixlen <= cls.max_prefixlen):
11741169
cls._report_invalid_netmask(prefixlen)
11751170
else:
11761171
try:
@@ -1268,15 +1263,6 @@ def _reverse_pointer(self):
12681263
reverse_octets = str(self).split('.')[::-1]
12691264
return '.'.join(reverse_octets) + '.in-addr.arpa'
12701265

1271-
@property
1272-
def max_prefixlen(self):
1273-
return self._max_prefixlen
1274-
1275-
@property
1276-
def version(self):
1277-
return self._version
1278-
1279-
12801266
class IPv4Address(_BaseV4, _BaseAddress):
12811267

12821268
"""Represent and manipulate single IPv4 Addresses."""
@@ -1556,9 +1542,9 @@ def __init__(self, address, strict=True):
15561542
self.network_address = IPv4Address(packed &
15571543
int(self.netmask))
15581544

1559-
if self._prefixlen == (self._max_prefixlen - 1):
1545+
if self._prefixlen == (self.max_prefixlen - 1):
15601546
self.hosts = self.__iter__
1561-
elif self._prefixlen == (self._max_prefixlen):
1547+
elif self._prefixlen == (self.max_prefixlen):
15621548
self.hosts = lambda: [IPv4Address(addr)]
15631549

15641550
@property
@@ -1628,11 +1614,11 @@ class _BaseV6:
16281614
"""
16291615

16301616
__slots__ = ()
1631-
_version = 6
1617+
version = 6
16321618
_ALL_ONES = (2**IPV6LENGTH) - 1
16331619
_HEXTET_COUNT = 8
16341620
_HEX_DIGITS = frozenset('0123456789ABCDEFabcdef')
1635-
_max_prefixlen = IPV6LENGTH
1621+
max_prefixlen = IPV6LENGTH
16361622

16371623
# There are only a bunch of valid v6 netmasks, so we cache them all
16381624
# when constructed (see _make_netmask()).
@@ -1650,7 +1636,7 @@ def _make_netmask(cls, arg):
16501636
if arg not in cls._netmask_cache:
16511637
if isinstance(arg, int):
16521638
prefixlen = arg
1653-
if not (0 <= prefixlen <= cls._max_prefixlen):
1639+
if not (0 <= prefixlen <= cls.max_prefixlen):
16541640
cls._report_invalid_netmask(prefixlen)
16551641
else:
16561642
prefixlen = cls._prefix_from_prefix_string(arg)
@@ -1912,15 +1898,6 @@ def _split_scope_id(ip_str):
19121898
raise AddressValueError('Invalid IPv6 address: "%r"' % ip_str)
19131899
return addr, scope_id
19141900

1915-
@property
1916-
def max_prefixlen(self):
1917-
return self._max_prefixlen
1918-
1919-
@property
1920-
def version(self):
1921-
return self._version
1922-
1923-
19241901
class IPv6Address(_BaseV6, _BaseAddress):
19251902

19261903
"""Represent and manipulate single IPv6 Addresses."""
@@ -2332,9 +2309,9 @@ def __init__(self, address, strict=True):
23322309
self.network_address = IPv6Address(packed &
23332310
int(self.netmask))
23342311

2335-
if self._prefixlen == (self._max_prefixlen - 1):
2312+
if self._prefixlen == (self.max_prefixlen - 1):
23362313
self.hosts = self.__iter__
2337-
elif self._prefixlen == self._max_prefixlen:
2314+
elif self._prefixlen == self.max_prefixlen:
23382315
self.hosts = lambda: [IPv6Address(addr)]
23392316

23402317
def hosts(self):

Lib/test/test_ipaddress.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,11 +2189,17 @@ def testIPv6AddressTooLarge(self):
21892189
ipaddress.ip_address('FFFF::c000:201%scope'))
21902190

21912191
def testIPVersion(self):
2192+
self.assertEqual(ipaddress.IPv4Address.version, 4)
2193+
self.assertEqual(ipaddress.IPv6Address.version, 6)
2194+
21922195
self.assertEqual(self.ipv4_address.version, 4)
21932196
self.assertEqual(self.ipv6_address.version, 6)
21942197
self.assertEqual(self.ipv6_scoped_address.version, 6)
21952198

21962199
def testMaxPrefixLength(self):
2200+
self.assertEqual(ipaddress.IPv4Address.max_prefixlen, 32)
2201+
self.assertEqual(ipaddress.IPv6Address.max_prefixlen, 128)
2202+
21972203
self.assertEqual(self.ipv4_interface.max_prefixlen, 32)
21982204
self.assertEqual(self.ipv6_interface.max_prefixlen, 128)
21992205
self.assertEqual(self.ipv6_scoped_interface.max_prefixlen, 128)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:class:`ipaddress.IPv4Address` and :class:`ipaddress.IPv6Address` attributes ``version`` and ``max_prefixlen`` are now available on the class.

0 commit comments

Comments
 (0)