From eb4a4072777a68587a0154cd2297620c86a03fdc Mon Sep 17 00:00:00 2001 From: opavliuk Date: Fri, 31 May 2019 14:40:26 +0300 Subject: [PATCH 01/56] bpo-34788 Add tests for ipv6 scope_id support --- Lib/test/test_ipaddress.py | 531 +++++++++++++++++++++++++++++++++++-- 1 file changed, 508 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 9e17ea0c7aac85..39e5d44427740c 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -169,6 +169,15 @@ def assertBadLength(length): assertBadLength(15) assertBadLength(17) + def test_valid_scope_id(self): + self.assertInstancesEqual('ff02::5678%scope', 'ff02::5678') + self.assertInstancesEqual('ff02::5678%1', 'ff02::5678') + + def test_blank_scope_id(self): + address = ('::1%') + with self.assertAddressError('Invalid IPv6 address: "%r"', address): + self.factory(address) + class AddressTestCase_v4(BaseTestCase, CommonTestMixin_v4): factory = ipaddress.IPv4Address @@ -261,24 +270,30 @@ class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6): factory = ipaddress.IPv6Address def test_network_passed_as_address(self): - addr = "::1/24" - with self.assertAddressError("Unexpected '/' in %r", addr): - ipaddress.IPv6Address(addr) + def assertBadSplit(addr): + msg = "Unexpected '/' in %r" + with self.assertAddressError(msg, addr): + ipaddress.IPv6Address(addr) + assertBadSplit("::1/24") + assertBadSplit("::1%scope_id/24") def test_bad_address_split_v6_not_enough_parts(self): def assertBadSplit(addr): msg = "At least 3 parts expected in %r" - with self.assertAddressError(msg, addr): + with self.assertAddressError(msg, addr.split('%')[0]): ipaddress.IPv6Address(addr) assertBadSplit(":") assertBadSplit(":1") assertBadSplit("FEDC:9878") + assertBadSplit(":%scope") + assertBadSplit(":1%scope") + assertBadSplit("FEDC:9878%scope") def test_bad_address_split_v6_too_many_colons(self): def assertBadSplit(addr): msg = "At most 8 colons permitted in %r" - with self.assertAddressError(msg, addr): + with self.assertAddressError(msg, addr.split('%')[0]): ipaddress.IPv6Address(addr) assertBadSplit("9:8:7:6:5:4:3::2:1") @@ -288,10 +303,17 @@ def assertBadSplit(addr): # A trailing IPv4 address is two parts assertBadSplit("10:9:8:7:6:5:4:3:42.42.42.42") + assertBadSplit("9:8:7:6:5:4:3::2:1%scope") + assertBadSplit("10:9:8:7:6:5:4:3:2:1%scope") + assertBadSplit("::8:7:6:5:4:3:2:1%scope") + assertBadSplit("8:7:6:5:4:3:2:1::%scope") + # A trailing IPv4 address is two parts + assertBadSplit("10:9:8:7:6:5:4:3:42.42.42.42%scope") + def test_bad_address_split_v6_too_many_parts(self): def assertBadSplit(addr): msg = "Exactly 8 parts expected without '::' in %r" - with self.assertAddressError(msg, addr): + with self.assertAddressError(msg, addr.split('%')[0]): ipaddress.IPv6Address(addr) assertBadSplit("3ffe:0:0:0:0:0:0:0:1") @@ -301,18 +323,26 @@ def assertBadSplit(addr): assertBadSplit("9:8:7:6:5:4:3:42.42.42.42") assertBadSplit("7:6:5:4:3:42.42.42.42") + assertBadSplit("3ffe:0:0:0:0:0:0:0:1%scope") + assertBadSplit("9:8:7:6:5:4:3:2:1%scope") + assertBadSplit("7:6:5:4:3:2:1%scope") + # A trailing IPv4 address is two parts + assertBadSplit("9:8:7:6:5:4:3:42.42.42.42%scope") + assertBadSplit("7:6:5:4:3:42.42.42.42%scope") + def test_bad_address_split_v6_too_many_parts_with_double_colon(self): def assertBadSplit(addr): msg = "Expected at most 7 other parts with '::' in %r" - with self.assertAddressError(msg, addr): + with self.assertAddressError(msg, addr.split('%')[0]): ipaddress.IPv6Address(addr) assertBadSplit("1:2:3:4::5:6:7:8") + assertBadSplit("1:2:3:4::5:6:7:8%scope") def test_bad_address_split_v6_repeated_double_colon(self): def assertBadSplit(addr): msg = "At most one '::' permitted in %r" - with self.assertAddressError(msg, addr): + with self.assertAddressError(msg, addr.split('%')[0]): ipaddress.IPv6Address(addr) assertBadSplit("3ffe::1::1") @@ -326,10 +356,21 @@ def assertBadSplit(addr): assertBadSplit(":::") assertBadSplit('2001:db8:::1') + assertBadSplit("3ffe::1::1%scope") + assertBadSplit("1::2::3::4:5%scope") + assertBadSplit("2001::db:::1%scope") + assertBadSplit("3ffe::1::%scope") + assertBadSplit("::3ffe::1%scope") + assertBadSplit(":3ffe::1::1%scope") + assertBadSplit("3ffe::1::1:%scope") + assertBadSplit(":3ffe::1::1:%scope") + assertBadSplit(":::%scope") + assertBadSplit('2001:db8:::1%scope') + def test_bad_address_split_v6_leading_colon(self): def assertBadSplit(addr): msg = "Leading ':' only permitted as part of '::' in %r" - with self.assertAddressError(msg, addr): + with self.assertAddressError(msg, addr.split('%')[0]): ipaddress.IPv6Address(addr) assertBadSplit(":2001:db8::1") @@ -337,10 +378,15 @@ def assertBadSplit(addr): assertBadSplit(":1:2:3:4:5:6:") assertBadSplit(":6:5:4:3:2:1::") + assertBadSplit(":2001:db8::1%scope") + assertBadSplit(":1:2:3:4:5:6:7%scope") + assertBadSplit(":1:2:3:4:5:6:%scope") + assertBadSplit(":6:5:4:3:2:1::%scope") + def test_bad_address_split_v6_trailing_colon(self): def assertBadSplit(addr): msg = "Trailing ':' only permitted as part of '::' in %r" - with self.assertAddressError(msg, addr): + with self.assertAddressError(msg, addr.split('%')[0]): ipaddress.IPv6Address(addr) assertBadSplit("2001:db8::1:") @@ -348,9 +394,14 @@ def assertBadSplit(addr): assertBadSplit("::1.2.3.4:") assertBadSplit("::7:6:5:4:3:2:") + assertBadSplit("2001:db8::1:%scope") + assertBadSplit("1:2:3:4:5:6:7:%scope") + assertBadSplit("::1.2.3.4:%scope") + assertBadSplit("::7:6:5:4:3:2:%scope") + def test_bad_v4_part_in(self): def assertBadAddressPart(addr, v4_error): - with self.assertAddressError("%s in %r", v4_error, addr): + with self.assertAddressError("%s in %r", v4_error, addr.split('%')[0]): ipaddress.IPv6Address(addr) assertBadAddressPart("3ffe::1.net", "Expected 4 octets in '1.net'") @@ -364,9 +415,20 @@ def assertBadAddressPart(addr, v4_error): "Only decimal digits permitted in 'net' " "in '1.1.1.net'") + assertBadAddressPart("3ffe::1.net%scope", "Expected 4 octets in '1.net'") + assertBadAddressPart("3ffe::127.0.1%scope", + "Expected 4 octets in '127.0.1'") + assertBadAddressPart("::1.2.3%scope", + "Expected 4 octets in '1.2.3'") + assertBadAddressPart("::1.2.3.4.5%scope", + "Expected 4 octets in '1.2.3.4.5'") + assertBadAddressPart("3ffe::1.1.1.net%scope", + "Only decimal digits permitted in 'net' " + "in '1.1.1.net'") + def test_invalid_characters(self): def assertBadPart(addr, part): - msg = "Only hex digits permitted in %r in %r" % (part, addr) + msg = "Only hex digits permitted in %r in %r" % (part, addr.split('%')[0]) with self.assertAddressError(re.escape(msg)): ipaddress.IPv6Address(addr) @@ -377,10 +439,17 @@ def assertBadPart(addr, part): assertBadPart("1.2.3.4::", "1.2.3.4") assertBadPart('1234:axy::b', "axy") + assertBadPart("3ffe::goog%scope", "goog") + assertBadPart("3ffe::-0%scope", "-0") + assertBadPart("3ffe::+0%scope", "+0") + assertBadPart("3ffe::-1%scope", "-1") + assertBadPart("1.2.3.4::%scope", "1.2.3.4") + assertBadPart('1234:axy::b%scope', "axy") + def test_part_length(self): def assertBadPart(addr, part): msg = "At most 4 characters permitted in %r in %r" - with self.assertAddressError(msg, part, addr): + with self.assertAddressError(msg, part, addr.split('%')[0]): ipaddress.IPv6Address(addr) assertBadPart("::00000", "00000") @@ -388,11 +457,18 @@ def assertBadPart(addr, part): assertBadPart("02001:db8::", "02001") assertBadPart('2001:888888::1', "888888") + assertBadPart("::00000%scope", "00000") + assertBadPart("3ffe::10000%scope", "10000") + assertBadPart("02001:db8::%scope", "02001") + assertBadPart('2001:888888::1%scope', "888888") + def test_pickle(self): self.pickle_test('2001:db8::') + self.pickle_test('2001:db8::%scope') def test_weakref(self): weakref.ref(self.factory('2001:db8::')) + weakref.ref(self.factory('2001:db8::%scope')) class NetmaskTestMixin_v4(CommonTestMixin_v4): @@ -550,11 +626,20 @@ def test_no_mask(self): # IPv6Network has prefixlen, but IPv6Interface doesn't. # Should we add it to IPv4Interface too? (bpo-36392) + scoped_net = self.factory('::1%scope') + self.assertEqual(str(scoped_net), '::1%scope/128') + self.assertEqual(str(scoped_net.netmask), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff') + self.assertEqual(str(scoped_net.hostmask), '::') + def test_split_netmask(self): addr = "cafe:cafe::/128/190" with self.assertAddressError("Only one '/' permitted in %r" % addr): self.factory(addr) + scoped_addr = "cafe:cafe::%scope/128/190" + with self.assertAddressError("Only one '/' permitted in %r" % scoped_addr): + self.factory(scoped_addr) + def test_address_errors(self): def assertBadAddress(addr, details): with self.assertAddressError(details): @@ -567,6 +652,13 @@ def assertBadAddress(addr, details): assertBadAddress("10/8", "At least 3 parts") assertBadAddress("1234:axy::b", "Only hex digits") + assertBadAddress("/%scope", "Address cannot be empty") + assertBadAddress("/%scope8", "Address cannot be empty") + assertBadAddress("google.com%scope", "At least 3 parts") + assertBadAddress("1.2.3.4%scope", "At least 3 parts") + assertBadAddress("10%scope/8", "At least 3 parts") + assertBadAddress("1234:axy::b%scope", "Only hex digits") + def test_valid_netmask(self): # We only support CIDR for IPv6, because expanded netmasks are not # standard notation. @@ -578,6 +670,14 @@ def test_valid_netmask(self): # Zero prefix is treated as decimal. self.assertEqual(str(self.factory('::/0%d' % i)), net_str) + self.assertEqual(str(self.factory('2001:db8::%scope/32')), '2001:db8::%scope/32') + for i in range(0, 129): + # Generate and re-parse the CIDR format (trivial). + net_str = '::/%d' % i + self.assertEqual(str(self.factory(net_str)), net_str) + # Zero prefix is treated as decimal. + self.assertEqual(str(self.factory('::/0%d' % i)), net_str) + def test_netmask_errors(self): def assertBadNetmask(addr, netmask): msg = "%r is not a valid netmask" % netmask @@ -596,6 +696,8 @@ def assertBadNetmask(addr, netmask): assertBadNetmask("::1", "pudding") assertBadNetmask("::", "::") + assertBadNetmask("::1%scope", "pudding") + def test_netmask_in_tuple_errors(self): def assertBadNetmask(addr, netmask): msg = "%r is not a valid netmask" % netmask @@ -603,12 +705,15 @@ def assertBadNetmask(addr, netmask): self.factory((addr, netmask)) assertBadNetmask("::1", -1) assertBadNetmask("::1", 129) + assertBadNetmask("::1%scope", 129) def test_pickle(self): self.pickle_test('2001:db8::1000/124') self.pickle_test('2001:db8::1000/127') # IPV6LENGTH - 1 self.pickle_test('2001:db8::1000') # IPV6LENGTH + self.pickle_test('2001:db8::1000%scope') # IPV6LENGTH + class InterfaceTestCase_v6(BaseTestCase, NetmaskTestMixin_v6): factory = ipaddress.IPv6Interface @@ -635,6 +740,13 @@ def test_subnet_of(self): self.factory('2000:aaa::/48').subnet_of( self.factory('2000:aaa::/56'))) + self.assertFalse( + self.factory('2000:999::%scope/56').subnet_of( + self.factory('2000:aaa::%scope/48'))) + self.assertTrue( + self.factory('2000:aaa::%scope/56').subnet_of( + self.factory('2000:aaa::%scope/48'))) + def test_supernet_of(self): # containee left of container self.assertFalse( @@ -695,13 +807,19 @@ class ComparisonTests(unittest.TestCase): v6addr = ipaddress.IPv6Address(1) v6net = ipaddress.IPv6Network(1) v6intf = ipaddress.IPv6Interface(1) + v6addr_scoped = ipaddress.IPv6Address('::1%scope') + v6net_scoped= ipaddress.IPv6Network('::1%scope') + v6intf_scoped= ipaddress.IPv6Interface('::1%scope') v4_addresses = [v4addr, v4intf] v4_objects = v4_addresses + [v4net] v6_addresses = [v6addr, v6intf] v6_objects = v6_addresses + [v6net] + v6_scoped_addresses = [v6addr_scoped, v6intf_scoped] + v6_scoped_objects = v6_addresses + [v6net_scoped] objects = v4_objects + v6_objects + objects_with_scoped = objects + v6_scoped_objects v4addr2 = ipaddress.IPv4Address(2) v4net2 = ipaddress.IPv4Network(2) @@ -709,11 +827,14 @@ class ComparisonTests(unittest.TestCase): v6addr2 = ipaddress.IPv6Address(2) v6net2 = ipaddress.IPv6Network(2) v6intf2 = ipaddress.IPv6Interface(2) + v6addr2_scoped = ipaddress.IPv6Address('::2%scope') + v6net2_scoped = ipaddress.IPv6Network('::2%scope') + v6intf2_scoped = ipaddress.IPv6Interface('::2%scope') def test_foreign_type_equality(self): # __eq__ should never raise TypeError directly other = object() - for obj in self.objects: + for obj in self.objects_with_scoped: self.assertNotEqual(obj, other) self.assertFalse(obj == other) self.assertEqual(obj.__eq__(other), NotImplemented) @@ -728,8 +849,17 @@ def test_mixed_type_equality(self): continue self.assertNotEqual(lhs, rhs) + def test_ipv6_equality_despite_scope(self): + for lhs, rhs in zip(self.v6_objects, self.v6_scoped_objects): + self.assertEqual(lhs, rhs) + + def test_v4_with_v6_scoped_equality(self): + for lhs in self.v4_objects: + for rhs in self.v6_scoped_objects: + self.assertNotEqual(lhs, rhs) + def test_same_type_equality(self): - for obj in self.objects: + for obj in self.objects_with_scoped: self.assertEqual(obj, obj) self.assertLessEqual(obj, obj) self.assertGreaterEqual(obj, obj) @@ -742,6 +872,9 @@ def test_same_type_ordering(self): (self.v6addr, self.v6addr2), (self.v6net, self.v6net2), (self.v6intf, self.v6intf2), + (self.v6addr_scoped, self.v6addr2_scoped), + (self.v6net_scoped, self.v6net2_scoped), + (self.v6intf_scoped, self.v6intf2_scoped), ): self.assertNotEqual(lhs, rhs) self.assertLess(lhs, rhs) @@ -756,16 +889,21 @@ def test_same_type_ordering(self): def test_containment(self): for obj in self.v4_addresses: self.assertIn(obj, self.v4net) - for obj in self.v6_addresses: + for obj in self.v6_addresses + self.v6_scoped_addresses: self.assertIn(obj, self.v6net) - for obj in self.v4_objects + [self.v6net]: + for obj in self.v6_addresses + self.v6_scoped_addresses: + self.assertIn(obj, self.v6net_scoped) + + for obj in self.v4_objects + [self.v6net, self.v6net_scoped]: self.assertNotIn(obj, self.v6net) - for obj in self.v6_objects + [self.v4net]: + for obj in self.v4_objects + [self.v6net, self.v6net_scoped]: + self.assertNotIn(obj, self.v6net_scoped) + for obj in self.v6_objects + self.v6_scoped_objects + [self.v4net]: self.assertNotIn(obj, self.v4net) def test_mixed_type_ordering(self): - for lhs in self.objects: - for rhs in self.objects: + for lhs in self.objects_with_scoped: + for rhs in self.objects_with_scoped: if isinstance(lhs, type(rhs)) or isinstance(rhs, type(lhs)): continue self.assertRaises(TypeError, lambda: lhs < rhs) @@ -777,7 +915,7 @@ def test_foreign_type_ordering(self): other = object() smallest = SmallestObject() largest = LargestObject() - for obj in self.objects: + for obj in self.objects_with_scoped: with self.assertRaises(TypeError): obj < other with self.assertRaises(TypeError): @@ -799,14 +937,18 @@ def test_mixed_type_key(self): # with get_mixed_type_key, you can sort addresses and network. v4_ordered = [self.v4addr, self.v4net, self.v4intf] v6_ordered = [self.v6addr, self.v6net, self.v6intf] + v6_scoped_ordered = [self.v6addr_scoped, self.v6net_scoped, self.v6intf_scoped] self.assertEqual(v4_ordered, sorted(self.v4_objects, key=ipaddress.get_mixed_type_key)) self.assertEqual(v6_ordered, sorted(self.v6_objects, key=ipaddress.get_mixed_type_key)) - self.assertEqual(v4_ordered + v6_ordered, - sorted(self.objects, + self.assertEqual(v6_scoped_ordered, + sorted(self.v6_scoped_objects, + key=ipaddress.get_mixed_type_key)) + self.assertEqual(v4_ordered + v6_scoped_ordered, + sorted(self.v4_objects + self.v6_scoped_objects, key=ipaddress.get_mixed_type_key)) self.assertEqual(NotImplemented, ipaddress.get_mixed_type_key(object)) @@ -816,6 +958,8 @@ def test_incompatible_versions(self): v4net = ipaddress.ip_network('1.1.1.1') v6addr = ipaddress.ip_address('::1') v6net = ipaddress.ip_network('::1') + v6addr_scoped = ipaddress.ip_address('::1%scope') + v6net_scoped = ipaddress.ip_network('::1%scope') self.assertRaises(TypeError, v4addr.__lt__, v6addr) self.assertRaises(TypeError, v4addr.__gt__, v6addr) @@ -827,6 +971,16 @@ def test_incompatible_versions(self): self.assertRaises(TypeError, v6net.__lt__, v4net) self.assertRaises(TypeError, v6net.__gt__, v4net) + self.assertRaises(TypeError, v4addr.__lt__, v6addr_scoped) + self.assertRaises(TypeError, v4addr.__gt__, v6addr_scoped) + self.assertRaises(TypeError, v4net.__lt__, v6net_scoped) + self.assertRaises(TypeError, v4net.__gt__, v6net_scoped) + + self.assertRaises(TypeError, v6addr_scoped.__lt__, v4addr) + self.assertRaises(TypeError, v6addr_scoped.__gt__, v4addr) + self.assertRaises(TypeError, v6net_scoped.__lt__, v4net) + self.assertRaises(TypeError, v6net_scoped.__gt__, v4net) + class IpaddrUnitTest(unittest.TestCase): @@ -840,12 +994,19 @@ def setUp(self): self.ipv6_interface = ipaddress.IPv6Interface( '2001:658:22a:cafe:200:0:0:1/64') self.ipv6_network = ipaddress.IPv6Network('2001:658:22a:cafe::/64') + self.ipv6_scoped_address = ipaddress.IPv6Interface( + '2001:658:22a:cafe:200:0:0:1%scope') + self.ipv6_scoped_interface = ipaddress.IPv6Interface( + '2001:658:22a:cafe:200:0:0:1%scope/64') + self.ipv6_scoped_network = ipaddress.IPv6Network('2001:658:22a:cafe::%scope/64') def testRepr(self): self.assertEqual("IPv4Interface('1.2.3.4/32')", repr(ipaddress.IPv4Interface('1.2.3.4'))) self.assertEqual("IPv6Interface('::1/128')", repr(ipaddress.IPv6Interface('::1'))) + self.assertEqual("IPv6Interface('::1%scope/128')", + repr(ipaddress.IPv6Interface('::1%scope'))) # issue #16531: constructing IPv4Network from an (address, mask) tuple def testIPv4Tuple(self): @@ -932,6 +1093,16 @@ def testIPv6Tuple(self): self.assertEqual(ipaddress.IPv6Network((ip, '96')), net) + ip_scoped = ipaddress.IPv6Address('2001:db8::%scope') + net_scoped = ipaddress.IPv6Network('2001:db8::%scope/128') + self.assertEqual(ipaddress.IPv6Network(('2001:db8::%scope', '128')), + net_scoped) + self.assertEqual(ipaddress.IPv6Network( + (42540766411282592856903984951653826560, 128)), + net_scoped) + self.assertEqual(ipaddress.IPv6Network((ip_scoped, '128')), + net_scoped) + # strict=True and host bits set ip = ipaddress.IPv6Address('2001:db8::1') with self.assertRaises(ValueError): @@ -960,6 +1131,23 @@ def testIPv6Tuple(self): (42540766411282592856903984951653826561, '96')), ipaddress.IPv6Interface('2001:db8::1/96')) + ip_scoped = ipaddress.IPv6Address('2001:db8::1%scope') + with self.assertRaises(ValueError): + ipaddress.IPv6Network(('2001:db8::1%scope', 96)) + with self.assertRaises(ValueError): + ipaddress.IPv6Network((ip_scoped, 96)) + # strict=False and host bits set + net_scoped = ipaddress.IPv6Network('2001:db8::/96') + self.assertEqual(ipaddress.IPv6Network(('2001:db8::1%scope', 96), + strict=False), + net_scoped) + self.assertEqual(ipaddress.IPv6Network( + (42540766411282592856903984951653826561, 96), + strict=False), + net_scoped) + self.assertEqual(ipaddress.IPv6Network((ip_scoped, 96), strict=False), + net_scoped) + # issue57 def testAddressIntMath(self): self.assertEqual(ipaddress.IPv4Address('1.1.1.1') + 255, @@ -970,6 +1158,10 @@ def testAddressIntMath(self): ipaddress.IPv6Address('::ffff')) self.assertEqual(ipaddress.IPv6Address('::ffff') - (2**16 - 2), ipaddress.IPv6Address('::1')) + self.assertEqual(ipaddress.IPv6Address('::1%scope') + (2**16 - 2), + ipaddress.IPv6Address('::ffff%scope')) + self.assertEqual(ipaddress.IPv6Address('::ffff%scope') - (2**16 - 2), + ipaddress.IPv6Address('::1%scope')) def testInvalidIntToBytes(self): self.assertRaises(ValueError, ipaddress.v4_int_to_packed, -1) @@ -1002,6 +1194,12 @@ def testGetNetwork(self): '2001:658:22a:cafe::') self.assertEqual(str(self.ipv6_network.hostmask), '::ffff:ffff:ffff:ffff') + self.assertEqual(int(self.ipv6_scoped_network.network_address), + 42540616829182469433403647294022090752) + self.assertEqual(str(self.ipv6_scoped_network.network_address), + '2001:658:22a:cafe::%scope') + self.assertEqual(str(self.ipv6_scoped_network.hostmask), + '::ffff:ffff:ffff:ffff') def testIpFromInt(self): self.assertEqual(self.ipv4_interface._ip, @@ -1009,17 +1207,23 @@ def testIpFromInt(self): ipv4 = ipaddress.ip_network('1.2.3.4') ipv6 = ipaddress.ip_network('2001:658:22a:cafe:200:0:0:1') + ipv6_scoped = ipaddress.ip_network('2001:658:22a:cafe:200:0:0:1%scope') self.assertEqual(ipv4, ipaddress.ip_network(int(ipv4.network_address))) self.assertEqual(ipv6, ipaddress.ip_network(int(ipv6.network_address))) + self.assertEqual(ipv6_scoped, ipaddress.ip_network(int(ipv6_scoped.network_address))) v6_int = 42540616829182469433547762482097946625 self.assertEqual(self.ipv6_interface._ip, ipaddress.IPv6Interface(v6_int)._ip) + self.assertEqual(self.ipv6_scoped_interface._ip, + ipaddress.IPv6Interface(v6_int)._ip) self.assertEqual(ipaddress.ip_network(self.ipv4_address._ip).version, 4) self.assertEqual(ipaddress.ip_network(self.ipv6_address._ip).version, 6) + self.assertEqual(ipaddress.ip_network(self.ipv6_scoped_address._ip).version, + 6) def testIpFromPacked(self): address = ipaddress.ip_address @@ -1045,6 +1249,24 @@ def testGetIp(self): 42540616829182469433547762482097946625) self.assertEqual(str(self.ipv6_interface.ip), '2001:658:22a:cafe:200::1') + self.assertEqual(int(self.ipv6_scoped_interface.ip), + 42540616829182469433547762482097946625) + self.assertEqual(str(self.ipv6_scoped_interface.ip), + '2001:658:22a:cafe:200::1') + + def testGetScopeId(self): + self.assertEqual(self.ipv6_address.scope_id, + None) + self.assertEqual(str(self.ipv6_scoped_address.scope_id), + 'scope') + self.assertEqual(self.ipv6_interface.scope_id, + None) + self.assertEqual(str(self.ipv6_scoped_interface.scope_id), + 'scope') + self.assertEqual(self.ipv6_network.network_address.scope_id, + None) + self.assertEqual(str(self.ipv6_scoped_network.network_address.scope_id), + 'scope') def testGetNetmask(self): self.assertEqual(int(self.ipv4_network.netmask), 4294967040) @@ -1052,6 +1274,9 @@ def testGetNetmask(self): self.assertEqual(int(self.ipv6_network.netmask), 340282366920938463444927863358058659840) self.assertEqual(self.ipv6_network.prefixlen, 64) + self.assertEqual(int(self.ipv6_scoped_network.netmask), + 340282366920938463444927863358058659840) + self.assertEqual(self.ipv6_scoped_network.prefixlen, 64) def testZeroNetmask(self): ipv4_zero_netmask = ipaddress.IPv4Interface('1.2.3.4/0') @@ -1062,6 +1287,10 @@ def testZeroNetmask(self): self.assertEqual(int(ipv6_zero_netmask.network.netmask), 0) self.assertEqual(ipv6_zero_netmask._prefix_from_prefix_string('0'), 0) + ipv6_scoped_zero_netmask = ipaddress.IPv6Interface('::1%scope/0') + self.assertEqual(int(ipv6_scoped_zero_netmask.network.netmask), 0) + self.assertEqual(ipv6_scoped_zero_netmask._prefix_from_prefix_string('0'), 0) + def testIPv4Net(self): net = ipaddress.IPv4Network('127.0.0.0/0.0.0.255') self.assertEqual(net.prefixlen, 24) @@ -1075,9 +1304,15 @@ def testGetBroadcast(self): self.assertEqual(str(self.ipv6_network.broadcast_address), '2001:658:22a:cafe:ffff:ffff:ffff:ffff') + self.assertEqual(int(self.ipv6_scoped_network.broadcast_address), + 42540616829182469451850391367731642367) + self.assertEqual(str(self.ipv6_scoped_network.broadcast_address), + '2001:658:22a:cafe:ffff:ffff:ffff:ffff') + def testGetPrefixlen(self): self.assertEqual(self.ipv4_interface.network.prefixlen, 24) self.assertEqual(self.ipv6_interface.network.prefixlen, 64) + self.assertEqual(self.ipv6_scoped_interface.network.prefixlen, 64) def testGetSupernet(self): self.assertEqual(self.ipv4_network.supernet().prefixlen, 23) @@ -1092,6 +1327,9 @@ def testGetSupernet(self): '2001:658:22a:cafe::') self.assertEqual(ipaddress.IPv6Interface('::0/0').network.supernet(), ipaddress.IPv6Network('::0/0')) + self.assertEqual(self.ipv6_scoped_network.supernet().prefixlen, 63) + self.assertEqual(str(self.ipv6_scoped_network.supernet().network_address), + '2001:658:22a:cafe::') def testGetSupernet3(self): self.assertEqual(self.ipv4_network.supernet(3).prefixlen, 21) @@ -1101,6 +1339,9 @@ def testGetSupernet3(self): self.assertEqual(self.ipv6_network.supernet(3).prefixlen, 61) self.assertEqual(str(self.ipv6_network.supernet(3).network_address), '2001:658:22a:caf8::') + self.assertEqual(self.ipv6_scoped_network.supernet(3).prefixlen, 61) + self.assertEqual(str(self.ipv6_scoped_network.supernet(3).network_address), + '2001:658:22a:caf8::') def testGetSupernet4(self): self.assertRaises(ValueError, self.ipv4_network.supernet, @@ -1116,6 +1357,12 @@ def testGetSupernet4(self): new_prefix=65) self.assertEqual(self.ipv6_network.supernet(prefixlen_diff=2), self.ipv6_network.supernet(new_prefix=62)) + self.assertRaises(ValueError, self.ipv6_scoped_network.supernet, + prefixlen_diff=2, new_prefix=1) + self.assertRaises(ValueError, self.ipv6_scoped_network.supernet, + new_prefix=65) + self.assertEqual(self.ipv6_scoped_network.supernet(prefixlen_diff=2), + self.ipv6_scoped_network.supernet(new_prefix=62)) def testHosts(self): hosts = list(self.ipv4_network.hosts()) @@ -1129,6 +1376,12 @@ def testHosts(self): self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::1'), hosts[0]) self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::ff'), hosts[-1]) + ipv6_scoped_network = ipaddress.IPv6Network('2001:658:22a:cafe::%scope/120') + hosts = list(ipv6_scoped_network.hosts()) + self.assertEqual(255, len(hosts)) + self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::1'), hosts[0]) + self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::ff'), hosts[-1]) + # special case where only 1 bit is left for address addrs = [ipaddress.IPv4Address('2.0.0.0'), ipaddress.IPv4Address('2.0.0.1')] @@ -1147,6 +1400,30 @@ def testHosts(self): self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts())) self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), list(ipaddress.ip_network(tpl_args).hosts())) + addrs = [ipaddress.IPv6Address('2001:658:22a:cafe::%scope'), + ipaddress.IPv6Address('2001:658:22a:cafe::1%scope')] + str_args = '2001:658:22a:cafe::%scope/127' + tpl_args = ('2001:658:22a:cafe::%scope', 127) + self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts())) + self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts())) + self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), + list(ipaddress.ip_network(tpl_args).hosts())) + addrs = [ipaddress.IPv6Address('2001:658:22a:cafe::'), + ipaddress.IPv6Address('2001:658:22a:cafe::1')] + str_args = '2001:658:22a:cafe::%scope/127' + tpl_args = ('2001:658:22a:cafe::%scope', 127) + self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts())) + self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts())) + self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), + list(ipaddress.ip_network(tpl_args).hosts())) + addrs = [ipaddress.IPv6Address('2001:658:22a:cafe::%scope'), + ipaddress.IPv6Address('2001:658:22a:cafe::1%scope')] + str_args = '2001:658:22a:cafe::/127' + tpl_args = ('2001:658:22a:cafe::', 127) + self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts())) + self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts())) + self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), + list(ipaddress.ip_network(tpl_args).hosts())) def testFancySubnetting(self): self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)), @@ -1163,6 +1440,13 @@ def testFancySubnetting(self): self.assertRaises(ValueError, list, self.ipv6_network.subnets(prefixlen_diff=4, new_prefix=68)) + self.assertEqual(sorted(self.ipv6_scoped_network.subnets(prefixlen_diff=4)), + sorted(self.ipv6_scoped_network.subnets(new_prefix=68))) + self.assertRaises(ValueError, list, + self.ipv6_scoped_network.subnets(new_prefix=63)) + self.assertRaises(ValueError, list, + self.ipv6_scoped_network.subnets(prefixlen_diff=4, + new_prefix=68)) def testGetSubnets(self): self.assertEqual(list(self.ipv4_network.subnets())[0].prefixlen, 25) @@ -1174,6 +1458,7 @@ def testGetSubnets(self): '1.2.3.128') self.assertEqual(list(self.ipv6_network.subnets())[0].prefixlen, 65) + self.assertEqual(list(self.ipv6_scoped_network.subnets())[0].prefixlen, 65) def testGetSubnetForSingle32(self): ip = ipaddress.IPv4Network('1.2.3.4/32') @@ -1189,6 +1474,12 @@ def testGetSubnetForSingle128(self): self.assertEqual(subnets1, ['::1/128']) self.assertEqual(subnets1, subnets2) + ip_scoped = ipaddress.IPv6Network('::1%scope/128') + subnets1 = [str(x) for x in ip_scoped.subnets()] + subnets2 = [str(x) for x in ip_scoped.subnets(2)] + self.assertEqual(subnets1, ['::1%scope/128']) + self.assertEqual(subnets1, subnets2) + def testSubnet2(self): ips = [str(x) for x in self.ipv4_network.subnets(2)] self.assertEqual( @@ -1232,12 +1523,18 @@ def testSubnetFailsForLargeCidrDiff(self): self.ipv6_interface.network.subnets(65)) self.assertRaises(ValueError, list, self.ipv6_network.subnets(65)) + self.assertRaises(ValueError, list, + self.ipv6_scoped_interface.network.subnets(65)) + self.assertRaises(ValueError, list, + self.ipv6_scoped_network.subnets(65)) def testSupernetFailsForLargeCidrDiff(self): self.assertRaises(ValueError, self.ipv4_interface.network.supernet, 25) self.assertRaises(ValueError, self.ipv6_interface.network.supernet, 65) + self.assertRaises(ValueError, + self.ipv6_scoped_interface.network.supernet, 65) def testSubnetFailsForNegativeCidrDiff(self): self.assertRaises(ValueError, list, @@ -1248,6 +1545,10 @@ def testSubnetFailsForNegativeCidrDiff(self): self.ipv6_interface.network.subnets(-1)) self.assertRaises(ValueError, list, self.ipv6_network.subnets(-1)) + self.assertRaises(ValueError, list, + self.ipv6_scoped_interface.network.subnets(-1)) + self.assertRaises(ValueError, list, + self.ipv6_scoped_network.subnets(-1)) def testGetNum_Addresses(self): self.assertEqual(self.ipv4_network.num_addresses, 256) @@ -1260,6 +1561,11 @@ def testGetNum_Addresses(self): 9223372036854775808) self.assertEqual(self.ipv6_network.supernet().num_addresses, 36893488147419103232) + self.assertEqual(self.ipv6_scoped_network.num_addresses, 18446744073709551616) + self.assertEqual(list(self.ipv6_scoped_network.subnets())[0].num_addresses, + 9223372036854775808) + self.assertEqual(self.ipv6_scoped_network.supernet().num_addresses, + 36893488147419103232) def testContains(self): self.assertIn(ipaddress.IPv4Interface('1.2.3.128/25'), @@ -1281,6 +1587,9 @@ def testNth(self): self.assertEqual(str(self.ipv6_network[5]), '2001:658:22a:cafe::5') self.assertRaises(IndexError, self.ipv6_network.__getitem__, 1 << 64) + self.assertEqual(str(self.ipv6_scoped_network[5]), + '2001:658:22a:cafe::5') + self.assertRaises(IndexError, self.ipv6_scoped_network.__getitem__, 1 << 64) def testGetitem(self): # http://code.google.com/p/ipaddr-py/issues/detail?id=15 @@ -1300,6 +1609,8 @@ def testEqual(self): ipaddress.IPv4Interface('1.2.3.4/23')) self.assertFalse(self.ipv4_interface == ipaddress.IPv6Interface('::1.2.3.4/24')) + self.assertFalse(self.ipv4_interface == + ipaddress.IPv6Interface('::1.2.3.4%scope/24')) self.assertFalse(self.ipv4_interface == '') self.assertFalse(self.ipv4_interface == []) self.assertFalse(self.ipv4_interface == 2) @@ -1314,6 +1625,20 @@ def testEqual(self): self.assertFalse(self.ipv6_interface == []) self.assertFalse(self.ipv6_interface == 2) + self.assertTrue(self.ipv6_scoped_interface == + ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/64')) + self.assertFalse(self.ipv6_scoped_interface == + ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/63')) + self.assertTrue(self.ipv6_scoped_interface == + ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/64')) + self.assertFalse(self.ipv6_scoped_interface == + ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/63')) + self.assertFalse(self.ipv6_scoped_interface == + ipaddress.IPv4Interface('1.2.3.4/23')) + self.assertFalse(self.ipv6_scoped_interface == '') + self.assertFalse(self.ipv6_scoped_interface == []) + self.assertFalse(self.ipv6_scoped_interface == 2) + def testNotEqual(self): self.assertFalse(self.ipv4_interface != ipaddress.IPv4Interface('1.2.3.4/24')) @@ -1321,6 +1646,8 @@ def testNotEqual(self): ipaddress.IPv4Interface('1.2.3.4/23')) self.assertTrue(self.ipv4_interface != ipaddress.IPv6Interface('::1.2.3.4/24')) + self.assertTrue(self.ipv4_interface != + ipaddress.IPv6Interface('::1.2.3.4%scope/24')) self.assertTrue(self.ipv4_interface != '') self.assertTrue(self.ipv4_interface != []) self.assertTrue(self.ipv4_interface != 2) @@ -1347,6 +1674,26 @@ def testNotEqual(self): self.assertTrue(self.ipv6_address != []) self.assertTrue(self.ipv6_address != 2) + self.assertFalse(self.ipv6_scoped_interface != + ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/64')) + self.assertTrue(self.ipv6_scoped_interface != + ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/63')) + self.assertFalse(self.ipv6_scoped_interface != + ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/64')) + self.assertTrue(self.ipv6_scoped_interface != + ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/63')) + self.assertTrue(self.ipv6_scoped_interface != + ipaddress.IPv4Interface('1.2.3.4/23')) + self.assertTrue(self.ipv6_scoped_interface != '') + self.assertTrue(self.ipv6_scoped_interface != []) + self.assertTrue(self.ipv6_scoped_interface != 2) + + self.assertTrue(self.ipv6_scoped_address != + ipaddress.IPv4Address('1.2.3.4')) + self.assertTrue(self.ipv6_scoped_address != '') + self.assertTrue(self.ipv6_scoped_address != []) + self.assertTrue(self.ipv6_scoped_address != 2) + def testSlash32Constructor(self): self.assertEqual(str(ipaddress.IPv4Interface( '1.2.3.4/255.255.255.255')), '1.2.3.4/32') @@ -1354,6 +1701,8 @@ def testSlash32Constructor(self): def testSlash128Constructor(self): self.assertEqual(str(ipaddress.IPv6Interface('::1/128')), '::1/128') + self.assertEqual(str(ipaddress.IPv6Interface('::1%scope/128')), + '::1%scope/128') def testSlash0Constructor(self): self.assertEqual(str(ipaddress.IPv4Interface('1.2.3.4/0.0.0.0')), @@ -1425,6 +1774,13 @@ def testCollapsing(self): collapsed = ipaddress.collapse_addresses([ip1, ip2, ip3]) self.assertEqual(list(collapsed), [ip3]) + ip1 = ipaddress.IPv6Network('2001::%scope/100') + ip2 = ipaddress.IPv6Network('2001::%scope/120') + ip3 = ipaddress.IPv6Network('2001::%scope/96') + # test that ipv6 addresses are subsumed properly. + collapsed = ipaddress.collapse_addresses([ip1, ip2, ip3]) + self.assertEqual(list(collapsed), [ip3]) + # the toejam test addr_tuples = [ (ipaddress.ip_address('1.1.1.1'), @@ -1438,6 +1794,18 @@ def testCollapsing(self): self.assertRaises(TypeError, ipaddress.collapse_addresses, [ip1, ip2]) + addr_tuples = [ + (ipaddress.ip_address('1.1.1.1'), + ipaddress.ip_address('::1%scope')), + (ipaddress.IPv4Network('1.1.0.0/24'), + ipaddress.IPv6Network('2001::%scope/120')), + (ipaddress.IPv4Network('1.1.0.0/32'), + ipaddress.IPv6Network('2001::%scope/128')), + ] + for ip1, ip2 in addr_tuples: + self.assertRaises(TypeError, ipaddress.collapse_addresses, + [ip1, ip2]) + def testSummarizing(self): #ip = ipaddress.ip_address #ipnet = ipaddress.ip_network @@ -1457,6 +1825,8 @@ def version(self): # test that a summary over ip4 & ip6 fails self.assertRaises(TypeError, list, summarize(ip1, ipaddress.IPv6Address('::1'))) + self.assertRaises(TypeError, list, + summarize(ip1, ipaddress.IPv6Address('::1%scope'))) # test a /24 is summarized properly self.assertEqual(list(summarize(ip1, ip2))[0], ipaddress.ip_network('1.1.1.0/24')) @@ -1482,6 +1852,17 @@ def version(self): [ipaddress.ip_network('1::/16'), ipaddress.ip_network('2::/128')]) + ip1 = ipaddress.ip_address('1::%scope') + ip2 = ipaddress.ip_address('1:ffff:ffff:ffff:ffff:ffff:ffff:ffff%scope') + # test an IPv6 is summarized properly + self.assertEqual(list(summarize(ip1, ip2))[0], + ipaddress.ip_network('1::/16')) + # test an IPv6 range that isn't on a network byte boundary + ip2 = ipaddress.ip_address('2::%scope') + self.assertEqual(list(summarize(ip1, ip2)), + [ipaddress.ip_network('1::/16'), + ipaddress.ip_network('2::/128')]) + # test exception raised when first is greater than last self.assertRaises(ValueError, list, summarize(ipaddress.ip_address('1.1.1.0'), @@ -1507,6 +1888,18 @@ def testAddressComparison(self): ipaddress.ip_address('::1')) self.assertTrue(ipaddress.ip_address('::1') <= ipaddress.ip_address('::2')) + self.assertTrue(ipaddress.ip_address('::1%scope') <= + ipaddress.ip_address('::1%scope')) + self.assertTrue(ipaddress.ip_address('::1%scope') <= + ipaddress.ip_address('::2%scope')) + self.assertTrue(ipaddress.ip_address('::1') <= + ipaddress.ip_address('::1%scope')) + self.assertTrue(ipaddress.ip_address('::1') <= + ipaddress.ip_address('::2%scope')) + self.assertTrue(ipaddress.ip_address('::1%scope') <= + ipaddress.ip_address('::1')) + self.assertTrue(ipaddress.ip_address('::1%scope') <= + ipaddress.ip_address('::2')) def testInterfaceComparison(self): self.assertTrue(ipaddress.ip_interface('1.1.1.1/24') == @@ -1539,6 +1932,52 @@ def testInterfaceComparison(self): self.assertTrue(ipaddress.ip_interface('::1/64') > ipaddress.ip_interface('::2/48')) + self.assertTrue(ipaddress.ip_interface('::1%scope/64') == + ipaddress.ip_interface('::1%scope/64')) + self.assertTrue(ipaddress.ip_interface('::1%scope/64') < + ipaddress.ip_interface('::1%scope/80')) + self.assertTrue(ipaddress.ip_interface('::1%scope/64') < + ipaddress.ip_interface('::2%scope/64')) + self.assertTrue(ipaddress.ip_interface('::2%scope/48') < + ipaddress.ip_interface('::1%scope/64')) + self.assertTrue(ipaddress.ip_interface('::1%scope/80') > + ipaddress.ip_interface('::1%scope/64')) + self.assertTrue(ipaddress.ip_interface('::2%scope/64') > + ipaddress.ip_interface('::1%scope/64')) + self.assertTrue(ipaddress.ip_interface('::1%scope/64') > + ipaddress.ip_interface('::2%scope/48')) + + + self.assertTrue(ipaddress.ip_interface('::1%scope/64') == + ipaddress.ip_interface('::1/64')) + self.assertTrue(ipaddress.ip_interface('::1%scope/64') < + ipaddress.ip_interface('::1/80')) + self.assertTrue(ipaddress.ip_interface('::1%scope/64') < + ipaddress.ip_interface('::2/64')) + self.assertTrue(ipaddress.ip_interface('::2%scope/48') < + ipaddress.ip_interface('::1/64')) + self.assertTrue(ipaddress.ip_interface('::1%scope/80') > + ipaddress.ip_interface('::1/64')) + self.assertTrue(ipaddress.ip_interface('::2%scope/64') > + ipaddress.ip_interface('::1/64')) + self.assertTrue(ipaddress.ip_interface('::1%scope/64') > + ipaddress.ip_interface('::2/48')) + + self.assertTrue(ipaddress.ip_interface('::1/64') == + ipaddress.ip_interface('::1%scope/64')) + self.assertTrue(ipaddress.ip_interface('::1/64') < + ipaddress.ip_interface('::1%scope/80')) + self.assertTrue(ipaddress.ip_interface('::1/64') < + ipaddress.ip_interface('::2%scope/64')) + self.assertTrue(ipaddress.ip_interface('::2/48') < + ipaddress.ip_interface('::1%scope/64')) + self.assertTrue(ipaddress.ip_interface('::1/80') > + ipaddress.ip_interface('::1%scope/64')) + self.assertTrue(ipaddress.ip_interface('::2/64') > + ipaddress.ip_interface('::1%scope/64')) + self.assertTrue(ipaddress.ip_interface('::1/64') > + ipaddress.ip_interface('::2%scope/48')) + def testNetworkComparison(self): # ip1 and ip2 have the same network address ip1 = ipaddress.IPv4Network('1.1.1.0/24') @@ -1618,6 +2057,7 @@ def testNetworkComparison(self): ipaddress.ip_network('1.1.1.2')) self.assertFalse(ipaddress.ip_network('1.1.1.2') <= ipaddress.ip_network('1.1.1.1')) + self.assertTrue(ipaddress.ip_network('::1') <= ipaddress.ip_network('::1')) self.assertTrue(ipaddress.ip_network('::1') <= @@ -1625,9 +2065,31 @@ def testNetworkComparison(self): self.assertFalse(ipaddress.ip_network('::2') <= ipaddress.ip_network('::1')) + self.assertTrue(ipaddress.ip_network('::1%scope') <= + ipaddress.ip_network('::1%scope')) + self.assertTrue(ipaddress.ip_network('::1%scope') <= + ipaddress.ip_network('::2%scope')) + self.assertFalse(ipaddress.ip_network('::2%scope') <= + ipaddress.ip_network('::1%scope')) + + self.assertTrue(ipaddress.ip_network('::1%scope') <= + ipaddress.ip_network('::1')) + self.assertTrue(ipaddress.ip_network('::1%scope') <= + ipaddress.ip_network('::2')) + self.assertFalse(ipaddress.ip_network('::2%scope') <= + ipaddress.ip_network('::1')) + + self.assertTrue(ipaddress.ip_network('::1') <= + ipaddress.ip_network('::1%scope')) + self.assertTrue(ipaddress.ip_network('::1') <= + ipaddress.ip_network('::2%scope')) + self.assertFalse(ipaddress.ip_network('::2') <= + ipaddress.ip_network('::1%scope')) + def testStrictNetworks(self): self.assertRaises(ValueError, ipaddress.ip_network, '192.168.1.1/24') self.assertRaises(ValueError, ipaddress.ip_network, '::1/120') + self.assertRaises(ValueError, ipaddress.ip_network, '::1%scope/120') def testOverlaps(self): other = ipaddress.IPv4Network('1.2.3.0/30') @@ -1656,13 +2118,28 @@ def testIPv6AddressTooLarge(self): self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1'), ipaddress.ip_address('FFFF::c000:201')) + self.assertEqual(ipaddress.ip_address('::FFFF:192.0.2.1%scope'), + ipaddress.ip_address('::FFFF:c000:201%scope')) + self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1%scope'), + ipaddress.ip_address('FFFF::c000:201%scope')) + self.assertEqual(ipaddress.ip_address('::FFFF:192.0.2.1%scope'), + ipaddress.ip_address('::FFFF:c000:201')) + self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1%scope'), + ipaddress.ip_address('FFFF::c000:201')) + self.assertEqual(ipaddress.ip_address('::FFFF:192.0.2.1'), + ipaddress.ip_address('::FFFF:c000:201%scope')) + self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1'), + ipaddress.ip_address('FFFF::c000:201%scope')) + def testIPVersion(self): 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(self.ipv4_interface.max_prefixlen, 32) self.assertEqual(self.ipv6_interface.max_prefixlen, 128) + self.assertEqual(self.ipv6_scoped_interface.max_prefixlen, 128) def testPacked(self): self.assertEqual(self.ipv4_address.packed, @@ -1677,6 +2154,14 @@ def testPacked(self): + b'\x00' * 6) self.assertEqual(ipaddress.IPv6Interface('::1:0:0:0:0').packed, b'\x00' * 6 + b'\x00\x01' + b'\x00' * 8) + self.assertEqual(self.ipv6_scoped_address.packed, + b'\x20\x01\x06\x58\x02\x2a\xca\xfe' + b'\x02\x00\x00\x00\x00\x00\x00\x01') + self.assertEqual(ipaddress.IPv6Interface('ffff:2:3:4:ffff::%scope').packed, + b'\xff\xff\x00\x02\x00\x03\x00\x04\xff\xff' + + b'\x00' * 6) + self.assertEqual(ipaddress.IPv6Interface('::1:0:0:0:0%scope').packed, + b'\x00' * 6 + b'\x00\x01' + b'\x00' * 8) def testIpType(self): ipv4net = ipaddress.ip_network('1.2.3.4') From 8a5c94fe0fce47fc62c117c048ea0ebb12531b29 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Fri, 31 May 2019 14:40:50 +0300 Subject: [PATCH 02/56] bpo-34788 Add support for scoped IPv6 addresses Support for scoped IPv6 addresses (including network and interface addresses) is required according to RFC 4007 (https://tools.ietf.org/html/rfc4007). --- Lib/ipaddress.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 873c7644081af6..b398e2a9148069 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1785,6 +1785,30 @@ def _reverse_pointer(self): reverse_chars = self.exploded[::-1].replace(':', '') return '.'.join(reverse_chars) + '.ip6.arpa' + @staticmethod + def _split_scope_id(ip_str): + """Helper function to parse IPv6 string address with scope id. + + See RFC 4007 for details. + + Arg: + ip_str: A string, the IPv6 address. + + Returns: + [addr, scope_id] list. + """ + if '%' not in ip_str: + return ip_str, None + + split_addr = ip_str.split('%') + if len(split_addr) > 2 or split_addr[-1] == '': + raise AddressValueError('Invalid IPv6 address: "%r"' % ip_str) + try: + addr, scope_id = split_addr + except ValueError: + return split_addr, None + return addr, scope_id + @property def max_prefixlen(self): return self._max_prefixlen @@ -1798,7 +1822,7 @@ class IPv6Address(_BaseV6, _BaseAddress): """Represent and manipulate single IPv6 Addresses.""" - __slots__ = ('_ip', '__weakref__') + __slots__ = ('_ip', 'scope_id', '__weakref__') def __init__(self, address): """Instantiate a new IPv6 address object. @@ -1817,6 +1841,8 @@ def __init__(self, address): AddressValueError: If address isn't a valid IPv6 address. """ + self.scope_id = None + # Efficient constructor from integer. if isinstance(address, int): self._check_int_address(address) @@ -1829,13 +1855,20 @@ def __init__(self, address): self._ip = int.from_bytes(address, 'big') return + # Assume input argument to be string or any object representation # which converts into a formatted IP string. addr_str = str(address) if '/' in addr_str: raise AddressValueError("Unexpected '/' in %r" % address) + addr_str, self.scope_id = self._split_scope_id(addr_str) + self._ip = self._ip_int_from_string(addr_str) + def __str__(self): + ip_str = self._string_from_ip_int(self._ip) + return ip_str if self.scope_id is None else ip_str + '%' + self.scope_id + @property def packed(self): """The binary representation of this address.""" @@ -1973,7 +2006,6 @@ def sixtofour(self): return None return IPv4Address((self._ip >> 80) & 0xFFFFFFFF) - class IPv6Interface(IPv6Address): def __init__(self, address): @@ -1989,7 +2021,7 @@ def hostmask(self): return self.network.hostmask def __str__(self): - return '%s/%d' % (self._string_from_ip_int(self._ip), + return '%s/%d' % (super().__str__(), self._prefixlen) def __eq__(self, other): From a841e9a9a5581eb174a3911073fe9f26916fc4a7 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Fri, 31 May 2019 16:06:18 +0300 Subject: [PATCH 03/56] bpo-34788 Add documentation for scope id support --- Doc/library/ipaddress.rst | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index b7b502aff15e0b..5ec0f9e1c55afa 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -219,9 +219,14 @@ write code that handles both IP versions correctly. Address objects are ``"::abc:7:def"``. 2. An integer that fits into 128 bits. 3. An integer packed into a :class:`bytes` object of length 16, big-endian. + 4. String IPv6 address may contain scope (or zone) id in format
%. + Scope id could not be blank or negative integer. + See :RFC: `4007` for details. >>> ipaddress.IPv6Address('2001:db8::1000') IPv6Address('2001:db8::1000') + >>> ipaddress.IPv6Address('2001:db8::1000%1') + IPv6Address('2001:db8::1000%1') .. attribute:: compressed @@ -268,6 +273,12 @@ write code that handles both IP versions correctly. Address objects are ``::FFFF/96``), this property will report the embedded IPv4 address. For any other address, this property will be ``None``. + .. attribute:: scope_id + + For addresses that appear to be scoped addresses (containing + ``%``) as defined by :RFC:`4007`, this attribute will consist + scope id. For any other address, this property will be ``None``. + .. attribute:: sixtofour For addresses that appear to be 6to4 addresses (starting with @@ -311,8 +322,8 @@ IPv6). Comparison operators """""""""""""""""""" -Address objects can be compared with the usual set of comparison operators. Some -examples:: +Address objects can be compared with the usual set of comparison operators. Scope id +is not considered while comparing IPv6 address objects. Some examples:: >>> IPv4Address('127.0.0.2') > IPv4Address('127.0.0.1') True @@ -320,6 +331,8 @@ examples:: False >>> IPv4Address('127.0.0.2') != IPv4Address('127.0.0.1') True + >>> IPv6Address('fe80::1234') == IPv6Address('fe80::1234%1') + True Arithmetic operators From 9a3f7ecc5b4d1ccd8b4edf1d1fc87f47938dc462 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Fri, 31 May 2019 16:55:48 +0300 Subject: [PATCH 04/56] bpo-34788 Refactor address split and validation --- Lib/ipaddress.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index b398e2a9148069..33ba629d5d707a 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1795,18 +1795,16 @@ def _split_scope_id(ip_str): ip_str: A string, the IPv6 address. Returns: - [addr, scope_id] list. + [addr, scope_id] tuple. """ if '%' not in ip_str: return ip_str, None - split_addr = ip_str.split('%') - if len(split_addr) > 2 or split_addr[-1] == '': + addr, sep, scope_id = ip_str.partition('%') + if not sep: + scope_id = None + elif not scope_id: raise AddressValueError('Invalid IPv6 address: "%r"' % ip_str) - try: - addr, scope_id = split_addr - except ValueError: - return split_addr, None return addr, scope_id @property @@ -1855,7 +1853,6 @@ def __init__(self, address): self._ip = int.from_bytes(address, 'big') return - # Assume input argument to be string or any object representation # which converts into a formatted IP string. addr_str = str(address) From a17538af7ed42c7f586e382b7684f3af746fd988 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Mon, 3 Jun 2019 11:31:58 +0300 Subject: [PATCH 05/56] bpo-34788 Convert scope_id to property --- Lib/ipaddress.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 33ba629d5d707a..b5abe93262bd23 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1790,12 +1790,11 @@ def _split_scope_id(ip_str): """Helper function to parse IPv6 string address with scope id. See RFC 4007 for details. - Arg: ip_str: A string, the IPv6 address. Returns: - [addr, scope_id] tuple. + (addr, scope_id) tuple. """ if '%' not in ip_str: return ip_str, None @@ -1820,7 +1819,7 @@ class IPv6Address(_BaseV6, _BaseAddress): """Represent and manipulate single IPv6 Addresses.""" - __slots__ = ('_ip', 'scope_id', '__weakref__') + __slots__ = ('_ip', '_scope_id', '__weakref__') def __init__(self, address): """Instantiate a new IPv6 address object. @@ -1839,7 +1838,7 @@ def __init__(self, address): AddressValueError: If address isn't a valid IPv6 address. """ - self.scope_id = None + self._scope_id = None # Efficient constructor from integer. if isinstance(address, int): @@ -1858,14 +1857,23 @@ def __init__(self, address): addr_str = str(address) if '/' in addr_str: raise AddressValueError("Unexpected '/' in %r" % address) - addr_str, self.scope_id = self._split_scope_id(addr_str) + addr_str, self._scope_id = self._split_scope_id(addr_str) self._ip = self._ip_int_from_string(addr_str) def __str__(self): - ip_str = self._string_from_ip_int(self._ip) + ip_str = super().__str__() return ip_str if self.scope_id is None else ip_str + '%' + self.scope_id + @property + def scope_id(self): + """Identifier of a particular zone of the address's scope. + + Returns: + A string, identifying the zone of the address if specified, else None. + """ + return self._scope_id + @property def packed(self): """The binary representation of this address.""" From 6da269eaca6318d8762593cb77d6d83e9a691c31 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Mon, 3 Jun 2019 11:33:55 +0300 Subject: [PATCH 06/56] bpo-34788 Update documentation --- Doc/library/ipaddress.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 5ec0f9e1c55afa..7ef32a193aea13 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -221,7 +221,7 @@ write code that handles both IP versions correctly. Address objects are 3. An integer packed into a :class:`bytes` object of length 16, big-endian. 4. String IPv6 address may contain scope (or zone) id in format
%. Scope id could not be blank or negative integer. - See :RFC: `4007` for details. + See :RFC: `4007` for details. >>> ipaddress.IPv6Address('2001:db8::1000') IPv6Address('2001:db8::1000') @@ -276,7 +276,7 @@ write code that handles both IP versions correctly. Address objects are .. attribute:: scope_id For addresses that appear to be scoped addresses (containing - ``%``) as defined by :RFC:`4007`, this attribute will consist + ``%``) as defined by :RFC:`4007`, this property will consist scope id. For any other address, this property will be ``None``. .. attribute:: sixtofour From 35750de110329238d82fc3eae5943d8c109b8a49 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Mon, 3 Jun 2019 11:47:34 +0300 Subject: [PATCH 07/56] bpo-34788 Remove redundant check --- Lib/ipaddress.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index b5abe93262bd23..a0ac57da03d3ce 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1796,9 +1796,6 @@ def _split_scope_id(ip_str): Returns: (addr, scope_id) tuple. """ - if '%' not in ip_str: - return ip_str, None - addr, sep, scope_id = ip_str.partition('%') if not sep: scope_id = None From 681cfa5d15a591ca73d2c1d00ec0b5328d102e7d Mon Sep 17 00:00:00 2001 From: opavliuk Date: Mon, 3 Jun 2019 11:49:15 +0300 Subject: [PATCH 08/56] bpo-34788 Fix newline --- Lib/ipaddress.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index a0ac57da03d3ce..1425703ee57817 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -2008,6 +2008,7 @@ def sixtofour(self): return None return IPv4Address((self._ip >> 80) & 0xFFFFFFFF) + class IPv6Interface(IPv6Address): def __init__(self, address): From fb99b6b939c0a971a05228bbd951851660ed5779 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Thu, 6 Jun 2019 17:52:34 +0300 Subject: [PATCH 09/56] bpo-34788 Documentation fix --- Doc/library/ipaddress.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 7ef32a193aea13..b7dd13838df84b 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -221,7 +221,7 @@ write code that handles both IP versions correctly. Address objects are 3. An integer packed into a :class:`bytes` object of length 16, big-endian. 4. String IPv6 address may contain scope (or zone) id in format
%. Scope id could not be blank or negative integer. - See :RFC: `4007` for details. + See :RFC:`4007` for details. >>> ipaddress.IPv6Address('2001:db8::1000') IPv6Address('2001:db8::1000') From d8bb67413908c687197439e86d37bb50d9721e1b Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 17 Jul 2019 08:26:15 +0000 Subject: [PATCH 10/56] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst diff --git a/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst b/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst new file mode 100644 index 00000000000000..f5616475d94d3c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst @@ -0,0 +1 @@ +Add support for scoped IPv6 addresses to [ipaddress](https://github.com/python/cpython/blob/master/Lib/ipaddress.py). Patch by Oleksandr Pavliuk. \ No newline at end of file From ccd83ed922b61f9f5b81e9482410fbd6ed0f7bc2 Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Wed, 17 Jul 2019 11:32:36 +0300 Subject: [PATCH 11/56] bpo-34788 Update NEWS --- .../next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst b/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst index f5616475d94d3c..2d38c5453eb812 100644 --- a/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst +++ b/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst @@ -1 +1 @@ -Add support for scoped IPv6 addresses to [ipaddress](https://github.com/python/cpython/blob/master/Lib/ipaddress.py). Patch by Oleksandr Pavliuk. \ No newline at end of file +Add support for scoped IPv6 addresses to `ipaddress `_. Patch by Oleksandr Pavliuk. From dc540e90c1ed06de337b425ac780463b98a200b6 Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Wed, 17 Jul 2019 11:36:07 +0300 Subject: [PATCH 12/56] bpo-34788 Fix NEWS filename --- ...-34788.pwV1OK.rst => 2019-07-17-08-26-14.bpo-34788.pwV1OK.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/Library/{2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst => 2019-07-17-08-26-14.bpo-34788.pwV1OK.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst b/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-34788.pwV1OK.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-bpo-34788.pwV1OK.rst rename to Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-34788.pwV1OK.rst From c03d4a564aacd9711536c913af696d1b54ac1834 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Wed, 17 Jul 2019 12:01:29 +0300 Subject: [PATCH 13/56] bpo-34788 Add sample IPv6 to ignore list --- Doc/tools/susp-ignored.csv | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index 85263d47c8bba8..ee9acb818e0b89 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -144,6 +144,10 @@ library/ipaddress,,:db8,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,:db8,IPv6Address('2001:db8::1000') library/ipaddress,,::,IPv6Address('2001:db8::1000') +library/ipaddress,,:db8,>>> ipaddress.IPv6Address('2001:db8::1000%1') +library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000%1') +library/ipaddress,,:db8,IPv6Address('2001:db8::1000%1') +library/ipaddress,,::,IPv6Address('2001:db8::1000%1') library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,"""::abc:7:def""" From c12e53350bcea4116b34ca16c5dd8bcc14e25d65 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Wed, 17 Jul 2019 12:10:12 +0300 Subject: [PATCH 14/56] bpo-34788 Add sample IPv6 to ignore list --- Doc/tools/susp-ignored.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index ee9acb818e0b89..e82a09a558fabb 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -148,6 +148,7 @@ library/ipaddress,,:db8,>>> ipaddress.IPv6Address('2001:db8::1000%1') library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000%1') library/ipaddress,,:db8,IPv6Address('2001:db8::1000%1') library/ipaddress,,::,IPv6Address('2001:db8::1000%1') +library/ipaddress,,::,>>> IPv6Address('fe80::1234') == IPv6Address('fe80::1234%1') library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,"""::abc:7:def""" From 033f0313013775ba841a8d965e32037469ea727d Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Thu, 18 Jul 2019 10:26:21 +0300 Subject: [PATCH 15/56] bpo-34788 Update Misc/NEWS.d Co-Authored-By: Kyle Stanley --- .../next/Library/2019-07-17-08-26-14.bpo-34788.pwV1OK.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-34788.pwV1OK.rst b/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-34788.pwV1OK.rst index 2d38c5453eb812..db6f6d54d9e8d8 100644 --- a/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-34788.pwV1OK.rst +++ b/Misc/NEWS.d/next/Library/2019-07-17-08-26-14.bpo-34788.pwV1OK.rst @@ -1 +1,2 @@ -Add support for scoped IPv6 addresses to `ipaddress `_. Patch by Oleksandr Pavliuk. +Add support for scoped IPv6 addresses to :mod:`ipaddress`. Patch by Oleksandr +Pavliuk. From 27c5cf4ad4e9366d32d5daa008d89c2f4bcb322d Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Thu, 18 Jul 2019 10:42:48 +0300 Subject: [PATCH 16/56] bpo-34788 Refactor condition Co-Authored-By: Kyle Stanley --- Lib/ipaddress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 1425703ee57817..c5009457bcd9f6 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1860,7 +1860,7 @@ def __init__(self, address): def __str__(self): ip_str = super().__str__() - return ip_str if self.scope_id is None else ip_str + '%' + self.scope_id + return ip_str + '%' + self.scope_id if self.scope_id else ip_str @property def scope_id(self): From 3b495c02ca7b96c05a67841999add5ebb079f93b Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Thu, 18 Jul 2019 11:05:26 +0300 Subject: [PATCH 17/56] bpo-34788 Fix excessive property call --- Lib/ipaddress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index c5009457bcd9f6..d9242f97d4cb0c 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1860,7 +1860,7 @@ def __init__(self, address): def __str__(self): ip_str = super().__str__() - return ip_str + '%' + self.scope_id if self.scope_id else ip_str + return ip_str + '%' + self._scope_id if self._scope_id else ip_str @property def scope_id(self): From b0a4fe901b2300b0be57619bf30d60e904e43fec Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Mon, 12 Aug 2019 14:23:35 +0300 Subject: [PATCH 18/56] bpo-34788 Mention RFC 4007 in the docstring --- Lib/ipaddress.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index d9242f97d4cb0c..14b4eaf7a01f80 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1790,6 +1790,7 @@ def _split_scope_id(ip_str): """Helper function to parse IPv6 string address with scope id. See RFC 4007 for details. + Arg: ip_str: A string, the IPv6 address. @@ -1865,7 +1866,9 @@ def __str__(self): @property def scope_id(self): """Identifier of a particular zone of the address's scope. - + + See RFC 4007 for details. + Returns: A string, identifying the zone of the address if specified, else None. """ From ac19fadfbcf73e314816c15c05f110cceede5b73 Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Mon, 12 Aug 2019 14:27:44 +0300 Subject: [PATCH 19/56] bpo-34788 Remove unnecessary comma --- Lib/ipaddress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 14b4eaf7a01f80..6b5d4565bbc37c 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1870,7 +1870,7 @@ def scope_id(self): See RFC 4007 for details. Returns: - A string, identifying the zone of the address if specified, else None. + A string identifying the zone of the address if specified, else None. """ return self._scope_id From 25d3ac7bbabab78cacab6e0c9cd99fce5d2e4641 Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Mon, 12 Aug 2019 14:43:18 +0300 Subject: [PATCH 20/56] bpo-34788 Add scope_id check --- Lib/ipaddress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 6b5d4565bbc37c..cc1770255bbbc2 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1800,7 +1800,7 @@ def _split_scope_id(ip_str): addr, sep, scope_id = ip_str.partition('%') if not sep: scope_id = None - elif not scope_id: + elif not scope_id or '%' in scope_id: raise AddressValueError('Invalid IPv6 address: "%r"' % ip_str) return addr, scope_id From 66db707bb02f58c0f1c79e4d891d1e714a3e0d7e Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Mon, 12 Aug 2019 14:46:53 +0300 Subject: [PATCH 21/56] bpo-34788 Add tests for invalid scope id --- Lib/test/test_ipaddress.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 17f8515c24917a..752854c9cb0fc5 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -179,6 +179,10 @@ def test_blank_scope_id(self): with self.assertAddressError('Invalid IPv6 address: "%r"', address): self.factory(address) + def test_invalid_scope_id_with_percent(self): + address = ('::1%scope%') + with self.assertAddressError('Invalid IPv6 address: "%r"', address): + self.factory(address) class AddressTestCase_v4(BaseTestCase, CommonTestMixin_v4): factory = ipaddress.IPv4Address From 064e01974821bedd436d84344e3b26357f5e58a9 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Wed, 14 Aug 2019 14:27:21 +0300 Subject: [PATCH 22/56] bpo-34788 Improve scoped IPv6 samples --- Doc/library/ipaddress.rst | 4 ++-- Doc/tools/susp-ignored.csv | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index b7dd13838df84b..22e391c02b7c1e 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -225,8 +225,8 @@ write code that handles both IP versions correctly. Address objects are >>> ipaddress.IPv6Address('2001:db8::1000') IPv6Address('2001:db8::1000') - >>> ipaddress.IPv6Address('2001:db8::1000%1') - IPv6Address('2001:db8::1000%1') + >>> ipaddress.IPv6Address('ff02::5678%1') + IPv6Address('ff02::5678%1') .. attribute:: compressed diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index c9dcd06640d700..49e9e7d3f11282 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -146,10 +146,10 @@ library/ipaddress,,:db8,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,:db8,IPv6Address('2001:db8::1000') library/ipaddress,,::,IPv6Address('2001:db8::1000') -library/ipaddress,,:db8,>>> ipaddress.IPv6Address('2001:db8::1000%1') -library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000%1') -library/ipaddress,,:db8,IPv6Address('2001:db8::1000%1') -library/ipaddress,,::,IPv6Address('2001:db8::1000%1') +library/ipaddress,,:db8,>>> ipaddress.IPv6Address('ff02::5678%1') +library/ipaddress,,::,>>> ipaddress.IPv6Address('ff02::5678%1') +library/ipaddress,,:db8,IPv6Address('ff02::5678%1') +library/ipaddress,,::,IPv6Address('ff02::5678%1') library/ipaddress,,::,>>> IPv6Address('fe80::1234') == IPv6Address('fe80::1234%1') library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" From ef91d9cd19041a48a3214443ca7cfb8a19543bc7 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Wed, 14 Aug 2019 14:28:11 +0300 Subject: [PATCH 23/56] bpo-34788 Remove trailing whitespaces --- Lib/ipaddress.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index cc1770255bbbc2..748b76a554ebbc 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1790,7 +1790,7 @@ def _split_scope_id(ip_str): """Helper function to parse IPv6 string address with scope id. See RFC 4007 for details. - + Arg: ip_str: A string, the IPv6 address. @@ -1866,9 +1866,9 @@ def __str__(self): @property def scope_id(self): """Identifier of a particular zone of the address's scope. - + See RFC 4007 for details. - + Returns: A string identifying the zone of the address if specified, else None. """ From d0eb91b995c39dc90d9be737913d5e7ce72756c7 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Wed, 14 Aug 2019 16:09:13 +0300 Subject: [PATCH 24/56] bpo-34788 Improve scope_id documentation --- Doc/library/ipaddress.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 22e391c02b7c1e..4517bede26089b 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -275,9 +275,9 @@ write code that handles both IP versions correctly. Address objects are .. attribute:: scope_id - For addresses that appear to be scoped addresses (containing - ``%``) as defined by :RFC:`4007`, this property will consist - scope id. For any other address, this property will be ``None``. + For scoped addresses as defined by :RFC:`4007`, this property identifies + the particular zone of the address's scope that the address belongs to, + as a string. When no scope zone is specified, this property will be ``None``. .. attribute:: sixtofour From 0735d3fda0ad4ac9c25d53e3a75ac0a7dc7746e6 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Wed, 14 Aug 2019 16:14:07 +0300 Subject: [PATCH 25/56] bpo-34788 Fix susp-ignored --- Doc/tools/susp-ignored.csv | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index 49e9e7d3f11282..646a7ea92d3c64 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -146,9 +146,7 @@ library/ipaddress,,:db8,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,:db8,IPv6Address('2001:db8::1000') library/ipaddress,,::,IPv6Address('2001:db8::1000') -library/ipaddress,,:db8,>>> ipaddress.IPv6Address('ff02::5678%1') library/ipaddress,,::,>>> ipaddress.IPv6Address('ff02::5678%1') -library/ipaddress,,:db8,IPv6Address('ff02::5678%1') library/ipaddress,,::,IPv6Address('ff02::5678%1') library/ipaddress,,::,>>> IPv6Address('fe80::1234') == IPv6Address('fe80::1234%1') library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" From fb47e34b97e2cac01cfdd2a84c614609f4b1cca2 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Wed, 14 Aug 2019 16:35:16 +0300 Subject: [PATCH 26/56] bpo-34788 Improve IPv6Address documentation --- Doc/library/ipaddress.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 4517bede26089b..f858e3db8bdb1f 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -217,11 +217,15 @@ write code that handles both IP versions correctly. Address objects are :RFC:`4291` for details. For example, ``"0000:0000:0000:0000:0000:0abc:0007:0def"`` can be compressed to ``"::abc:7:def"``. + + Optionally, the string may also have a scope (zone) id, expressed + with a suffix ``%``. If present, the scope id must be non-empty, + and may not contain ``%``. + + See :RFC:`4007` for details. 2. An integer that fits into 128 bits. 3. An integer packed into a :class:`bytes` object of length 16, big-endian. - 4. String IPv6 address may contain scope (or zone) id in format
%. - Scope id could not be blank or negative integer. - See :RFC:`4007` for details. + >>> ipaddress.IPv6Address('2001:db8::1000') IPv6Address('2001:db8::1000') From b8dffe5d2bc938d15b0985cd7b69093cd7083ed8 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Mon, 16 Sep 2019 16:37:55 +0300 Subject: [PATCH 27/56] bpo-34788 Move self._scope_id initialization under if statements --- Lib/ipaddress.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 748b76a554ebbc..309291aaa0ee12 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1836,18 +1836,18 @@ def __init__(self, address): AddressValueError: If address isn't a valid IPv6 address. """ - self._scope_id = None - # Efficient constructor from integer. if isinstance(address, int): self._check_int_address(address) self._ip = address + self._scope_id = None return # Constructing from a packed address if isinstance(address, bytes): self._check_packed_address(address, 16) self._ip = int.from_bytes(address, 'big') + self._scope_id = None return # Assume input argument to be string or any object representation From fc2425fc4dcfa15c812f79909312b30e7e2ecff1 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Mon, 16 Sep 2019 16:41:16 +0300 Subject: [PATCH 28/56] bpo-34788 Remove newline --- Doc/library/ipaddress.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index f858e3db8bdb1f..b0d9ca8ab3a036 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -221,7 +221,6 @@ write code that handles both IP versions correctly. Address objects are Optionally, the string may also have a scope (zone) id, expressed with a suffix ``%``. If present, the scope id must be non-empty, and may not contain ``%``. - See :RFC:`4007` for details. 2. An integer that fits into 128 bits. 3. An integer packed into a :class:`bytes` object of length 16, big-endian. From f9d38c06ebc77c2261a5a338d04ee8c96e64da1f Mon Sep 17 00:00:00 2001 From: opavliuk Date: Mon, 16 Sep 2019 16:46:36 +0300 Subject: [PATCH 29/56] bpo-34788 Improve documentation --- Doc/library/ipaddress.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index b0d9ca8ab3a036..71da352fb1cb56 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -218,10 +218,11 @@ write code that handles both IP versions correctly. Address objects are ``"0000:0000:0000:0000:0000:0abc:0007:0def"`` can be compressed to ``"::abc:7:def"``. - Optionally, the string may also have a scope (zone) id, expressed - with a suffix ``%``. If present, the scope id must be non-empty, + Optionally, the string may also have a scope zone ID, expressed + with a suffix ``%``. If present, the scope ID must be non-empty, and may not contain ``%``. See :RFC:`4007` for details. + For example, ``fe80::1234%1`` might identify address ``fe80::1234`` on the first link of the node. 2. An integer that fits into 128 bits. 3. An integer packed into a :class:`bytes` object of length 16, big-endian. From d8972af06c60dba7398606cd02ee4cd16e1009a9 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Tue, 26 Nov 2019 16:53:34 +0200 Subject: [PATCH 30/56] bpo-34788 Consider scope id while comparing IPv6 addresses --- Lib/ipaddress.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 309291aaa0ee12..a92c6e6c5d751f 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1863,6 +1863,15 @@ def __str__(self): ip_str = super().__str__() return ip_str + '%' + self._scope_id if self._scope_id else ip_str + def __hash__(self): + return super().__hash__() ^ hash(self._scope_id) + + def __eq__(self, other): + address_equal = _BaseAddress.__eq__(self, other) + if not address_equal or address_equal is NotImplemented: + return address_equal + return self._scope_id == other._scope_id + @property def scope_id(self): """Identifier of a particular zone of the address's scope. From 0b37c30d23aaf7abe465bd8363b35b1986856dc5 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Tue, 26 Nov 2019 17:22:31 +0200 Subject: [PATCH 31/56] bpo-34788 Update susp-ignored.csv --- Doc/tools/susp-ignored.csv | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index 646a7ea92d3c64..33f102a0d361a2 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -146,9 +146,14 @@ library/ipaddress,,:db8,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,:db8,IPv6Address('2001:db8::1000') library/ipaddress,,::,IPv6Address('2001:db8::1000') +library/ipaddress,,:db8,>>> ipaddress.IPv6Address('ff02::5678%1') library/ipaddress,,::,>>> ipaddress.IPv6Address('ff02::5678%1') +library/ipaddress,,:db8,ipaddress.IPv6Address('ff02::5678%1') library/ipaddress,,::,IPv6Address('ff02::5678%1') -library/ipaddress,,::,>>> IPv6Address('fe80::1234') == IPv6Address('fe80::1234%1') +library/ipaddress,,:db8,>>> IPv6Address('fe80::1234') != IPv6Address('fe80::1234%1') +library/ipaddress,,::,>>> IPv6Address('fe80::1234') != IPv6Address('fe80::1234%1') +library/ipaddress,,:db8,>>> IPv6Address('fe80::1234%1') != IPv6Address('fe80::1234%2') +library/ipaddress,,::,>>> IPv6Address('fe80::1234%1') != IPv6Address('fe80::1234%2') library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,"""::abc:7:def""" From 41ce543348a1edfdaf526e21ebf97aa4859f3f48 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Tue, 26 Nov 2019 17:46:35 +0200 Subject: [PATCH 32/56] bpo-34788 Update documentation --- Doc/library/ipaddress.rst | 7 +++++-- Doc/tools/susp-ignored.csv | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 71da352fb1cb56..aa6b34e5889b13 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -326,8 +326,9 @@ IPv6). Comparison operators """""""""""""""""""" -Address objects can be compared with the usual set of comparison operators. Scope id -is not considered while comparing IPv6 address objects. Some examples:: +Address objects can be compared with the usual set of comparison operators. +Same IPv6 addresses with different scope zone IDs are not equal. +Some examples:: >>> IPv4Address('127.0.0.2') > IPv4Address('127.0.0.1') True @@ -336,6 +337,8 @@ is not considered while comparing IPv6 address objects. Some examples:: >>> IPv4Address('127.0.0.2') != IPv4Address('127.0.0.1') True >>> IPv6Address('fe80::1234') == IPv6Address('fe80::1234%1') + False + >>> IPv6Address('fe80::1234%1') != IPv6Address('fe80::1234%2') True diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index 33f102a0d361a2..74b75e197fdde2 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -154,6 +154,8 @@ library/ipaddress,,:db8,>>> IPv6Address('fe80::1234') != IPv6Address('fe80::1234 library/ipaddress,,::,>>> IPv6Address('fe80::1234') != IPv6Address('fe80::1234%1') library/ipaddress,,:db8,>>> IPv6Address('fe80::1234%1') != IPv6Address('fe80::1234%2') library/ipaddress,,::,>>> IPv6Address('fe80::1234%1') != IPv6Address('fe80::1234%2') +library/ipaddress,,:db8,For example, ``fe80::1234%1`` might identify address ``fe80::1234`` on the first link of the node. +library/ipaddress,,::,For example, ``fe80::1234%1`` might identify address ``fe80::1234`` on the first link of the node. library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,"""::abc:7:def""" From 7f3fae8818a46945c47a0903da5f66ff9b35ac29 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Tue, 26 Nov 2019 17:52:44 +0200 Subject: [PATCH 33/56] bpo-34788 Update susp-ignored --- Doc/tools/susp-ignored.csv | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index 74b75e197fdde2..3b61084458374d 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -150,12 +150,12 @@ library/ipaddress,,:db8,>>> ipaddress.IPv6Address('ff02::5678%1') library/ipaddress,,::,>>> ipaddress.IPv6Address('ff02::5678%1') library/ipaddress,,:db8,ipaddress.IPv6Address('ff02::5678%1') library/ipaddress,,::,IPv6Address('ff02::5678%1') -library/ipaddress,,:db8,>>> IPv6Address('fe80::1234') != IPv6Address('fe80::1234%1') -library/ipaddress,,::,>>> IPv6Address('fe80::1234') != IPv6Address('fe80::1234%1') -library/ipaddress,,:db8,>>> IPv6Address('fe80::1234%1') != IPv6Address('fe80::1234%2') -library/ipaddress,,::,>>> IPv6Address('fe80::1234%1') != IPv6Address('fe80::1234%2') -library/ipaddress,,:db8,For example, ``fe80::1234%1`` might identify address ``fe80::1234`` on the first link of the node. -library/ipaddress,,::,For example, ``fe80::1234%1`` might identify address ``fe80::1234`` on the first link of the node. +library/ipaddress,,:db8,fe80::1234 +library/ipaddress,,::,fe80::1234 +library/ipaddress,,:db8,'fe80::1234%1 +library/ipaddress,,::fe80::1234%1 +library/ipaddress,,:db8,'fe80::1234%2 +library/ipaddress,,::fe80::1234%2 library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,"""::abc:7:def""" From 6c5a8455297525561745c904a8907187466ce62f Mon Sep 17 00:00:00 2001 From: opavliuk Date: Tue, 26 Nov 2019 17:56:58 +0200 Subject: [PATCH 34/56] bpo-34788 Update susp-ignored --- Doc/tools/susp-ignored.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index 3b61084458374d..7c4834ef10e03b 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -152,9 +152,9 @@ library/ipaddress,,:db8,ipaddress.IPv6Address('ff02::5678%1') library/ipaddress,,::,IPv6Address('ff02::5678%1') library/ipaddress,,:db8,fe80::1234 library/ipaddress,,::,fe80::1234 -library/ipaddress,,:db8,'fe80::1234%1 +library/ipaddress,,:db8,fe80::1234%1 library/ipaddress,,::fe80::1234%1 -library/ipaddress,,:db8,'fe80::1234%2 +library/ipaddress,,:db8,fe80::1234%2 library/ipaddress,,::fe80::1234%2 library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" From e569cd50b37dc17bdf03dd966281bfe339cbc79b Mon Sep 17 00:00:00 2001 From: opavliuk Date: Tue, 26 Nov 2019 18:01:14 +0200 Subject: [PATCH 35/56] bpo-34788 Update susp-ignored --- Doc/tools/susp-ignored.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index 7c4834ef10e03b..c8ef26776a4ccf 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -153,9 +153,9 @@ library/ipaddress,,::,IPv6Address('ff02::5678%1') library/ipaddress,,:db8,fe80::1234 library/ipaddress,,::,fe80::1234 library/ipaddress,,:db8,fe80::1234%1 -library/ipaddress,,::fe80::1234%1 +library/ipaddress,,::,fe80::1234%1 library/ipaddress,,:db8,fe80::1234%2 -library/ipaddress,,::fe80::1234%2 +library/ipaddress,,::,fe80::1234%2 library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,"""::abc:7:def""" From 3982d787ae63a3475b2294c460918bde1cfb55ce Mon Sep 17 00:00:00 2001 From: opavliuk Date: Tue, 26 Nov 2019 18:08:35 +0200 Subject: [PATCH 36/56] bpo-34788 Update susp-ignored --- Doc/tools/susp-ignored.csv | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index c8ef26776a4ccf..6b2c77f4694b21 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -146,16 +146,10 @@ library/ipaddress,,:db8,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,:db8,IPv6Address('2001:db8::1000') library/ipaddress,,::,IPv6Address('2001:db8::1000') -library/ipaddress,,:db8,>>> ipaddress.IPv6Address('ff02::5678%1') -library/ipaddress,,::,>>> ipaddress.IPv6Address('ff02::5678%1') library/ipaddress,,:db8,ipaddress.IPv6Address('ff02::5678%1') library/ipaddress,,::,IPv6Address('ff02::5678%1') library/ipaddress,,:db8,fe80::1234 library/ipaddress,,::,fe80::1234 -library/ipaddress,,:db8,fe80::1234%1 -library/ipaddress,,::,fe80::1234%1 -library/ipaddress,,:db8,fe80::1234%2 -library/ipaddress,,::,fe80::1234%2 library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,"""::abc:7:def""" From 6b5198f1d5776259fb1eb43bf2e96acaa04a44af Mon Sep 17 00:00:00 2001 From: opavliuk Date: Tue, 26 Nov 2019 18:13:52 +0200 Subject: [PATCH 37/56] bpo-34788 Update susp-ignored --- Doc/tools/susp-ignored.csv | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index 6b2c77f4694b21..57726414100bb7 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -146,9 +146,7 @@ library/ipaddress,,:db8,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,:db8,IPv6Address('2001:db8::1000') library/ipaddress,,::,IPv6Address('2001:db8::1000') -library/ipaddress,,:db8,ipaddress.IPv6Address('ff02::5678%1') library/ipaddress,,::,IPv6Address('ff02::5678%1') -library/ipaddress,,:db8,fe80::1234 library/ipaddress,,::,fe80::1234 library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" From 97a7707c5ed8071f2486a1180ac3da0dc2909643 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Wed, 27 Nov 2019 15:01:58 +0200 Subject: [PATCH 38/56] bpo-34788 Update tests according to new comparison policy --- Lib/test/test_ipaddress.py | 104 +++++++------------------------------ 1 file changed, 18 insertions(+), 86 deletions(-) diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 752854c9cb0fc5..6a30f8f1f94787 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -170,10 +170,6 @@ def assertBadLength(length): assertBadLength(15) assertBadLength(17) - def test_valid_scope_id(self): - self.assertInstancesEqual('ff02::5678%scope', 'ff02::5678') - self.assertInstancesEqual('ff02::5678%1', 'ff02::5678') - def test_blank_scope_id(self): address = ('::1%') with self.assertAddressError('Invalid IPv6 address: "%r"', address): @@ -469,7 +465,6 @@ def assertBadPart(addr, part): def test_pickle(self): self.pickle_test('2001:db8::') - self.pickle_test('2001:db8::%scope') def test_weakref(self): weakref.ref(self.factory('2001:db8::')) @@ -807,7 +802,7 @@ class ComparisonTests(unittest.TestCase): v6_addresses = [v6addr, v6intf] v6_objects = v6_addresses + [v6net] v6_scoped_addresses = [v6addr_scoped, v6intf_scoped] - v6_scoped_objects = v6_addresses + [v6net_scoped] + v6_scoped_objects = v6_scoped_addresses + [v6net_scoped] objects = v4_objects + v6_objects objects_with_scoped = objects + v6_scoped_objects @@ -840,9 +835,9 @@ def test_mixed_type_equality(self): continue self.assertNotEqual(lhs, rhs) - def test_ipv6_equality_despite_scope(self): + def test_scoped_ipv6_equality(self): for lhs, rhs in zip(self.v6_objects, self.v6_scoped_objects): - self.assertEqual(lhs, rhs) + self.assertNotEqual(lhs, rhs) def test_v4_with_v6_scoped_equality(self): for lhs in self.v4_objects: @@ -1083,14 +1078,6 @@ def testIPv6Tuple(self): net) ip_scoped = ipaddress.IPv6Address('2001:db8::%scope') - net_scoped = ipaddress.IPv6Network('2001:db8::%scope/128') - self.assertEqual(ipaddress.IPv6Network(('2001:db8::%scope', '128')), - net_scoped) - self.assertEqual(ipaddress.IPv6Network( - (42540766411282592856903984951653826560, 128)), - net_scoped) - self.assertEqual(ipaddress.IPv6Network((ip_scoped, '128')), - net_scoped) # strict=True and host bits set ip = ipaddress.IPv6Address('2001:db8::1') @@ -1126,16 +1113,6 @@ def testIPv6Tuple(self): with self.assertRaises(ValueError): ipaddress.IPv6Network((ip_scoped, 96)) # strict=False and host bits set - net_scoped = ipaddress.IPv6Network('2001:db8::/96') - self.assertEqual(ipaddress.IPv6Network(('2001:db8::1%scope', 96), - strict=False), - net_scoped) - self.assertEqual(ipaddress.IPv6Network( - (42540766411282592856903984951653826561, 96), - strict=False), - net_scoped) - self.assertEqual(ipaddress.IPv6Network((ip_scoped, 96), strict=False), - net_scoped) # issue57 def testAddressIntMath(self): @@ -1147,10 +1124,10 @@ def testAddressIntMath(self): ipaddress.IPv6Address('::ffff')) self.assertEqual(ipaddress.IPv6Address('::ffff') - (2**16 - 2), ipaddress.IPv6Address('::1')) - self.assertEqual(ipaddress.IPv6Address('::1%scope') + (2**16 - 2), - ipaddress.IPv6Address('::ffff%scope')) - self.assertEqual(ipaddress.IPv6Address('::ffff%scope') - (2**16 - 2), - ipaddress.IPv6Address('::1%scope')) + self.assertNotEqual(ipaddress.IPv6Address('::1%scope') + (2**16 - 2), + ipaddress.IPv6Address('::ffff%scope')) + self.assertNotEqual(ipaddress.IPv6Address('::ffff%scope') - (2**16 - 2), + ipaddress.IPv6Address('::1%scope')) def testInvalidIntToBytes(self): self.assertRaises(ValueError, ipaddress.v4_int_to_packed, -1) @@ -1199,7 +1176,7 @@ def testIpFromInt(self): ipv6_scoped = ipaddress.ip_network('2001:658:22a:cafe:200:0:0:1%scope') self.assertEqual(ipv4, ipaddress.ip_network(int(ipv4.network_address))) self.assertEqual(ipv6, ipaddress.ip_network(int(ipv6.network_address))) - self.assertEqual(ipv6_scoped, ipaddress.ip_network(int(ipv6_scoped.network_address))) + self.assertNotEqual(ipv6_scoped, ipaddress.ip_network(int(ipv6_scoped.network_address))) v6_int = 42540616829182469433547762482097946625 self.assertEqual(self.ipv6_interface._ip, @@ -1387,26 +1364,10 @@ def testHosts(self): tpl_args = ('2001:658:22a:cafe::', 127) self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts())) self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts())) - self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), - list(ipaddress.ip_network(tpl_args).hosts())) - addrs = [ipaddress.IPv6Address('2001:658:22a:cafe::%scope'), - ipaddress.IPv6Address('2001:658:22a:cafe::1%scope')] - str_args = '2001:658:22a:cafe::%scope/127' - tpl_args = ('2001:658:22a:cafe::%scope', 127) - self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts())) - self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts())) self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), list(ipaddress.ip_network(tpl_args).hosts())) addrs = [ipaddress.IPv6Address('2001:658:22a:cafe::'), ipaddress.IPv6Address('2001:658:22a:cafe::1')] - str_args = '2001:658:22a:cafe::%scope/127' - tpl_args = ('2001:658:22a:cafe::%scope', 127) - self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts())) - self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts())) - self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), - list(ipaddress.ip_network(tpl_args).hosts())) - addrs = [ipaddress.IPv6Address('2001:658:22a:cafe::%scope'), - ipaddress.IPv6Address('2001:658:22a:cafe::1%scope')] str_args = '2001:658:22a:cafe::/127' tpl_args = ('2001:658:22a:cafe::', 127) self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts())) @@ -1618,7 +1579,7 @@ def testEqual(self): ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/64')) self.assertFalse(self.ipv6_scoped_interface == ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/63')) - self.assertTrue(self.ipv6_scoped_interface == + self.assertFalse(self.ipv6_scoped_interface == ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/64')) self.assertFalse(self.ipv6_scoped_interface == ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/63')) @@ -1667,7 +1628,7 @@ def testNotEqual(self): ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/64')) self.assertTrue(self.ipv6_scoped_interface != ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/63')) - self.assertFalse(self.ipv6_scoped_interface != + self.assertTrue(self.ipv6_scoped_interface != ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/64')) self.assertTrue(self.ipv6_scoped_interface != ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/63')) @@ -1881,14 +1842,6 @@ def testAddressComparison(self): ipaddress.ip_address('::1%scope')) self.assertTrue(ipaddress.ip_address('::1%scope') <= ipaddress.ip_address('::2%scope')) - self.assertTrue(ipaddress.ip_address('::1') <= - ipaddress.ip_address('::1%scope')) - self.assertTrue(ipaddress.ip_address('::1') <= - ipaddress.ip_address('::2%scope')) - self.assertTrue(ipaddress.ip_address('::1%scope') <= - ipaddress.ip_address('::1')) - self.assertTrue(ipaddress.ip_address('::1%scope') <= - ipaddress.ip_address('::2')) def testInterfaceComparison(self): self.assertTrue(ipaddress.ip_interface('1.1.1.1/24') == @@ -1937,7 +1890,7 @@ def testInterfaceComparison(self): ipaddress.ip_interface('::2%scope/48')) - self.assertTrue(ipaddress.ip_interface('::1%scope/64') == + self.assertFalse(ipaddress.ip_interface('::1%scope/64') == ipaddress.ip_interface('::1/64')) self.assertTrue(ipaddress.ip_interface('::1%scope/64') < ipaddress.ip_interface('::1/80')) @@ -1952,7 +1905,7 @@ def testInterfaceComparison(self): self.assertTrue(ipaddress.ip_interface('::1%scope/64') > ipaddress.ip_interface('::2/48')) - self.assertTrue(ipaddress.ip_interface('::1/64') == + self.assertFalse(ipaddress.ip_interface('::1/64') == ipaddress.ip_interface('::1%scope/64')) self.assertTrue(ipaddress.ip_interface('::1/64') < ipaddress.ip_interface('::1%scope/80')) @@ -2054,27 +2007,6 @@ def testNetworkComparison(self): self.assertFalse(ipaddress.ip_network('::2') <= ipaddress.ip_network('::1')) - self.assertTrue(ipaddress.ip_network('::1%scope') <= - ipaddress.ip_network('::1%scope')) - self.assertTrue(ipaddress.ip_network('::1%scope') <= - ipaddress.ip_network('::2%scope')) - self.assertFalse(ipaddress.ip_network('::2%scope') <= - ipaddress.ip_network('::1%scope')) - - self.assertTrue(ipaddress.ip_network('::1%scope') <= - ipaddress.ip_network('::1')) - self.assertTrue(ipaddress.ip_network('::1%scope') <= - ipaddress.ip_network('::2')) - self.assertFalse(ipaddress.ip_network('::2%scope') <= - ipaddress.ip_network('::1')) - - self.assertTrue(ipaddress.ip_network('::1') <= - ipaddress.ip_network('::1%scope')) - self.assertTrue(ipaddress.ip_network('::1') <= - ipaddress.ip_network('::2%scope')) - self.assertFalse(ipaddress.ip_network('::2') <= - ipaddress.ip_network('::1%scope')) - def testStrictNetworks(self): self.assertRaises(ValueError, ipaddress.ip_network, '192.168.1.1/24') self.assertRaises(ValueError, ipaddress.ip_network, '::1/120') @@ -2111,13 +2043,13 @@ def testIPv6AddressTooLarge(self): ipaddress.ip_address('::FFFF:c000:201%scope')) self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1%scope'), ipaddress.ip_address('FFFF::c000:201%scope')) - self.assertEqual(ipaddress.ip_address('::FFFF:192.0.2.1%scope'), - ipaddress.ip_address('::FFFF:c000:201')) - self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1%scope'), - ipaddress.ip_address('FFFF::c000:201')) - self.assertEqual(ipaddress.ip_address('::FFFF:192.0.2.1'), + self.assertNotEqual(ipaddress.ip_address('::FFFF:192.0.2.1%scope'), + ipaddress.ip_address('::FFFF:c000:201')) + self.assertNotEqual(ipaddress.ip_address('FFFF::192.0.2.1%scope'), + ipaddress.ip_address('FFFF::c000:201')) + self.assertNotEqual(ipaddress.ip_address('::FFFF:192.0.2.1'), ipaddress.ip_address('::FFFF:c000:201%scope')) - self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1'), + self.assertNotEqual(ipaddress.ip_address('FFFF::192.0.2.1'), ipaddress.ip_address('FFFF::c000:201%scope')) def testIPVersion(self): From 6339732b2fbe212a6f5922f55afdf7f7ded69776 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Wed, 27 Nov 2019 17:16:20 +0200 Subject: [PATCH 39/56] bpo-34788 Update documentation --- Doc/library/ipaddress.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index aa6b34e5889b13..fbcc8e421536fc 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -314,6 +314,8 @@ the :func:`str` and :func:`int` builtin functions:: >>> int(ipaddress.IPv6Address('::1')) 1 +Note that IPv6 scoped addresses are converted to integers without scope zone ID. + Operators ^^^^^^^^^ From eff04f69597812f86fb3408bea5043f2a8e6899f Mon Sep 17 00:00:00 2001 From: opavliuk Date: Wed, 27 Nov 2019 17:40:08 +0200 Subject: [PATCH 40/56] bpo-34788 Add 3.9 whatsnew record --- Doc/whatsnew/3.9.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index 61d9e745e87ccf..39de56ce6c7a6c 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -132,6 +132,22 @@ now raises :exc:`ImportError` instead of :exc:`ValueError` for invalid relative import attempts. (Contributed by Ngalim Siregar in :issue:`37444`.) +ipaddress +--------- + +:mod:`ipaddress` now supports IPv6 Scoped Addresses (IPv6 address with suffix ``%``). + +Scoped IPv6 could be parsed to :class:`ipaddress.IPv6Address` constructor: + + +>>> ipaddress.IPv6Address('ff02::5678%1') +IPv6Address('ff02::5678%1') + + +If present, scope zone ID is avalaible through :attr:`scope_id` attribute. + +(Contributed by Oleksand Pavliuk in :issue:`34788`.) + Optimizations ============= From c25a327db663f343db1a90d96e64b2c932f61761 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Thu, 28 Nov 2019 12:20:40 +0200 Subject: [PATCH 41/56] bpo-34788 Improve __hash__() --- Lib/ipaddress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index a92c6e6c5d751f..8c24fcef254d7b 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1864,7 +1864,7 @@ def __str__(self): return ip_str + '%' + self._scope_id if self._scope_id else ip_str def __hash__(self): - return super().__hash__() ^ hash(self._scope_id) + return hash((self._ip, self._scope_id)) def __eq__(self, other): address_equal = _BaseAddress.__eq__(self, other) From c6695bf27d1b34f4f1e147157553eb35d5f6a926 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Thu, 28 Nov 2019 12:22:20 +0200 Subject: [PATCH 42/56] bpo-34788 Update whatsnew --- Doc/whatsnew/3.9.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index 19855b0e11b9d6..c01390c8d48247 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -207,13 +207,7 @@ ipaddress :mod:`ipaddress` now supports IPv6 Scoped Addresses (IPv6 address with suffix ``%``). -Scoped IPv6 could be parsed to :class:`ipaddress.IPv6Address` constructor: - - ->>> ipaddress.IPv6Address('ff02::5678%1') -IPv6Address('ff02::5678%1') - - +Scoped IPv6 could be parsed to :class:`ipaddress.IPv6Address` constructor. If present, scope zone ID is avalaible through :attr:`scope_id` attribute. (Contributed by Oleksand Pavliuk in :issue:`34788`.) From d30ea41cc3cb21295f0b6f742f47de1ba46dd8e7 Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Thu, 28 Nov 2019 14:29:41 +0200 Subject: [PATCH 43/56] Update Doc/whatsnew/3.9.rst Co-Authored-By: Kyle Stanley --- Doc/whatsnew/3.9.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index c01390c8d48247..a36bb500302cc3 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -207,7 +207,7 @@ ipaddress :mod:`ipaddress` now supports IPv6 Scoped Addresses (IPv6 address with suffix ``%``). -Scoped IPv6 could be parsed to :class:`ipaddress.IPv6Address` constructor. +Scoped IPv6 addresses can be parsed using :class:`ipaddress.IPv6Address`. If present, scope zone ID is avalaible through :attr:`scope_id` attribute. (Contributed by Oleksand Pavliuk in :issue:`34788`.) From e971130e4a74c6332f5d5b6bb4746ed89a38723b Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Thu, 28 Nov 2019 14:30:00 +0200 Subject: [PATCH 44/56] Update Doc/whatsnew/3.9.rst Co-Authored-By: Kyle Stanley --- Doc/whatsnew/3.9.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index a36bb500302cc3..2b1a1694c8179a 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -208,7 +208,7 @@ ipaddress :mod:`ipaddress` now supports IPv6 Scoped Addresses (IPv6 address with suffix ``%``). Scoped IPv6 addresses can be parsed using :class:`ipaddress.IPv6Address`. -If present, scope zone ID is avalaible through :attr:`scope_id` attribute. +If present, scope zone ID is available through the :attr:`scope_id` attribute. (Contributed by Oleksand Pavliuk in :issue:`34788`.) From 22c310e79520c9316302e3d50e3ae6e0b7139b2c Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Fri, 29 Nov 2019 10:37:24 +0200 Subject: [PATCH 45/56] Update Doc/whatsnew/3.9.rst Co-Authored-By: Andrew Svetlov --- Doc/whatsnew/3.9.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index 2b1a1694c8179a..783d50b8942bde 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -208,7 +208,7 @@ ipaddress :mod:`ipaddress` now supports IPv6 Scoped Addresses (IPv6 address with suffix ``%``). Scoped IPv6 addresses can be parsed using :class:`ipaddress.IPv6Address`. -If present, scope zone ID is available through the :attr:`scope_id` attribute. +If present, scope zone ID is available through the :attr:`~ipaddress.IPv6Address.scope_id` attribute. (Contributed by Oleksand Pavliuk in :issue:`34788`.) From 5882d06f3e8d88c279764ad7a84ce1e8c573c84e Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Fri, 29 Nov 2019 11:02:52 +0200 Subject: [PATCH 46/56] bpo-34788 Split conditional statement Co-Authored-By: Andrew Svetlov --- Lib/ipaddress.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 1eb5f51d535cce..dfb68fb0e844fc 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1919,7 +1919,10 @@ def __hash__(self): def __eq__(self, other): address_equal = _BaseAddress.__eq__(self, other) - if not address_equal or address_equal is NotImplemented: + if address_equal is NotImplemented: + return NotImplemented + if not address_equal: + return False return address_equal return self._scope_id == other._scope_id From 2c6fd5451a1b838530f3a2aea54a31f08f2c3c9c Mon Sep 17 00:00:00 2001 From: opavliuk Date: Fri, 29 Nov 2019 11:15:43 +0200 Subject: [PATCH 47/56] bpo-34788 Fix eq --- Lib/ipaddress.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index dfb68fb0e844fc..dc31269e156283 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1842,7 +1842,7 @@ def _split_scope_id(ip_str): See RFC 4007 for details. - Arg: + Args: ip_str: A string, the IPv6 address. Returns: @@ -1918,12 +1918,11 @@ def __hash__(self): return hash((self._ip, self._scope_id)) def __eq__(self, other): - address_equal = _BaseAddress.__eq__(self, other) + address_equal = super().__eq__(self, other) if address_equal is NotImplemented: return NotImplemented if not address_equal: return False - return address_equal return self._scope_id == other._scope_id @property From 0ec1572aba1dd22cc77fba43b072979b7bb918fa Mon Sep 17 00:00:00 2001 From: opavliuk Date: Fri, 29 Nov 2019 11:22:50 +0200 Subject: [PATCH 48/56] bpo-34788 Unify docstring styles --- Lib/ipaddress.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index dc31269e156283..bebfda6a355731 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1847,6 +1847,7 @@ def _split_scope_id(ip_str): Returns: (addr, scope_id) tuple. + """ addr, sep, scope_id = ip_str.partition('%') if not sep: @@ -1933,6 +1934,7 @@ def scope_id(self): Returns: A string identifying the zone of the address if specified, else None. + """ return self._scope_id From 5530a799e27f1694dcf54ff8d284a2ce634dd1de Mon Sep 17 00:00:00 2001 From: opavliuk Date: Fri, 29 Nov 2019 11:53:19 +0200 Subject: [PATCH 49/56] bpo-34788 Remove unnecessary arg --- Lib/ipaddress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index bebfda6a355731..b8b8fa0fa8bc43 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1919,7 +1919,7 @@ def __hash__(self): return hash((self._ip, self._scope_id)) def __eq__(self, other): - address_equal = super().__eq__(self, other) + address_equal = super().__eq__(other) if address_equal is NotImplemented: return NotImplemented if not address_equal: From 2f7de77ef1bff368389200577754c3dd88b7f4b6 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Mon, 2 Dec 2019 14:20:25 +0200 Subject: [PATCH 50/56] bpo-34788 Improve comparison --- Lib/ipaddress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index b8b8fa0fa8bc43..511efc70eb4ff7 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1924,7 +1924,7 @@ def __eq__(self, other): return NotImplemented if not address_equal: return False - return self._scope_id == other._scope_id + return self._scope_id == getattr(other, '._scope_id', None) @property def scope_id(self): From ef42a2d2342c87ab75f0cf6a9f569c091a42491c Mon Sep 17 00:00:00 2001 From: opavliuk Date: Mon, 2 Dec 2019 14:55:10 +0200 Subject: [PATCH 51/56] bpo-34788 Fix typo --- Lib/ipaddress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 511efc70eb4ff7..9c47405ce8d8c6 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1924,7 +1924,7 @@ def __eq__(self, other): return NotImplemented if not address_equal: return False - return self._scope_id == getattr(other, '._scope_id', None) + return self._scope_id == getattr(other, '_scope_id', None) @property def scope_id(self): From 641bc6b61201f10e71f42e5ac5ab6719b0bd80a7 Mon Sep 17 00:00:00 2001 From: opavliuk Date: Tue, 3 Dec 2019 14:37:33 +0200 Subject: [PATCH 52/56] bpo-34788 Unify scope id naming --- Doc/library/ipaddress.rst | 2 +- Doc/library/socket.rst | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index d722c3ee8f6bb5..5938439941792d 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -219,7 +219,7 @@ write code that handles both IP versions correctly. Address objects are ``"::abc:7:def"``. Optionally, the string may also have a scope zone ID, expressed - with a suffix ``%``. If present, the scope ID must be non-empty, + with a suffix ``%scope_id``. If present, the scope ID must be non-empty, and may not contain ``%``. See :RFC:`4007` for details. For example, ``fe80::1234%1`` might identify address ``fe80::1234`` on the first link of the node. diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 2d7ca33f2927dc..3d4368625b1201 100755 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -78,15 +78,15 @@ created. Socket addresses are represented as follows: Python programs. - For :const:`AF_INET6` address family, a four-tuple ``(host, port, flowinfo, - scopeid)`` is used, where *flowinfo* and *scopeid* represent the ``sin6_flowinfo`` + scope_id)`` is used, where *flowinfo* and *scope_id* represent the ``sin6_flowinfo`` and ``sin6_scope_id`` members in :const:`struct sockaddr_in6` in C. For - :mod:`socket` module methods, *flowinfo* and *scopeid* can be omitted just for - backward compatibility. Note, however, omission of *scopeid* can cause problems + :mod:`socket` module methods, *flowinfo* and *scope_id* can be omitted just for + backward compatibility. Note, however, omission of *scope_id* can cause problems in manipulating scoped IPv6 addresses. .. versionchanged:: 3.7 - For multicast addresses (with *scopeid* meaningful) *address* may not contain - ``%scope`` (or ``zone id``) part. This information is superfluous and may + For multicast addresses (with *scope_id* meaningful) *address* may not contain + ``%scope_id`` (or ``zone id``) part. This information is superfluous and may be safely omitted (recommended). - :const:`AF_NETLINK` sockets are represented as pairs ``(pid, groups)``. @@ -757,7 +757,7 @@ The :mod:`socket` module also offers various network-related services: .. versionchanged:: 3.7 for IPv6 multicast addresses, string representing an address will not - contain ``%scope`` part. + contain ``%scope_id`` part. .. function:: getfqdn([name]) @@ -825,8 +825,8 @@ The :mod:`socket` module also offers various network-related services: or numeric address representation in *host*. Similarly, *port* can contain a string port name or a numeric port number. - For IPv6 addresses, ``%scope`` is appended to the host part if *sockaddr* - contains meaningful *scopeid*. Usually this happens for multicast addresses. + For IPv6 addresses, ``%scope_id`` is appended to the host part if *sockaddr* + contains meaningful *scope_id*. Usually this happens for multicast addresses. For more information about *flags* you can consult :manpage:`getnameinfo(3)`. @@ -1352,7 +1352,7 @@ to sockets. .. versionchanged:: 3.7 For multicast IPv6 address, first item of *address* does not contain - ``%scope`` part anymore. In order to get full IPv6 address use + ``%scope_id`` part anymore. In order to get full IPv6 address use :func:`getnameinfo`. .. method:: socket.recvmsg(bufsize[, ancbufsize[, flags]]) From 34728d219b4a3c744fa54554b56ceadfe1d052fc Mon Sep 17 00:00:00 2001 From: opavliuk Date: Tue, 3 Dec 2019 15:05:10 +0200 Subject: [PATCH 53/56] bpo-34788 Unify naming --- Doc/library/socket.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 3d4368625b1201..c199402911c123 100755 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -736,7 +736,7 @@ The :mod:`socket` module also offers various network-related services: :const:`AI_CANONNAME` is part of the *flags* argument; else *canonname* will be empty. *sockaddr* is a tuple describing a socket address, whose format depends on the returned *family* (a ``(address, port)`` 2-tuple for - :const:`AF_INET`, a ``(address, port, flow info, scope id)`` 4-tuple for + :const:`AF_INET`, a ``(address, port, flowinfo, scope_id)`` 4-tuple for :const:`AF_INET6`), and is meant to be passed to the :meth:`socket.connect` method. From af90771cf8287c651966cfab4027a814d5fe939c Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Wed, 11 Dec 2019 09:32:25 +0200 Subject: [PATCH 54/56] Update Doc/whatsnew/3.9.rst Co-Authored-By: Kyle Stanley --- Doc/whatsnew/3.9.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index 783d50b8942bde..1287ea0541c3b2 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -209,7 +209,6 @@ ipaddress Scoped IPv6 addresses can be parsed using :class:`ipaddress.IPv6Address`. If present, scope zone ID is available through the :attr:`~ipaddress.IPv6Address.scope_id` attribute. - (Contributed by Oleksand Pavliuk in :issue:`34788`.) signal From 2c4d3d548dcff9bb06c8d16cb505b6d7183e9aed Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Wed, 26 Feb 2020 16:04:24 +0200 Subject: [PATCH 55/56] Cleanup Whatsnew --- Doc/whatsnew/3.9.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index b0351a7909f4d4..ae974ae1e3de38 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -339,13 +339,6 @@ Optimizations Build and C API Changes ======================= -* Provide :c:func:`Py_EnterRecursiveCall` and :c:func:`Py_LeaveRecursiveCall` - as regular functions for the limited API. Previously, there were defined as - macros, but these macros didn't work with the limited API which cannot access - ``PyThreadState.recursion_depth`` field. Remove ``_Py_CheckRecursionLimit`` - from the stable ABI. - (Contributed by Victor Stinner in :issue:`38644`.) - * Add a new public :c:func:`PyObject_CallNoArgs` function to the C API, which calls a callable Python object without any arguments. It is the most efficient way to call a callable Python object without any argument. From 555f59f60acf12aba46e67afe687fe5bd11555e6 Mon Sep 17 00:00:00 2001 From: opavlyuk <40970635+opavlyuk@users.noreply.github.com> Date: Wed, 26 Feb 2020 16:14:15 +0200 Subject: [PATCH 56/56] Fix typo --- Doc/whatsnew/3.9.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index ae974ae1e3de38..0aa7e9a845dcf2 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -220,7 +220,7 @@ ipaddress Scoped IPv6 addresses can be parsed using :class:`ipaddress.IPv6Address`. If present, scope zone ID is available through the :attr:`~ipaddress.IPv6Address.scope_id` attribute. -(Contributed by Oleksand Pavliuk in :issue:`34788`.) +(Contributed by Oleksandr Pavliuk in :issue:`34788`.) math ----