Issue #16531: ipaddress.IPv4Network and ipaddress.IPv6Network now accept an (address, netmask) tuple argument, so as to easily construct network objects from existing addresses.

This commit is contained in:
Antoine Pitrou 2014-05-12 20:36:46 +02:00
parent 3b5162d05d
commit 5fb195f854
4 changed files with 210 additions and 21 deletions

View File

@ -392,6 +392,12 @@ so to avoid duplication they are only documented for :class:`IPv4Network`.
3. An integer packed into a :class:`bytes` object of length 4, big-endian. 3. An integer packed into a :class:`bytes` object of length 4, big-endian.
The interpretation is similar to an integer *address*. The interpretation is similar to an integer *address*.
4. A two-tuple of an address description and a netmask, where the address
description is either a string, a 32-bits integer, a 4-bytes packed
integer, or an existing IPv4Address object; and the netmask is either
an integer representing the prefix length (e.g. ``24``) or a string
representing the prefix mask (e.g. ``255.255.255.0``).
An :exc:`AddressValueError` is raised if *address* is not a valid IPv4 An :exc:`AddressValueError` is raised if *address* is not a valid IPv4
address. A :exc:`NetmaskValueError` is raised if the mask is not valid for address. A :exc:`NetmaskValueError` is raised if the mask is not valid for
an IPv4 address. an IPv4 address.
@ -404,6 +410,10 @@ so to avoid duplication they are only documented for :class:`IPv4Network`.
objects will raise :exc:`TypeError` if the argument's IP version is objects will raise :exc:`TypeError` if the argument's IP version is
incompatible to ``self`` incompatible to ``self``
.. versionchanged:: 3.5
Added the two-tuple form for the *address* constructor parameter.
.. attribute:: version .. attribute:: version
.. attribute:: max_prefixlen .. attribute:: max_prefixlen
@ -568,6 +578,11 @@ so to avoid duplication they are only documented for :class:`IPv4Network`.
3. An integer packed into a :class:`bytes` object of length 16, bit-endian. 3. An integer packed into a :class:`bytes` object of length 16, bit-endian.
The interpretation is similar to an integer *address*. The interpretation is similar to an integer *address*.
4. A two-tuple of an address description and a netmask, where the address
description is either a string, a 128-bits integer, a 16-bytes packed
integer, or an existing IPv4Address object; and the netmask is an
integer representing the prefix length.
An :exc:`AddressValueError` is raised if *address* is not a valid IPv6 An :exc:`AddressValueError` is raised if *address* is not a valid IPv6
address. A :exc:`NetmaskValueError` is raised if the mask is not valid for address. A :exc:`NetmaskValueError` is raised if the mask is not valid for
an IPv6 address. an IPv6 address.
@ -576,6 +591,10 @@ so to avoid duplication they are only documented for :class:`IPv4Network`.
then :exc:`ValueError` is raised. Otherwise, the host bits are masked out then :exc:`ValueError` is raised. Otherwise, the host bits are masked out
to determine the appropriate network address. to determine the appropriate network address.
.. versionchanged:: 3.5
Added the two-tuple form for the *address* constructor parameter.
.. attribute:: version .. attribute:: version
.. attribute:: max_prefixlen .. attribute:: max_prefixlen
.. attribute:: is_multicast .. attribute:: is_multicast

View File

@ -991,15 +991,15 @@ def supernet(self, prefixlen_diff=1, new_prefix=None):
raise ValueError('cannot set prefixlen_diff and new_prefix') raise ValueError('cannot set prefixlen_diff and new_prefix')
prefixlen_diff = self._prefixlen - new_prefix prefixlen_diff = self._prefixlen - new_prefix
if self.prefixlen - prefixlen_diff < 0: new_prefixlen = self.prefixlen - prefixlen_diff
if new_prefixlen < 0:
raise ValueError( raise ValueError(
'current prefixlen is %d, cannot have a prefixlen_diff of %d' % 'current prefixlen is %d, cannot have a prefixlen_diff of %d' %
(self.prefixlen, prefixlen_diff)) (self.prefixlen, prefixlen_diff))
# TODO (pmoody): optimize this. return self.__class__((
t = self.__class__('%s/%d' % (self.network_address, int(self.network_address) & (int(self.netmask) << prefixlen_diff),
self.prefixlen - prefixlen_diff), new_prefixlen
strict=False) ))
return t.__class__('%s/%d' % (t.network_address, t.prefixlen))
@property @property
def is_multicast(self): def is_multicast(self):
@ -1389,6 +1389,18 @@ def __init__(self, address):
self._prefixlen = self._max_prefixlen self._prefixlen = self._max_prefixlen
return return
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) addr = _split_optional_netmask(address)
IPv4Address.__init__(self, addr[0]) IPv4Address.__init__(self, addr[0])
@ -1504,22 +1516,42 @@ def __init__(self, address, strict=True):
_BaseV4.__init__(self, address) _BaseV4.__init__(self, address)
_BaseNetwork.__init__(self, address) _BaseNetwork.__init__(self, address)
# Constructing from a packed address # Constructing from a packed address or integer
if isinstance(address, bytes): if isinstance(address, (int, bytes)):
self.network_address = IPv4Address(address) self.network_address = IPv4Address(address)
self._prefixlen = self._max_prefixlen self._prefixlen = self._max_prefixlen
self.netmask = IPv4Address(self._ALL_ONES) self.netmask = IPv4Address(self._ALL_ONES)
#fixme: address/network test here #fixme: address/network test here
return return
# Efficient constructor from integer. if isinstance(address, tuple):
if isinstance(address, int): if len(address) > 1:
self.network_address = IPv4Address(address) # If address[1] is a string, treat it like a netmask.
self._prefixlen = self._max_prefixlen if isinstance(address[1], str):
self.netmask = IPv4Address(self._ALL_ONES) self.netmask = IPv4Address(address[1])
#fixme: address/network test here. self._prefixlen = self._prefix_from_ip_int(
int(self.netmask))
# address[1] should be an int.
else:
self._prefixlen = int(address[1])
self.netmask = IPv4Address(self._ip_int_from_prefix(
self._prefixlen))
# We weren't given an address[1].
else:
self._prefixlen = self._max_prefixlen
self.netmask = IPv4Address(self._ip_int_from_prefix(
self._prefixlen))
self.network_address = IPv4Address(address[0])
packed = int(self.network_address)
if packed & int(self.netmask) != packed:
if strict:
raise ValueError('%s has host bits set' % self)
else:
self.network_address = IPv4Address(packed &
int(self.netmask))
return return
# Assume input argument to be string or any object representation # Assume input argument to be string or any object representation
# which converts into a formatted IP prefix string. # which converts into a formatted IP prefix string.
addr = _split_optional_netmask(address) addr = _split_optional_netmask(address)
@ -2030,6 +2062,16 @@ def __init__(self, address):
self.network = IPv6Network(self._ip) self.network = IPv6Network(self._ip)
self._prefixlen = self._max_prefixlen self._prefixlen = self._max_prefixlen
return 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 = _split_optional_netmask(address) addr = _split_optional_netmask(address)
IPv6Address.__init__(self, addr[0]) IPv6Address.__init__(self, addr[0])
@ -2147,18 +2189,29 @@ def __init__(self, address, strict=True):
_BaseV6.__init__(self, address) _BaseV6.__init__(self, address)
_BaseNetwork.__init__(self, address) _BaseNetwork.__init__(self, address)
# Efficient constructor from integer. # Efficient constructor from integer or packed address
if isinstance(address, int): if isinstance(address, (bytes, int)):
self.network_address = IPv6Address(address) self.network_address = IPv6Address(address)
self._prefixlen = self._max_prefixlen self._prefixlen = self._max_prefixlen
self.netmask = IPv6Address(self._ALL_ONES) self.netmask = IPv6Address(self._ALL_ONES)
return return
# Constructing from a packed address if isinstance(address, tuple):
if isinstance(address, bytes): self.network_address = IPv6Address(address[0])
self.network_address = IPv6Address(address) if len(address) > 1:
self._prefixlen = self._max_prefixlen self._prefixlen = int(address[1])
self.netmask = IPv6Address(self._ALL_ONES) else:
self._prefixlen = self._max_prefixlen
self.netmask = IPv6Address(self._ip_int_from_prefix(
self._prefixlen))
self.network_address = IPv6Address(address[0])
packed = int(self.network_address)
if packed & int(self.netmask) != packed:
if strict:
raise ValueError('%s has host bits set' % self)
else:
self.network_address = IPv6Address(packed &
int(self.netmask))
return return
# Assume input argument to be string or any object representation # Assume input argument to be string or any object representation

View File

@ -628,6 +628,119 @@ def testRepr(self):
self.assertEqual("IPv6Interface('::1/128')", self.assertEqual("IPv6Interface('::1/128')",
repr(ipaddress.IPv6Interface('::1'))) repr(ipaddress.IPv6Interface('::1')))
# issue #16531: constructing IPv4Network from a (address, mask) tuple
def testIPv4Tuple(self):
# /32
ip = ipaddress.IPv4Address('192.0.2.1')
net = ipaddress.IPv4Network('192.0.2.1/32')
self.assertEqual(ipaddress.IPv4Network(('192.0.2.1', 32)), net)
self.assertEqual(ipaddress.IPv4Network((ip, 32)), net)
self.assertEqual(ipaddress.IPv4Network((3221225985, 32)), net)
self.assertEqual(ipaddress.IPv4Network(('192.0.2.1',
'255.255.255.255')), net)
self.assertEqual(ipaddress.IPv4Network((ip,
'255.255.255.255')), net)
self.assertEqual(ipaddress.IPv4Network((3221225985,
'255.255.255.255')), net)
# strict=True and host bits set
with self.assertRaises(ValueError):
ipaddress.IPv4Network(('192.0.2.1', 24))
with self.assertRaises(ValueError):
ipaddress.IPv4Network((ip, 24))
with self.assertRaises(ValueError):
ipaddress.IPv4Network((3221225985, 24))
with self.assertRaises(ValueError):
ipaddress.IPv4Network(('192.0.2.1', '255.255.255.0'))
with self.assertRaises(ValueError):
ipaddress.IPv4Network((ip, '255.255.255.0'))
with self.assertRaises(ValueError):
ipaddress.IPv4Network((3221225985, '255.255.255.0'))
# strict=False and host bits set
net = ipaddress.IPv4Network('192.0.2.0/24')
self.assertEqual(ipaddress.IPv4Network(('192.0.2.1', 24),
strict=False), net)
self.assertEqual(ipaddress.IPv4Network((ip, 24),
strict=False), net)
self.assertEqual(ipaddress.IPv4Network((3221225985, 24),
strict=False), net)
self.assertEqual(ipaddress.IPv4Network(('192.0.2.1',
'255.255.255.0'),
strict=False), net)
self.assertEqual(ipaddress.IPv4Network((ip,
'255.255.255.0'),
strict=False), net)
self.assertEqual(ipaddress.IPv4Network((3221225985,
'255.255.255.0'),
strict=False), net)
# /24
ip = ipaddress.IPv4Address('192.0.2.0')
net = ipaddress.IPv4Network('192.0.2.0/24')
self.assertEqual(ipaddress.IPv4Network(('192.0.2.0',
'255.255.255.0')), net)
self.assertEqual(ipaddress.IPv4Network((ip,
'255.255.255.0')), net)
self.assertEqual(ipaddress.IPv4Network((3221225984,
'255.255.255.0')), net)
self.assertEqual(ipaddress.IPv4Network(('192.0.2.0', 24)), net)
self.assertEqual(ipaddress.IPv4Network((ip, 24)), net)
self.assertEqual(ipaddress.IPv4Network((3221225984, 24)), net)
self.assertEqual(ipaddress.IPv4Interface(('192.0.2.1', 24)),
ipaddress.IPv4Interface('192.0.2.1/24'))
self.assertEqual(ipaddress.IPv4Interface((3221225985, 24)),
ipaddress.IPv4Interface('192.0.2.1/24'))
# issue #16531: constructing IPv6Network from a (address, mask) tuple
def testIPv6Tuple(self):
# /128
ip = ipaddress.IPv6Address('2001:db8::')
net = ipaddress.IPv6Network('2001:db8::/128')
self.assertEqual(ipaddress.IPv6Network(('2001:db8::', '128')),
net)
self.assertEqual(ipaddress.IPv6Network(
(42540766411282592856903984951653826560, 128)),
net)
self.assertEqual(ipaddress.IPv6Network((ip, '128')),
net)
ip = ipaddress.IPv6Address('2001:db8::')
net = ipaddress.IPv6Network('2001:db8::/96')
self.assertEqual(ipaddress.IPv6Network(('2001:db8::', '96')),
net)
self.assertEqual(ipaddress.IPv6Network(
(42540766411282592856903984951653826560, 96)),
net)
self.assertEqual(ipaddress.IPv6Network((ip, '96')),
net)
# strict=True and host bits set
ip = ipaddress.IPv6Address('2001:db8::1')
with self.assertRaises(ValueError):
ipaddress.IPv6Network(('2001:db8::1', 96))
with self.assertRaises(ValueError):
ipaddress.IPv6Network((
42540766411282592856903984951653826561, 96))
with self.assertRaises(ValueError):
ipaddress.IPv6Network((ip, 96))
# strict=False and host bits set
net = ipaddress.IPv6Network('2001:db8::/96')
self.assertEqual(ipaddress.IPv6Network(('2001:db8::1', 96),
strict=False),
net)
self.assertEqual(ipaddress.IPv6Network(
(42540766411282592856903984951653826561, 96),
strict=False),
net)
self.assertEqual(ipaddress.IPv6Network((ip, 96), strict=False),
net)
# /96
self.assertEqual(ipaddress.IPv6Interface(('2001:db8::1', '96')),
ipaddress.IPv6Interface('2001:db8::1/96'))
self.assertEqual(ipaddress.IPv6Interface(
(42540766411282592856903984951653826561, '96')),
ipaddress.IPv6Interface('2001:db8::1/96'))
# issue57 # issue57
def testAddressIntMath(self): def testAddressIntMath(self):
self.assertEqual(ipaddress.IPv4Address('1.1.1.1') + 255, self.assertEqual(ipaddress.IPv4Address('1.1.1.1') + 255,

View File

@ -81,6 +81,10 @@ Core and Builtins
Library Library
------- -------
- Issue #16531: ipaddress.IPv4Network and ipaddress.IPv6Network now accept
an (address, netmask) tuple argument, so as to easily construct network
objects from existing addresses.
- Issue #21156: importlib.abc.InspectLoader.source_to_code() is now a - Issue #21156: importlib.abc.InspectLoader.source_to_code() is now a
staticmethod. staticmethod.