Update ipaddress to CPython 3.8

This commit is contained in:
Padraic Fanning committed 2021-04-14 19:22:07 -04:00
1 parent 24dccb74a9
commit e9b2f71df0
1 file changed
+80 -171
+80 -171
View File
@@ -488,7 +488,7 @@ class _IPAddressBase:
"""
# int allows a leading +/- as well as surrounding whitespace,
# so we ensure that isn't the case
if not _BaseV4._DECIMAL_DIGITS.issuperset(prefixlen_str):
if not (prefixlen_str.isascii() and prefixlen_str.isdigit()):
cls._report_invalid_netmask(prefixlen_str)
try:
prefixlen = int(prefixlen_str)
@@ -532,6 +532,30 @@ class _IPAddressBase:
except ValueError:
cls._report_invalid_netmask(ip_str)
@classmethod
def _split_addr_prefix(cls, address):
"""Helper function to parse address of Network/Interface.
Arg:
address: Argument of Network/Interface.
Returns:
(addr, prefix) tuple.
"""
# a packed address or integer
if isinstance(address, (bytes, int)):
return address, cls._max_prefixlen
if not isinstance(address, tuple):
# Assume input argument to be string or any object representation
# which converts into a formatted IP prefix string.
address = _split_optional_netmask(address)
# Constructing from a tuple (addr, [mask])
if len(address) > 1:
return address
return address[0], cls._max_prefixlen
def __reduce__(self):
return self.__class__, (str(self),)
@@ -597,15 +621,11 @@ class _BaseAddress(_IPAddressBase):
@functools.total_ordering
class _BaseNetwork(_IPAddressBase):
"""A generic IP network object.
This IP class contains the version independent methods which are
used by networks.
"""
def __init__(self, address):
self._cache = {}
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, str(self))
@@ -677,8 +697,7 @@ class _BaseNetwork(_IPAddressBase):
# dealing with another address
else:
# address
return (int(self.network_address) <= int(other._ip) <=
int(self.broadcast_address))
return other._ip & self.netmask._ip == self.network_address._ip
def overlaps(self, other):
"""Tell if self is partly contained in other."""
@@ -687,22 +706,14 @@ class _BaseNetwork(_IPAddressBase):
other.network_address in self or (
other.broadcast_address in self)))
@property
@functools.cached_property
def broadcast_address(self):
x = self._cache.get('broadcast_address')
if x is None:
x = self._address_class(int(self.network_address) |
int(self.hostmask))
self._cache['broadcast_address'] = x
return x
return self._address_class(int(self.network_address) |
int(self.hostmask))
@property
@functools.cached_property
def hostmask(self):
x = self._cache.get('hostmask')
if x is None:
x = self._address_class(int(self.netmask) ^ self._ALL_ONES)
self._cache['hostmask'] = x
return x
return self._address_class(int(self.netmask) ^ self._ALL_ONES)
@property
def with_prefixlen(self):
@@ -776,8 +787,7 @@ class _BaseNetwork(_IPAddressBase):
if not isinstance(other, _BaseNetwork):
raise TypeError("%s is not a network object" % other)
if not (other.network_address >= self.network_address and
other.broadcast_address <= self.broadcast_address):
if not other.subnet_of(self):
raise ValueError('%s not contained in %s' % (other, self))
if other == self:
return
@@ -788,12 +798,10 @@ class _BaseNetwork(_IPAddressBase):
s1, s2 = self.subnets()
while s1 != other and s2 != other:
if (other.network_address >= s1.network_address and
other.broadcast_address <= s1.broadcast_address):
if other.subnet_of(s1):
yield s2
s1, s2 = s1.subnets()
elif (other.network_address >= s2.network_address and
other.broadcast_address <= s2.broadcast_address):
elif other.subnet_of(s2):
yield s1
s1, s2 = s2.subnets()
else:
@@ -975,6 +983,26 @@ class _BaseNetwork(_IPAddressBase):
return (self.network_address.is_multicast and
self.broadcast_address.is_multicast)
@staticmethod
def _is_subnet_of(a, b):
try:
# Always false if one is v4 and the other is v6.
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)
except AttributeError:
raise TypeError(f"Unable to test subnet containment "
f"between {a} and {b}")
def subnet_of(self, other):
"""Return True if this network is a subnet of other."""
return self._is_subnet_of(self, other)
def supernet_of(self, other):
"""Return True if this network is a supernet of other."""
return self._is_subnet_of(other, self)
@property
def is_reserved(self):
"""Test if the address is otherwise IETF reserved.
@@ -1059,10 +1087,6 @@ class _BaseV4:
_version = 4
# Equivalent to 255.255.255.255 or 32 bits of 1's.
_ALL_ONES = (2**IPV4LENGTH) - 1
_DECIMAL_DIGITS = frozenset('0123456789')
# the valid octets for host and netmasks. only useful for IPv4.
_valid_mask_octets = frozenset({255, 254, 252, 248, 240, 224, 192, 128, 0})
_max_prefixlen = IPV4LENGTH
# There are only a handful of valid v4 netmasks, so we cache them all
@@ -1084,6 +1108,8 @@ class _BaseV4:
if arg not in cls._netmask_cache:
if isinstance(arg, int):
prefixlen = arg
if not (0 <= prefixlen <= cls._max_prefixlen):
cls._report_invalid_netmask(prefixlen)
else:
try:
# Check for a netmask in prefix length form
@@ -1139,7 +1165,7 @@ class _BaseV4:
if not octet_str:
raise ValueError("Empty octet not permitted")
# Whitelist the characters, since int() allows a lot of bizarre stuff.
if not cls._DECIMAL_DIGITS.issuperset(octet_str):
if not (octet_str.isascii() and octet_str.isdigit()):
msg = "Only decimal digits permitted in %r"
raise ValueError(msg % octet_str)
# We do the length check second, since the invalid character error
@@ -1149,12 +1175,6 @@ class _BaseV4:
raise ValueError(msg % octet_str)
# Convert to integer (we know digits are legal)
octet_int = int(octet_str, 10)
# Any octets that look like they *might* be written in octal,
# and which don't look exactly the same in both octal and
# decimal are rejected as ambiguous
if octet_int > 7 and octet_str[0] == '0':
msg = "Ambiguous (octal/decimal) value in %r not permitted"
raise ValueError(msg % octet_str)
if octet_int > 255:
raise ValueError("Octet %d (> 255) not permitted" % octet_int)
return octet_int
@@ -1172,58 +1192,6 @@ class _BaseV4:
"""
return '.'.join(map(str, ip_int.to_bytes(4, 'big')))
def _is_valid_netmask(self, netmask):
"""Verify that the netmask is valid.
Args:
netmask: A string, either a prefix or dotted decimal
netmask.
Returns:
A boolean, True if the prefix represents a valid IPv4
netmask.
"""
mask = netmask.split('.')
if len(mask) == 4:
try:
for x in mask:
if int(x) not in self._valid_mask_octets:
return False
except ValueError:
# Found something that isn't an integer or isn't valid
return False
for idx, y in enumerate(mask):
if idx > 0 and y > mask[idx - 1]:
return False
return True
try:
netmask = int(netmask)
except ValueError:
return False
return 0 <= netmask <= self._max_prefixlen
def _is_hostmask(self, ip_str):
"""Test if the IP string is a hostmask (rather than a netmask).
Args:
ip_str: A string, the potential hostmask.
Returns:
A boolean, True if the IP string is a hostmask.
"""
bits = ip_str.split('.')
try:
parts = [x for x in map(int, bits) if x in self._valid_mask_octets]
except ValueError:
return False
if len(parts) != len(bits):
return False
if parts[0] < parts[-1]:
return True
return False
def _reverse_pointer(self):
"""Return the reverse DNS pointer name for the IPv4 address.
@@ -1362,36 +1330,20 @@ class IPv4Address(_BaseV4, _BaseAddress):
class IPv4Interface(IPv4Address):
def __init__(self, address):
if isinstance(address, (bytes, int)):
IPv4Address.__init__(self, address)
self.network = IPv4Network(self._ip)
self._prefixlen = self._max_prefixlen
return
addr, mask = self._split_addr_prefix(address)
if isinstance(address, tuple):
IPv4Address.__init__(self, address[0])
if len(address) > 1:
self._prefixlen = int(address[1])
else:
self._prefixlen = self._max_prefixlen
self.network = IPv4Network(address, strict=False)
self.netmask = self.network.netmask
self.hostmask = self.network.hostmask
return
addr = _split_optional_netmask(address)
IPv4Address.__init__(self, addr[0])
self.network = IPv4Network(address, strict=False)
IPv4Address.__init__(self, addr)
self.network = IPv4Network((addr, mask), strict=False)
self.netmask = self.network.netmask
self._prefixlen = self.network._prefixlen
self.netmask = self.network.netmask
self.hostmask = self.network.hostmask
@functools.cached_property
def hostmask(self):
return self.network.hostmask
def __str__(self):
return '%s/%d' % (self._string_from_ip_int(self._ip),
self.network.prefixlen)
self._prefixlen)
def __eq__(self, other):
address_equal = IPv4Address.__eq__(self, other)
@@ -1418,7 +1370,7 @@ class IPv4Interface(IPv4Address):
return False
def __hash__(self):
return self._ip ^ self._prefixlen ^ int(self.network.network_address)
return hash((self._ip, self._prefixlen, int(self.network.network_address)))
__reduce__ = _IPAddressBase.__reduce__
@@ -1458,14 +1410,13 @@ class IPv4Network(_BaseV4, _BaseNetwork):
_address_class = IPv4Address
def __init__(self, address, strict=True):
"""Instantiate a new IPv4 network object.
Args:
address: A string or integer representing the IP [& network].
'192.0.2.0/24'
'192.0.2.0/255.255.255.0'
'192.0.0.2/0.0.0.255'
'192.0.2.0/0.0.0.255'
are all functionally the same in IPv4. Similarly,
'192.0.2.1'
'192.0.2.1/255.255.255.255'
@@ -1492,24 +1443,8 @@ class IPv4Network(_BaseV4, _BaseNetwork):
an IPv4 address.
ValueError: If strict is True and a network address is not
supplied.
"""
_BaseNetwork.__init__(self, address)
# Constructing from a packed address or integer
if isinstance(address, (int, bytes)):
addr = address
mask = self._max_prefixlen
# Constructing from a tuple (addr, [mask])
elif isinstance(address, tuple):
addr = address[0]
mask = address[1] if len(address) > 1 else self._max_prefixlen
# Assume input argument to be string or any object representation
# which converts into a formatted IP prefix string.
else:
args = _split_optional_netmask(address)
addr = self._ip_int_from_string(args[0])
mask = args[1] if len(args) == 2 else self._max_prefixlen
addr, mask = self._split_addr_prefix(address)
self.network_address = IPv4Address(addr)
self.netmask, self._prefixlen = self._make_netmask(mask)
@@ -1605,6 +1540,8 @@ class _BaseV6:
if arg not in cls._netmask_cache:
if isinstance(arg, int):
prefixlen = arg
if not (0 <= prefixlen <= cls._max_prefixlen):
cls._report_invalid_netmask(prefixlen)
else:
prefixlen = cls._prefix_from_prefix_string(arg)
netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen))
@@ -2040,32 +1977,20 @@ class IPv6Address(_BaseV6, _BaseAddress):
class IPv6Interface(IPv6Address):
def __init__(self, address):
if isinstance(address, (bytes, int)):
IPv6Address.__init__(self, address)
self.network = IPv6Network(self._ip)
self._prefixlen = self._max_prefixlen
return
if isinstance(address, tuple):
IPv6Address.__init__(self, address[0])
if len(address) > 1:
self._prefixlen = int(address[1])
else:
self._prefixlen = self._max_prefixlen
self.network = IPv6Network(address, strict=False)
self.netmask = self.network.netmask
self.hostmask = self.network.hostmask
return
addr, mask = self._split_addr_prefix(address)
addr = _split_optional_netmask(address)
IPv6Address.__init__(self, addr[0])
self.network = IPv6Network(address, strict=False)
IPv6Address.__init__(self, addr)
self.network = IPv6Network((addr, mask), strict=False)
self.netmask = self.network.netmask
self._prefixlen = self.network._prefixlen
self.hostmask = self.network.hostmask
@functools.cached_property
def hostmask(self):
return self.network.hostmask
def __str__(self):
return '%s/%d' % (self._string_from_ip_int(self._ip),
self.network.prefixlen)
self._prefixlen)
def __eq__(self, other):
address_equal = IPv6Address.__eq__(self, other)
@@ -2092,7 +2017,7 @@ class IPv6Interface(IPv6Address):
return False
def __hash__(self):
return self._ip ^ self._prefixlen ^ int(self.network.network_address)
return hash((self._ip, self._prefixlen, int(self.network.network_address)))
__reduce__ = _IPAddressBase.__reduce__
@@ -2170,24 +2095,8 @@ class IPv6Network(_BaseV6, _BaseNetwork):
an IPv6 address.
ValueError: If strict was True and a network address was not
supplied.
"""
_BaseNetwork.__init__(self, address)
# Constructing from a packed address or integer
if isinstance(address, (int, bytes)):
addr = address
mask = self._max_prefixlen
# Constructing from a tuple (addr, [mask])
elif isinstance(address, tuple):
addr = address[0]
mask = address[1] if len(address) > 1 else self._max_prefixlen
# Assume input argument to be string or any object representation
# which converts into a formatted IP prefix string.
else:
args = _split_optional_netmask(address)
addr = self._ip_int_from_string(args[0])
mask = args[1] if len(args) == 2 else self._max_prefixlen
addr, mask = self._split_addr_prefix(address)
self.network_address = IPv6Address(addr)
self.netmask, self._prefixlen = self._make_netmask(mask)