Skip to content

Commit 1a18438

Browse files
committed
Use buffer protocol to support all byte-like objects
1 parent 17e9ab6 commit 1a18438

File tree

5 files changed

+32
-18
lines changed

5 files changed

+32
-18
lines changed

Doc/library/stdtypes.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2746,7 +2746,7 @@ data and are closely related to string objects in a variety of other ways.
27462746

27472747
.. versionchanged:: next
27482748
:meth:`bytes.fromhex` now accepts ASCII :class:`bytes` and
2749-
:class:`bytearray` objects as input.
2749+
:term:`bytes-like objects <bytes-like object>` as input.
27502750

27512751

27522752

@@ -2837,7 +2837,7 @@ objects.
28372837

28382838
.. versionchanged:: next
28392839
:meth:`bytearray.fromhex` now accepts ASCII :class:`bytes` and
2840-
:class:`bytearray` objects as input.
2840+
:term:`bytes-like objects <bytes-like object>` as input.
28412841

28422842
A reverse conversion function exists to transform a bytearray object into its
28432843
hexadecimal representation.

Doc/whatsnew/3.14.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ Other language changes
355355
(Contrubuted by Sergey B Kirpichev in :gh:`87790`.)
356356

357357
* The :func:`bytes.fromhex` and :func:`bytearray.fromhex` methods now accept
358-
ASCII :class:`bytes` and :class:`bytearray` objects.
358+
ASCII :class:`bytes` and :term:`bytes-like objects <bytes-like object>`.
359359
(Contributed by Daniel Pope in :gh:`129349`.)
360360

361361
* ``\B`` in :mod:`regular expression <re>` now matches empty input string.

Lib/test/test_bytes.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,16 @@ def test_fromhex(self):
457457
self.assertRaises(ValueError, self.type2test.fromhex, c)
458458

459459
# Check that we can parse bytes and bytearray
460-
self.assertEqual(self.type2test.fromhex(b' 012abc'), b'\x01\x2a\xbc')
461-
self.assertEqual(
462-
self.type2test.fromhex(bytearray(b' 012abc')),
463-
b'\x01\x2a\xbc',
464-
)
460+
tests = [
461+
("bytes", bytes),
462+
("bytearray", bytearray),
463+
("memoryview", memoryview),
464+
("array.array", lambda bs: array.array('B', bs)),
465+
]
466+
for name, factory in tests:
467+
with self.subTest(name=name):
468+
self.assertEqual(self.type2test.fromhex(factory(b' 1A 2B 30 ')), b)
469+
465470
# Invalid bytes are rejected
466471
for u8 in b"\0\x1C\x1D\x1E\x1F\x85\xa0":
467472
b = bytes([30, 31, u8])
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
:meth:`bytes.fromhex` and :meth:`bytearray.fromhex` now accepts ASCII
2-
:class:`bytes`/:class:`bytearray` objects.
2+
:class:`bytes` and :term:`bytes-like objects <bytes-like object>`.

Objects/bytesobject.c

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,6 +2512,8 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray)
25122512
unsigned int top, bot;
25132513
const Py_UCS1 *str, *start, *end;
25142514
_PyBytesWriter writer;
2515+
Py_buffer view;
2516+
view.obj = NULL;
25152517

25162518
_PyBytesWriter_Init(&writer);
25172519
writer.use_bytearray = use_bytearray;
@@ -2534,15 +2536,13 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray)
25342536
}
25352537

25362538
assert(PyUnicode_KIND(string) == PyUnicode_1BYTE_KIND);
2537-
str = start = PyUnicode_1BYTE_DATA(string);
2538-
}
2539-
else if (PyBytes_Check(string)) {
2540-
hexlen = PyBytes_GET_SIZE(string);
2541-
str = start = (Py_UCS1 *)PyBytes_AS_STRING(string);
2539+
str = PyUnicode_1BYTE_DATA(string);
25422540
}
2543-
else if (PyByteArray_Check(string)) {
2544-
hexlen = PyByteArray_GET_SIZE(string);
2545-
str = start = (Py_UCS1 *)PyByteArray_AS_STRING(string);
2541+
else if (PyObject_CheckBuffer(string)) {
2542+
if (PyObject_GetBuffer(string, &view, PyBUF_SIMPLE) != 0)
2543+
return NULL;
2544+
hexlen = view.len;
2545+
str = view.buf;
25462546
}
25472547
else {
25482548
PyErr_Format(PyExc_TypeError,
@@ -2554,8 +2554,9 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray)
25542554
/* This overestimates if there are spaces */
25552555
buf = _PyBytesWriter_Alloc(&writer, hexlen / 2);
25562556
if (buf == NULL)
2557-
return NULL;
2557+
goto release_buffer;
25582558

2559+
start = str;
25592560
end = str + hexlen;
25602561
while (str < end) {
25612562
/* skip over spaces in the input */
@@ -2589,6 +2590,9 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray)
25892590
*buf++ = (unsigned char)((top << 4) + bot);
25902591
}
25912592

2593+
if (view.obj != NULL) {
2594+
PyBuffer_Release(&view);
2595+
}
25922596
return _PyBytesWriter_Finish(&writer, buf);
25932597

25942598
error:
@@ -2601,6 +2605,11 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray)
26012605
"fromhex() arg at position %zd", invalid_char);
26022606
}
26032607
_PyBytesWriter_Dealloc(&writer);
2608+
2609+
release_buffer:
2610+
if (view.obj != NULL) {
2611+
PyBuffer_Release(&view);
2612+
}
26042613
return NULL;
26052614
}
26062615

0 commit comments

Comments
 (0)
close