]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-118710: Make IPv*Address.version & max_prefixlen available on the class (GH-120698)
authorNice Zombies <nineteendo19d0@gmail.com>
Wed, 4 Sep 2024 13:51:12 +0000 (15:51 +0200)
committerGitHub <noreply@github.com>
Wed, 4 Sep 2024 13:51:12 +0000 (15:51 +0200)
Doc/library/ipaddress.rst
Lib/ipaddress.py
Lib/test/test_ipaddress.py
Misc/NEWS.d/next/Library/2024-06-18-14-45-38.gh-issue-118710.5GZZPX.rst [new file with mode: 0644]

index b6fb6249155c79faf308d4e2af80d0e680c48654..e5bdfbb144b65a662483fc78489b671260524c67 100644 (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
 
index c165505a53330c5ee93200b5d071b3f0754ab1d1..c5a737d76de245a02c00a196c71429600165bb68 100644 (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):
index bd8d5d220e9131a24e5fe86d0a559478286a93f7..7364d6fc7202c46387d366642acd448d93d02db4 100644 (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)
diff --git a/Misc/NEWS.d/next/Library/2024-06-18-14-45-38.gh-issue-118710.5GZZPX.rst b/Misc/NEWS.d/next/Library/2024-06-18-14-45-38.gh-issue-118710.5GZZPX.rst
new file mode 100644 (file)
index 0000000..a02d286
--- /dev/null
@@ -0,0 +1 @@
+:class:`ipaddress.IPv4Address` and :class:`ipaddress.IPv6Address` attributes ``version`` and ``max_prefixlen`` are now available on the class.