Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions Doc/library/ipaddress.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1007,13 +1007,16 @@ The module also provides the following module level functions:

Return an iterator of the collapsed :class:`IPv4Network` or
:class:`IPv6Network` objects. *addresses* is an :term:`iterable` of
:class:`IPv4Network` or :class:`IPv6Network` objects. A :exc:`TypeError` is
raised if *addresses* contains mixed version objects.
:class:`IPv4Address`, :class:`IPv6Address`, :class:`IPv4Network` or
:class:`IPv6Network` objects.
A :exc:`TypeError` is raised if *addresses* contains mixed version objects.

>>> [ipaddr for ipaddr in
... ipaddress.collapse_addresses([ipaddress.IPv4Network('192.0.2.0/25'),
... ipaddress.IPv4Network('192.0.2.128/25')])]
>>> list(ipaddress.collapse_addresses([ipaddress.IPv4Network('192.0.2.0/25'),
... ipaddress.IPv4Network('192.0.2.128/25')]))
[IPv4Network('192.0.2.0/24')]
>>> list(ipaddress.collapse_addresses([ipaddress.IPv4Address('192.0.2.0'),
... ipaddress.IPv4Address('192.0.2.1'), ipaddress.IPv4Network('192.0.2.2/31')]))
[IPv4Network('192.0.2.0/30')]


.. function:: get_mixed_type_key(obj)
Expand Down
32 changes: 14 additions & 18 deletions Lib/ipaddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,21 +299,22 @@ def _collapse_addresses_internal(addresses):


def collapse_addresses(addresses):
"""Collapse a list of IP objects.
"""Collapse an iterable of IP addresses or networks.

Example:
collapse_addresses([IPv4Network('192.0.2.0/25'),
IPv4Network('192.0.2.128/25')]) ->
[IPv4Network('192.0.2.0/24')]

Args:
addresses: An iterable of IPv4Network or IPv6Network objects.
addresses: An iterable of IPv4Address, IPv6Address, IPv4Network or
IPv6Network objects.

Returns:
An iterator of the collapsed IPv(4|6)Network objects.

Raises:
TypeError: If passed a list of mixed version objects.
TypeError: If passed an iterable of mixed version objects.

"""
addrs = []
Expand All @@ -322,24 +323,19 @@ 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:
raise TypeError("%s and %s are not of the same version" % (
ip, ips[-1]))
if isinstance(ip, _BaseAddress) and not hasattr(ip, 'ip'):
ips.append(ip)
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:
ips.append(ip.ip)
except AttributeError:
elif isinstance(ip, _BaseNetwork):
if ip._prefixlen == ip.max_prefixlen:
ips.append(ip.network_address)
else:
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)
else:
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)
raise TypeError("expected an iterable of IP addresses or "
"networks, not %s" % type(ip).__name__)

# sort and dedup
ips = sorted(set(ips))
Expand Down
67 changes: 45 additions & 22 deletions Lib/test/test_ipaddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -1845,6 +1845,9 @@ def testSlash0Constructor(self):
'1.2.3.4/0')

def testCollapsing(self):
collapsed = ipaddress.collapse_addresses([])
self.assertEqual(list(collapsed), [])

# test only IP addresses including some duplicates
ip1 = ipaddress.IPv4Address('1.1.1.0')
ip2 = ipaddress.IPv4Address('1.1.1.1')
Expand All @@ -1858,27 +1861,21 @@ def testCollapsing(self):
self.assertEqual(list(collapsed),
[ipaddress.IPv4Network('1.1.1.0/30'),
ipaddress.IPv4Network('1.1.1.4/32')])

# test a mix of IP addresses and networks including some duplicates
ip1 = ipaddress.IPv4Address('1.1.1.0')
ip2 = ipaddress.IPv4Address('1.1.1.1')
ip3 = ipaddress.IPv4Address('1.1.1.2')
ip4 = ipaddress.IPv4Address('1.1.1.3')
#ip5 = ipaddress.IPv4Interface('1.1.1.4/30')
#ip6 = ipaddress.IPv4Interface('1.1.1.4/30')
# check that addresses are subsumed properly.
collapsed = ipaddress.collapse_addresses([ip1, ip2, ip3, ip4])
collapsed = ipaddress.collapse_addresses([ip1])
self.assertEqual(list(collapsed),
[ipaddress.IPv4Network('1.1.1.0/30')])
[ipaddress.IPv4Network('1.1.1.0/32')])
# test same IP addresses
self.assertEqual(list(ipaddress.collapse_addresses([ip1, ip1, ip6])),
[ipaddress.ip_network('1.1.1.0/32')])

# test only IP networks
ip1 = ipaddress.IPv4Network('1.1.0.0/24')
ip2 = ipaddress.IPv4Network('1.1.1.0/24')
ip3 = ipaddress.IPv4Network('1.1.2.0/24')
ip4 = ipaddress.IPv4Network('1.1.3.0/24')
ip5 = ipaddress.IPv4Network('1.1.4.0/24')
# stored in no particular order b/c we want CollapseAddr to call
# [].sort
# stored in no particular order b/c we want collapse_addresses()
# to call sorted()
ip6 = ipaddress.IPv4Network('1.1.0.0/22')
# check that addresses are subsumed properly.
collapsed = ipaddress.collapse_addresses([ip1, ip2, ip3, ip4, ip5,
Expand All @@ -1893,16 +1890,9 @@ def testCollapsing(self):
[ipaddress.IPv4Network('1.1.0.0/23')])

# test same IP networks
ip_same1 = ip_same2 = ipaddress.IPv4Network('1.1.1.1/32')
self.assertEqual(list(ipaddress.collapse_addresses(
[ip_same1, ip_same2])),
[ip_same1])
self.assertEqual(list(ipaddress.collapse_addresses([ip1, ip1])),
[ip1])

# test same IP addresses
ip_same1 = ip_same2 = ipaddress.IPv4Address('1.1.1.1')
self.assertEqual(list(ipaddress.collapse_addresses(
[ip_same1, ip_same2])),
[ipaddress.ip_network('1.1.1.1/32')])
ip1 = ipaddress.IPv6Network('2001::/100')
ip2 = ipaddress.IPv6Network('2001::/120')
ip3 = ipaddress.IPv6Network('2001::/96')
Expand All @@ -1917,6 +1907,21 @@ def testCollapsing(self):
collapsed = ipaddress.collapse_addresses([ip1, ip2, ip3])
self.assertEqual(list(collapsed), [ip3])

# test a mix of IP addresses and networks
ip1 = ipaddress.IPv4Address('1.1.1.0')
ip2 = ipaddress.IPv4Address('1.1.1.1')
ip3 = ipaddress.IPv4Network('1.1.1.2/31')
# check that addresses are subsumed properly.
collapsed = ipaddress.collapse_addresses([ip1, ip2, ip3])
self.assertEqual(list(collapsed),
[ipaddress.IPv4Network('1.1.1.0/30')])

# unsupported types
self.assertRaises(TypeError, ipaddress.collapse_addresses, [42])
self.assertRaises(TypeError, ipaddress.collapse_addresses, [None])
self.assertRaises(TypeError, ipaddress.collapse_addresses,
[ipaddress.IPv4Interface('1.1.1.4/30')])

# the toejam test
addr_tuples = [
(ipaddress.ip_address('1.1.1.1'),
Expand All @@ -1941,6 +1946,24 @@ def testCollapsing(self):
for ip1, ip2 in addr_tuples:
self.assertRaises(TypeError, ipaddress.collapse_addresses,
[ip1, ip2])
for ip1 in [
ipaddress.ip_address('1.1.1.1'),
ipaddress.IPv4Network('1.1.0.0/24'),
ipaddress.IPv4Network('1.1.0.0/32'),
]:
for ip2 in [
ipaddress.ip_address('::1'),
ipaddress.IPv6Network('2001::/120'),
ipaddress.IPv6Network('2001::/128'),
ipaddress.ip_address('::1%scope'),
ipaddress.IPv6Network('2001::%scope/120'),
ipaddress.IPv6Network('2001::%scope/128'),
]:
with self.subTest(ip1=ip1, ip2=ip2):
with self.assertRaises(TypeError):
list(ipaddress.collapse_addresses([ip1, ip2]))
with self.assertRaises(TypeError):
list(ipaddress.collapse_addresses([ip2, ip1]))

def testSummarizing(self):
#ip = ipaddress.ip_address
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Make :func:`ipaddress.collapse_addresses` always raising ``TypeError`` for
unsupported types, including IP interfaces. Document that IP addresses are
supported.
Loading