gh-118710: Make IPv*Address.version & max_prefixlen available on the class (GH-120698)

This commit is contained in:
Nice Zombies 2024-09-04 15:51:12 +02:00 committed by GitHub
parent 2daed5f7a7
commit c530ce1e9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 56 additions and 64 deletions

View File

@ -131,6 +131,10 @@ write code that handles both IP versions correctly. Address objects are
The appropriate version number: ``4`` for IPv4, ``6`` for IPv6.
.. versionchanged:: 3.14
Made available on the class.
.. attribute:: max_prefixlen
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
are compared to determine whether or not an address is part of a
network.
.. versionchanged:: 3.14
Made available on the class.
.. attribute:: compressed
.. attribute:: exploded

View File

@ -239,7 +239,7 @@ def summarize_address_range(first, last):
else:
raise ValueError('unknown IP version')
ip_bits = first._max_prefixlen
ip_bits = first.max_prefixlen
first_int = first._ip
last_int = last._ip
while first_int <= last_int:
@ -326,12 +326,12 @@ def collapse_addresses(addresses):
# split IP addresses and networks
for ip in addresses:
if isinstance(ip, _BaseAddress):
if ips and ips[-1]._version != ip._version:
if ips and ips[-1].version != ip.version:
raise TypeError("%s and %s are not of the same version" % (
ip, ips[-1]))
ips.append(ip)
elif ip._prefixlen == ip._max_prefixlen:
if ips and ips[-1]._version != ip._version:
elif ip._prefixlen == ip.max_prefixlen:
if ips and ips[-1].version != ip.version:
raise TypeError("%s and %s are not of the same version" % (
ip, ips[-1]))
try:
@ -339,7 +339,7 @@ def collapse_addresses(addresses):
except AttributeError:
ips.append(ip.network_address)
else:
if nets and nets[-1]._version != ip._version:
if nets and nets[-1].version != ip.version:
raise TypeError("%s and %s are not of the same version" % (
ip, nets[-1]))
nets.append(ip)
@ -407,26 +407,21 @@ class _IPAddressBase:
"""
return self._reverse_pointer()
@property
def version(self):
msg = '%200s has no version specified' % (type(self),)
raise NotImplementedError(msg)
def _check_int_address(self, address):
if address < 0:
msg = "%d (< 0) is not permitted as an IPv%d address"
raise AddressValueError(msg % (address, self._version))
raise AddressValueError(msg % (address, self.version))
if address > self._ALL_ONES:
msg = "%d (>= 2**%d) is not permitted as an IPv%d address"
raise AddressValueError(msg % (address, self._max_prefixlen,
self._version))
raise AddressValueError(msg % (address, self.max_prefixlen,
self.version))
def _check_packed_address(self, address, expected_len):
address_len = len(address)
if address_len != expected_len:
msg = "%r (len %d != %d) is not permitted as an IPv%d address"
raise AddressValueError(msg % (address, address_len,
expected_len, self._version))
expected_len, self.version))
@classmethod
def _ip_int_from_prefix(cls, prefixlen):
@ -455,12 +450,12 @@ class _IPAddressBase:
ValueError: If the input intermingles zeroes & ones
"""
trailing_zeroes = _count_righthand_zero_bits(ip_int,
cls._max_prefixlen)
prefixlen = cls._max_prefixlen - trailing_zeroes
cls.max_prefixlen)
prefixlen = cls.max_prefixlen - trailing_zeroes
leading_ones = ip_int >> trailing_zeroes
all_ones = (1 << prefixlen) - 1
if leading_ones != all_ones:
byteslen = cls._max_prefixlen // 8
byteslen = cls.max_prefixlen // 8
details = ip_int.to_bytes(byteslen, 'big')
msg = 'Netmask pattern %r mixes zeroes & ones'
raise ValueError(msg % details)
@ -492,7 +487,7 @@ class _IPAddressBase:
prefixlen = int(prefixlen_str)
except ValueError:
cls._report_invalid_netmask(prefixlen_str)
if not (0 <= prefixlen <= cls._max_prefixlen):
if not (0 <= prefixlen <= cls.max_prefixlen):
cls._report_invalid_netmask(prefixlen_str)
return prefixlen
@ -542,7 +537,7 @@ class _IPAddressBase:
"""
# a packed address or integer
if isinstance(address, (bytes, int)):
return address, cls._max_prefixlen
return address, cls.max_prefixlen
if not isinstance(address, tuple):
# Assume input argument to be string or any object representation
@ -552,7 +547,7 @@ class _IPAddressBase:
# Constructing from a tuple (addr, [mask])
if len(address) > 1:
return address
return address[0], cls._max_prefixlen
return address[0], cls.max_prefixlen
def __reduce__(self):
return self.__class__, (str(self),)
@ -577,14 +572,14 @@ class _BaseAddress(_IPAddressBase):
def __eq__(self, other):
try:
return (self._ip == other._ip
and self._version == other._version)
and self.version == other.version)
except AttributeError:
return NotImplemented
def __lt__(self, other):
if not isinstance(other, _BaseAddress):
return NotImplemented
if self._version != other._version:
if self.version != other.version:
raise TypeError('%s and %s are not of the same version' % (
self, other))
if self._ip != other._ip:
@ -613,7 +608,7 @@ class _BaseAddress(_IPAddressBase):
return hash(hex(int(self._ip)))
def _get_address_key(self):
return (self._version, self)
return (self.version, self)
def __reduce__(self):
return self.__class__, (self._ip,)
@ -649,15 +644,15 @@ class _BaseAddress(_IPAddressBase):
# Set some defaults
if fmt_base == 'n':
if self._version == 4:
if self.version == 4:
fmt_base = 'b' # Binary is default for ipv4
else:
fmt_base = 'x' # Hex is default for ipv6
if fmt_base == 'b':
padlen = self._max_prefixlen
padlen = self.max_prefixlen
else:
padlen = self._max_prefixlen // 4
padlen = self.max_prefixlen // 4
if grouping:
padlen += padlen // 4 - 1
@ -716,7 +711,7 @@ class _BaseNetwork(_IPAddressBase):
def __lt__(self, other):
if not isinstance(other, _BaseNetwork):
return NotImplemented
if self._version != other._version:
if self.version != other.version:
raise TypeError('%s and %s are not of the same version' % (
self, other))
if self.network_address != other.network_address:
@ -727,7 +722,7 @@ class _BaseNetwork(_IPAddressBase):
def __eq__(self, other):
try:
return (self._version == other._version and
return (self.version == other.version and
self.network_address == other.network_address and
int(self.netmask) == int(other.netmask))
except AttributeError:
@ -738,7 +733,7 @@ class _BaseNetwork(_IPAddressBase):
def __contains__(self, other):
# always false if one is v4 and the other is v6.
if self._version != other._version:
if self.version != other.version:
return False
# dealing with another network.
if isinstance(other, _BaseNetwork):
@ -829,7 +824,7 @@ class _BaseNetwork(_IPAddressBase):
ValueError: If other is not completely contained by self.
"""
if not self._version == other._version:
if not self.version == other.version:
raise TypeError("%s and %s are not of the same version" % (
self, other))
@ -901,10 +896,10 @@ class _BaseNetwork(_IPAddressBase):
"""
# does this need to raise a ValueError?
if self._version != other._version:
if self.version != other.version:
raise TypeError('%s and %s are not of the same type' % (
self, other))
# self._version == other._version below here:
# self.version == other.version below here:
if self.network_address < other.network_address:
return -1
if self.network_address > other.network_address:
@ -924,7 +919,7 @@ class _BaseNetwork(_IPAddressBase):
and list.sort().
"""
return (self._version, self.network_address, self.netmask)
return (self.version, self.network_address, self.netmask)
def subnets(self, prefixlen_diff=1, new_prefix=None):
"""The subnets which join to make the current subnet.
@ -952,7 +947,7 @@ class _BaseNetwork(_IPAddressBase):
number means a larger network)
"""
if self._prefixlen == self._max_prefixlen:
if self._prefixlen == self.max_prefixlen:
yield self
return
@ -967,7 +962,7 @@ class _BaseNetwork(_IPAddressBase):
raise ValueError('prefix length diff must be > 0')
new_prefixlen = self._prefixlen + prefixlen_diff
if new_prefixlen > self._max_prefixlen:
if new_prefixlen > self.max_prefixlen:
raise ValueError(
'prefix length diff %d is invalid for netblock %s' % (
new_prefixlen, self))
@ -1036,7 +1031,7 @@ class _BaseNetwork(_IPAddressBase):
def _is_subnet_of(a, b):
try:
# Always false if one is v4 and the other is v6.
if a._version != b._version:
if a.version != b.version:
raise TypeError(f"{a} and {b} are not of the same version")
return (b.network_address <= a.network_address and
b.broadcast_address >= a.broadcast_address)
@ -1146,11 +1141,11 @@ class _BaseV4:
"""
__slots__ = ()
_version = 4
version = 4
# Equivalent to 255.255.255.255 or 32 bits of 1's.
_ALL_ONES = (2**IPV4LENGTH) - 1
_max_prefixlen = IPV4LENGTH
max_prefixlen = IPV4LENGTH
# There are only a handful of valid v4 netmasks, so we cache them all
# when constructed (see _make_netmask()).
_netmask_cache = {}
@ -1170,7 +1165,7 @@ class _BaseV4:
if arg not in cls._netmask_cache:
if isinstance(arg, int):
prefixlen = arg
if not (0 <= prefixlen <= cls._max_prefixlen):
if not (0 <= prefixlen <= cls.max_prefixlen):
cls._report_invalid_netmask(prefixlen)
else:
try:
@ -1268,15 +1263,6 @@ class _BaseV4:
reverse_octets = str(self).split('.')[::-1]
return '.'.join(reverse_octets) + '.in-addr.arpa'
@property
def max_prefixlen(self):
return self._max_prefixlen
@property
def version(self):
return self._version
class IPv4Address(_BaseV4, _BaseAddress):
"""Represent and manipulate single IPv4 Addresses."""
@ -1556,9 +1542,9 @@ class IPv4Network(_BaseV4, _BaseNetwork):
self.network_address = IPv4Address(packed &
int(self.netmask))
if self._prefixlen == (self._max_prefixlen - 1):
if self._prefixlen == (self.max_prefixlen - 1):
self.hosts = self.__iter__
elif self._prefixlen == (self._max_prefixlen):
elif self._prefixlen == (self.max_prefixlen):
self.hosts = lambda: [IPv4Address(addr)]
@property
@ -1628,11 +1614,11 @@ class _BaseV6:
"""
__slots__ = ()
_version = 6
version = 6
_ALL_ONES = (2**IPV6LENGTH) - 1
_HEXTET_COUNT = 8
_HEX_DIGITS = frozenset('0123456789ABCDEFabcdef')
_max_prefixlen = IPV6LENGTH
max_prefixlen = IPV6LENGTH
# There are only a bunch of valid v6 netmasks, so we cache them all
# when constructed (see _make_netmask()).
@ -1650,7 +1636,7 @@ class _BaseV6:
if arg not in cls._netmask_cache:
if isinstance(arg, int):
prefixlen = arg
if not (0 <= prefixlen <= cls._max_prefixlen):
if not (0 <= prefixlen <= cls.max_prefixlen):
cls._report_invalid_netmask(prefixlen)
else:
prefixlen = cls._prefix_from_prefix_string(arg)
@ -1912,15 +1898,6 @@ class _BaseV6:
raise AddressValueError('Invalid IPv6 address: "%r"' % ip_str)
return addr, scope_id
@property
def max_prefixlen(self):
return self._max_prefixlen
@property
def version(self):
return self._version
class IPv6Address(_BaseV6, _BaseAddress):
"""Represent and manipulate single IPv6 Addresses."""
@ -2332,9 +2309,9 @@ class IPv6Network(_BaseV6, _BaseNetwork):
self.network_address = IPv6Address(packed &
int(self.netmask))
if self._prefixlen == (self._max_prefixlen - 1):
if self._prefixlen == (self.max_prefixlen - 1):
self.hosts = self.__iter__
elif self._prefixlen == self._max_prefixlen:
elif self._prefixlen == self.max_prefixlen:
self.hosts = lambda: [IPv6Address(addr)]
def hosts(self):

View File

@ -2189,11 +2189,17 @@ class IpaddrUnitTest(unittest.TestCase):
ipaddress.ip_address('FFFF::c000:201%scope'))
def testIPVersion(self):
self.assertEqual(ipaddress.IPv4Address.version, 4)
self.assertEqual(ipaddress.IPv6Address.version, 6)
self.assertEqual(self.ipv4_address.version, 4)
self.assertEqual(self.ipv6_address.version, 6)
self.assertEqual(self.ipv6_scoped_address.version, 6)
def testMaxPrefixLength(self):
self.assertEqual(ipaddress.IPv4Address.max_prefixlen, 32)
self.assertEqual(ipaddress.IPv6Address.max_prefixlen, 128)
self.assertEqual(self.ipv4_interface.max_prefixlen, 32)
self.assertEqual(self.ipv6_interface.max_prefixlen, 128)
self.assertEqual(self.ipv6_scoped_interface.max_prefixlen, 128)

View File

@ -0,0 +1 @@
:class:`ipaddress.IPv4Address` and :class:`ipaddress.IPv6Address` attributes ``version`` and ``max_prefixlen`` are now available on the class.