From f47be44628da061c775861a8c77e6d4c1ecdb4aa Mon Sep 17 00:00:00 2001 From: Matan Perelman Date: Sun, 22 Jun 2025 09:48:28 +0300 Subject: [PATCH] ipaddress: Add bytearray support --- Doc/library/ipaddress.rst | 8 ++++---- Lib/ipaddress.py | 6 +++--- Lib/test/test_ipaddress.py | 12 ++++++++++++ .../2025-06-22-09-47-34.gh-issue-78646.Awon0R.rst | 1 + 4 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-06-22-09-47-34.gh-issue-78646.Awon0R.rst diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index e5bdfbb144b65a..c743d11c183a0e 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -106,7 +106,7 @@ write code that handles both IP versions correctly. Address objects are integer represents an octet (byte) in the address. Leading zeroes are not tolerated to prevent confusion with octal notation. 2. An integer that fits into 32 bits. - 3. An integer packed into a :class:`bytes` object of length 4 (most + 3. An integer packed into a :class:`bytes` or :class:`bytearray` object of length 4 (most significant octet first). >>> ipaddress.IPv4Address('192.168.0.1') @@ -310,7 +310,7 @@ write code that handles both IP versions correctly. Address objects are 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. + 3. An integer packed into a :class:`bytes` or :class:`bytearray` object of length 16, big-endian. >>> ipaddress.IPv6Address('2001:db8::1000') @@ -507,7 +507,7 @@ dictionaries. single-address network, with the network address being *address* and the mask being ``/32``. - 3. An integer packed into a :class:`bytes` object of length 4, big-endian. + 3. An integer packed into a :class:`bytes` or :class:`bytearray` object of length 4, big-endian. The interpretation is similar to an integer *address*. 4. A two-tuple of an address description and a netmask, where the address @@ -728,7 +728,7 @@ dictionaries. single-address network, with the network address being *address* and the mask being ``/128``. - 3. An integer packed into a :class:`bytes` object of length 16, big-endian. + 3. An integer packed into a :class:`bytes` or :class:`bytearray` object of length 16, big-endian. The interpretation is similar to an integer *address*. 4. A two-tuple of an address description and a netmask, where the address diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 8b60b9d5c9cd51..f324bd534ef412 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -536,7 +536,7 @@ def _split_addr_prefix(cls, address): (addr, prefix) tuple. """ # a packed address or integer - if isinstance(address, (bytes, int)): + if isinstance(address, (bytes, bytearray, int)): return address, cls.max_prefixlen if not isinstance(address, tuple): @@ -1292,7 +1292,7 @@ def __init__(self, address): return # Constructing from a packed address - if isinstance(address, bytes): + if isinstance(address, bytes) or isinstance(address, bytearray): self._check_packed_address(address, 4) self._ip = int.from_bytes(address) # big endian return @@ -1938,7 +1938,7 @@ def __init__(self, address): return # Constructing from a packed address - if isinstance(address, bytes): + if isinstance(address, bytes) or isinstance(address, bytearray): self._check_packed_address(address, 16) self._ip = int.from_bytes(address, 'big') self._scope_id = None diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index db1c38243e2268..76c7f109e48722 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -122,6 +122,10 @@ def test_packed(self): self.assertInstancesEqual(bytes.fromhex("00000000"), "0.0.0.0") self.assertInstancesEqual(bytes.fromhex("c0a80001"), "192.168.0.1") + def test_packed_bytearray(self): + self.assertInstancesEqual(bytearray.fromhex("00000000"), "0.0.0.0") + self.assertInstancesEqual(bytearray.fromhex("c0a80001"), "192.168.0.1") + def test_negative_ints_rejected(self): msg = "-1 (< 0) is not permitted as an IPv4 address" with self.assertAddressError(re.escape(msg)): @@ -161,6 +165,14 @@ def test_packed(self): addr = bytes.fromhex("c0a80001") + b'\0'*12 self.assertInstancesEqual(addr, "c0a8:1::") + def test_packed_bytearray(self): + addr = bytearray(b'\0'*12) + bytearray.fromhex("00000000") + self.assertInstancesEqual(addr, "::") + addr = bytearray(b'\0'*12) + bytearray.fromhex("c0a80001") + self.assertInstancesEqual(addr, "::c0a8:1") + addr = bytearray.fromhex("c0a80001") + bytearray(b'\0'*12) + self.assertInstancesEqual(addr, "c0a8:1::") + def test_negative_ints_rejected(self): msg = "-1 (< 0) is not permitted as an IPv6 address" with self.assertAddressError(re.escape(msg)): diff --git a/Misc/NEWS.d/next/Library/2025-06-22-09-47-34.gh-issue-78646.Awon0R.rst b/Misc/NEWS.d/next/Library/2025-06-22-09-47-34.gh-issue-78646.Awon0R.rst new file mode 100644 index 00000000000000..f2881e289f12ad --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-22-09-47-34.gh-issue-78646.Awon0R.rst @@ -0,0 +1 @@ +Add :class:`bytearray` support for ipaddress.