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.
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
address. A :exc:`NetmaskValueError` is raised if the mask is not valid for
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
incompatible to ``self``
.. versionchanged:: 3.5
Added the two-tuple form for the *address* constructor parameter.
.. attribute:: version
.. 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.
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
address. A :exc:`NetmaskValueError` is raised if the mask is not valid for
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
to determine the appropriate network address.
.. versionchanged:: 3.5
Added the two-tuple form for the *address* constructor parameter.
.. attribute:: version
.. attribute:: max_prefixlen
.. 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')
prefixlen_diff = self._prefixlen - new_prefix
if self.prefixlen - prefixlen_diff < 0:
new_prefixlen = self.prefixlen - prefixlen_diff
if new_prefixlen < 0:
raise ValueError(
'current prefixlen is %d, cannot have a prefixlen_diff of %d' %
(self.prefixlen, prefixlen_diff))
# TODO (pmoody): optimize this.
t = self.__class__('%s/%d' % (self.network_address,
self.prefixlen - prefixlen_diff),
strict=False)
return t.__class__('%s/%d' % (t.network_address, t.prefixlen))
return self.__class__((
int(self.network_address) & (int(self.netmask) << prefixlen_diff),
new_prefixlen
))
@property
def is_multicast(self):
@ -1389,6 +1389,18 @@ def __init__(self, address):
self._prefixlen = self._max_prefixlen
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)
IPv4Address.__init__(self, addr[0])
@ -1504,22 +1516,42 @@ def __init__(self, address, strict=True):
_BaseV4.__init__(self, address)
_BaseNetwork.__init__(self, address)
# Constructing from a packed address
if isinstance(address, bytes):
# Constructing from a packed address or integer
if isinstance(address, (int, bytes)):
self.network_address = IPv4Address(address)
self._prefixlen = self._max_prefixlen
self.netmask = IPv4Address(self._ALL_ONES)
#fixme: address/network test here
return
# Efficient constructor from integer.
if isinstance(address, int):
self.network_address = IPv4Address(address)
if isinstance(address, tuple):
if len(address) > 1:
# If address[1] is a string, treat it like a netmask.
if isinstance(address[1], str):
self.netmask = IPv4Address(address[1])
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._ALL_ONES)
#fixme: address/network test here.
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
# Assume input argument to be string or any object representation
# which converts into a formatted IP prefix string.
addr = _split_optional_netmask(address)
@ -2030,6 +2062,16 @@ def __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 = _split_optional_netmask(address)
IPv6Address.__init__(self, addr[0])
@ -2147,18 +2189,29 @@ def __init__(self, address, strict=True):
_BaseV6.__init__(self, address)
_BaseNetwork.__init__(self, address)
# Efficient constructor from integer.
if isinstance(address, int):
# Efficient constructor from integer or packed address
if isinstance(address, (bytes, int)):
self.network_address = IPv6Address(address)
self._prefixlen = self._max_prefixlen
self.netmask = IPv6Address(self._ALL_ONES)
return
# Constructing from a packed address
if isinstance(address, bytes):
self.network_address = IPv6Address(address)
if isinstance(address, tuple):
self.network_address = IPv6Address(address[0])
if len(address) > 1:
self._prefixlen = int(address[1])
else:
self._prefixlen = self._max_prefixlen
self.netmask = IPv6Address(self._ALL_ONES)
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
# Assume input argument to be string or any object representation

View File

@ -628,6 +628,119 @@ def testRepr(self):
self.assertEqual("IPv6Interface('::1/128')",
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
def testAddressIntMath(self):
self.assertEqual(ipaddress.IPv4Address('1.1.1.1') + 255,

View File

@ -81,6 +81,10 @@ Core and Builtins
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
staticmethod.