diff --git a/Doc/c-api/buffer.rst b/Doc/c-api/buffer.rst index 9500fe465c7d94..6285370e9ee493 100644 --- a/Doc/c-api/buffer.rst +++ b/Doc/c-api/buffer.rst @@ -517,6 +517,16 @@ Buffer-related functions ``0`` is returned on success, ``-1`` on error. +.. c:function:: int PyObject_CopyToObject(PyObject *obj, void *buf, Py_ssize_t len, char fort) + + Copy data from *buf* to *obj* buffer. Can convert between C-style and + or Fortran-style buffers (C-style if *fort* is ``'C'`` or Fortran-style + if *fort* is ``'F'``, ``'A'`` or other for defalut quick copy). + + ``0`` is returned on success, ``-1`` on error. + + .. versionadded:: 3.14 + .. c:function:: void PyBuffer_FillContiguousStrides(int ndims, Py_ssize_t *shape, Py_ssize_t *strides, int itemsize, char order) Fill the *strides* array with byte-strides of a :term:`contiguous` (C-style if diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 7eeee270bb7f32..4f34d45e91cc5b 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -518,6 +518,7 @@ func,PyObject_Calloc,3.7,, func,PyObject_CheckBuffer,3.11,, func,PyObject_ClearWeakRefs,3.2,, func,PyObject_CopyData,3.11,, +func,PyObject_CopyToObject,3.14,, func,PyObject_DelAttr,3.13,, func,PyObject_DelAttrString,3.13,, func,PyObject_DelItem,3.2,, diff --git a/Include/pybuffer.h b/Include/pybuffer.h index ca1c6058d9052c..e33958a3a9d03b 100644 --- a/Include/pybuffer.h +++ b/Include/pybuffer.h @@ -75,6 +75,8 @@ PyAPI_FUNC(int) PyBuffer_FromContiguous(const Py_buffer *view, const void *buf, is 'A', then it does not matter and the copy will be made in whatever way is more efficient. */ PyAPI_FUNC(int) PyObject_CopyData(PyObject *dest, PyObject *src); +PyAPI_FUNC(int) PyObject_CopyToObject(PyObject *obj, void *buf, + Py_ssize_t len, char fort); /* Copy the data from the src buffer to the buffer of destination. */ PyAPI_FUNC(int) PyBuffer_IsContiguous(const Py_buffer *view, char fort); diff --git a/Lib/test/test_capi/test_abstract.py b/Lib/test/test_capi/test_abstract.py index 6a626813f23379..c2014628b64115 100644 --- a/Lib/test/test_capi/test_abstract.py +++ b/Lib/test/test_capi/test_abstract.py @@ -339,6 +339,20 @@ def test_object_delattrstring(self): # CRASHES delattrstring(obj, NULL) # CRASHES delattrstring(NULL, b'a') + def test_copy_to_object(self): + copy_to_object = _testcapi.object_copy_to_object + s1 = copy_to_object(bytearray(3), 'abc', b'C') + s2 = copy_to_object(bytearray(3), 'abc', b'F') + s3 = copy_to_object(bytearray(3), 'abc', b'A') + self.assertEqual(s1, s2) + self.assertEqual(s2, s3) + self.assertEqual(s1, s3) + self.assertRaises(BufferError, copy_to_object, bytearray(2), 'abc', b'C') + self.assertRaises(BufferError, copy_to_object, bytearray(2), 'abc', b'F') + self.assertRaises(BufferError, copy_to_object, bytearray(2), 'abc', b'A') + self.assertRaises(TypeError, copy_to_object, list(), 'abc', b'C') + self.assertRaises(TypeError, copy_to_object, list(), 'abc', b'F') + self.assertRaises(TypeError, copy_to_object, list(), 'abc', b'A') def test_mapping_check(self): check = _testlimitedcapi.mapping_check diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 4bca33b7451f80..93ec46ca7fe5d9 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -546,6 +546,7 @@ def test_windows_feature_macros(self): "PyObject_CheckReadBuffer", "PyObject_ClearWeakRefs", "PyObject_CopyData", + "PyObject_CopyToObject", "PyObject_DelAttr", "PyObject_DelAttrString", "PyObject_DelItem", diff --git a/Misc/NEWS.d/next/C_API/2024-09-29-14-34-53.gh-issue-84016.f1ThMN.rst b/Misc/NEWS.d/next/C_API/2024-09-29-14-34-53.gh-issue-84016.f1ThMN.rst new file mode 100644 index 00000000000000..a64fd6851f204d --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2024-09-29-14-34-53.gh-issue-84016.f1ThMN.rst @@ -0,0 +1,2 @@ +Added :c:func:`PyObject_CopyToObject` to copy a C-style or Fortran-style +buffer to a Python object. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 8bf638c473c712..c991fc620fdc1f 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2283,6 +2283,8 @@ added = '3.11' [function.PyObject_CopyData] added = '3.11' +[function.PyObject_CopyToObject] + added = '3.14' [function.PyBuffer_IsContiguous] added = '3.11' [function.PyBuffer_FillContiguousStrides] diff --git a/Modules/_testcapi/abstract.c b/Modules/_testcapi/abstract.c index 8c2c7137cdce40..e8be80c3f5bd0c 100644 --- a/Modules/_testcapi/abstract.c +++ b/Modules/_testcapi/abstract.c @@ -78,6 +78,25 @@ object_hasattrstringwitherror(PyObject *self, PyObject *args) RETURN_INT(PyObject_HasAttrStringWithError(obj, attr_name)); } +static PyObject * +object_copy_to_object(PyObject *self, PyObject *args) +{ + PyObject *obj; + Py_ssize_t len; + int result; + char *buf, fort; + + if (!PyArg_ParseTuple(args, "Os#c", &obj, &buf, &len, &fort)) { + return NULL; + } + result = PyObject_CopyToObject(obj, buf, len, fort); + if (result < 0) { + return NULL; + } + Py_INCREF(obj); + return obj; +} + static PyObject * mapping_getoptionalitemstring(PyObject *self, PyObject *args) { @@ -162,6 +181,8 @@ static PyMethodDef test_methods[] = { {"object_getoptionalattrstring", object_getoptionalattrstring, METH_VARARGS}, {"object_hasattrwitherror", object_hasattrwitherror, METH_VARARGS}, {"object_hasattrstringwitherror", object_hasattrstringwitherror, METH_VARARGS}, + {"object_copy_to_object", object_copy_to_object, METH_VARARGS}, + {"mapping_getoptionalitem", mapping_getoptionalitem, METH_VARARGS}, {"mapping_getoptionalitemstring", mapping_getoptionalitemstring, METH_VARARGS}, diff --git a/Objects/abstract.c b/Objects/abstract.c index 7cca81464cd112..0c04cca33134d8 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -663,7 +663,8 @@ PyBuffer_FromContiguous(const Py_buffer *view, const void *buf, Py_ssize_t len, return 0; } -int PyObject_CopyData(PyObject *dest, PyObject *src) +int +PyObject_CopyData(PyObject *dest, PyObject *src) { Py_buffer view_dest, view_src; int k; @@ -733,6 +734,59 @@ int PyObject_CopyData(PyObject *dest, PyObject *src) return 0; } +int +PyObject_CopyToObject(PyObject *obj, void *buf, Py_ssize_t len, char fort) +{ + char *tmp_obj, *tmp_buf = buf; + Py_buffer view_obj; + Py_ssize_t *indices, elem_num = 1; + + assert(buf != NULL); + if (!PyObject_CheckBuffer(obj)) { + PyErr_SetString(PyExc_TypeError, + "object supporting the buffer protocol required"); + return -1; + } + + if (PyObject_GetBuffer(obj, &view_obj, PyBUF_FULL)) { + return -1; + } + + if (view_obj.len < len) { + PyErr_SetString(PyExc_BufferError, + "destination is too small to receive data from buf"); + PyBuffer_Release(&view_obj); + return -1; + } + + /* just copy it directly through memcpy */ + if (fort == 'C' || fort == 'F') { + memcpy(view_obj.buf, buf, len); + PyBuffer_Release(&view_obj); + return 0; + } + + /* quick copy implementation */ + indices = (Py_ssize_t *)PyMem_Calloc(view_obj.ndim, sizeof(Py_ssize_t)); + if (!indices) { + PyErr_NoMemory(); + PyBuffer_Release(&view_obj); + return -1; + } + for (int i = 0; i < view_obj.ndim; i++) { + /* XXX(nnorwitz): can this overflow? */ + elem_num *= view_obj.shape[i]; + } + while (elem_num--) { + tmp_obj = PyBuffer_GetPointer(&view_obj, indices); + memcpy(tmp_obj, tmp_buf++, view_obj.itemsize); + _Py_add_one_to_index_C(view_obj.ndim, indices, view_obj.shape); + } + PyMem_Free(indices); + PyBuffer_Release(&view_obj); + return 0; +} + void PyBuffer_FillContiguousStrides(int nd, Py_ssize_t *shape, Py_ssize_t *strides, int itemsize, diff --git a/PC/python3dll.c b/PC/python3dll.c index 1845334b244d8c..503cf706b71bb7 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -477,6 +477,7 @@ EXPORT_FUNC(PyObject_CheckBuffer) EXPORT_FUNC(PyObject_CheckReadBuffer) EXPORT_FUNC(PyObject_ClearWeakRefs) EXPORT_FUNC(PyObject_CopyData) +EXPORT_FUNC(PyObject_CopyToObject) EXPORT_FUNC(PyObject_DelAttr) EXPORT_FUNC(PyObject_DelAttrString) EXPORT_FUNC(PyObject_DelItem)