diff --git a/Doc/c-api/abstract.rst b/Doc/c-api/abstract.rst index f5df09fa7fd786..f5bb0514b95948 100644 --- a/Doc/c-api/abstract.rst +++ b/Doc/c-api/abstract.rst @@ -24,3 +24,4 @@ but whose items have not been set to some non-\ ``NULL`` value yet. mapping.rst iter.rst buffer.rst + resource.rst diff --git a/Doc/c-api/bytes.rst b/Doc/c-api/bytes.rst index 9f48f2ffafe170..d888a3fd8bd8d3 100644 --- a/Doc/c-api/bytes.rst +++ b/Doc/c-api/bytes.rst @@ -151,6 +151,13 @@ called with a non-bytes parameter. Similar to :c:func:`PyBytes_AsString`, but without error checking. +.. c:function:: char* PyBytes_AsStringRes(PyObject *o, PyResource *res) + + Similar to :c:func:`PyBytes_AsString`, but the returning string remains + valid until :c:func:`PyResource_Close(res) ` is called. + + .. versionadded:: 3.13 + .. c:function:: int PyBytes_AsStringAndSize(PyObject *obj, char **buffer, Py_ssize_t *length) Return the null-terminated contents of the object *obj* diff --git a/Doc/c-api/resource.rst b/Doc/c-api/resource.rst new file mode 100644 index 00000000000000..91bdf0aaa320f5 --- /dev/null +++ b/Doc/c-api/resource.rst @@ -0,0 +1,25 @@ +.. highlight:: c + +PyResource +========== + +.. versionadded:: 3.13 + +API using a callback function to close a resource. + + +.. c:type:: PyResource + + .. c:member:: void (*close_func) (void *data) + + Callback function called by :c:func:`PyResource_Close`. + + .. c:member:: void *data + + Argument passed to :c:member:`close_func` by :c:func:`PyResource_Close`. + + +.. c:function:: void PyResource_Close(PyResource *res) + + Close a resource: call :c:member:`PyResource.close_func` with + :c:member:`PyResource.data`. diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index ed415a4dc644a4..97ea778b4a4f08 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -30,6 +30,7 @@ var,PyByteArray_Type,3.2,, var,PyBytesIter_Type,3.2,, function,PyBytes_AsString,3.2,, function,PyBytes_AsStringAndSize,3.2,, +function,PyBytes_AsStringRes,3.13,, function,PyBytes_Concat,3.2,, function,PyBytes_ConcatAndDel,3.2,, function,PyBytes_DecodeEscape,3.2,, @@ -548,6 +549,8 @@ function,PyObject_VectorcallMethod,3.12,, var,PyProperty_Type,3.2,, var,PyRangeIter_Type,3.2,, var,PyRange_Type,3.2,, +type,PyResource,3.13,,full-abi +function,PyResource_Close,3.13,, var,PyReversed_Type,3.2,, function,PySeqIter_New,3.2,, var,PySeqIter_Type,3.2,, diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 8ca0abf5525763..d6bea9d6b1b65c 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -802,6 +802,12 @@ New Features not needed. (Contributed by Victor Stinner in :gh:`106004`.) +* Added the :c:func:`PyResource_Close` function. + (Contributed by Victor Stinner in :gh:`106592`.) + +* Added the :c:func:`PyBytes_AsStringRes` function. + (Contributed by Victor Stinner in :gh:`106592`.) + Porting to Python 3.13 ---------------------- diff --git a/Include/Python.h b/Include/Python.h index 07f6c202a7f126..e004b6085b27a8 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -39,6 +39,7 @@ #include "pymacro.h" #include "pymath.h" #include "pymem.h" +#include "pyresource.h" #include "pytypedefs.h" #include "pybuffer.h" #include "object.h" diff --git a/Include/bytesobject.h b/Include/bytesobject.h index ee448cd02bdab3..5b7a4255b1c164 100644 --- a/Include/bytesobject.h +++ b/Include/bytesobject.h @@ -39,7 +39,8 @@ PyAPI_FUNC(PyObject *) PyBytes_FromFormatV(const char*, va_list) PyAPI_FUNC(PyObject *) PyBytes_FromFormat(const char*, ...) Py_GCC_ATTRIBUTE((format(printf, 1, 2))); PyAPI_FUNC(Py_ssize_t) PyBytes_Size(PyObject *); -PyAPI_FUNC(char *) PyBytes_AsString(PyObject *); +PyAPI_FUNC(char*) PyBytes_AsString(PyObject *op); +PyAPI_FUNC(const char*) PyBytes_AsStringRes(PyObject *op, PyResource *res); PyAPI_FUNC(PyObject *) PyBytes_Repr(PyObject *, int); PyAPI_FUNC(void) PyBytes_Concat(PyObject **, PyObject *); PyAPI_FUNC(void) PyBytes_ConcatAndDel(PyObject **, PyObject *); diff --git a/Include/pyresource.h b/Include/pyresource.h new file mode 100644 index 00000000000000..34151f0de7e9a5 --- /dev/null +++ b/Include/pyresource.h @@ -0,0 +1,21 @@ +#ifndef Py_RESOURCE_H +#define Py_RESOURCE_H +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + void (*close_func) (void *data); + void *data; +} PyResource; + +PyAPI_FUNC(void) PyResource_Close(PyResource *res); + +#ifdef Py_BUILD_CORE +extern void _PyResource_DECREF(void *data); +#endif + +#ifdef __cplusplus +} +#endif +#endif // !Py_RESOURCE_H diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 566d36a3f5ba11..ce1404085d75f1 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -65,6 +65,7 @@ def test_windows_feature_macros(self): "PyBytesIter_Type", "PyBytes_AsString", "PyBytes_AsStringAndSize", + "PyBytes_AsStringRes", "PyBytes_Concat", "PyBytes_ConcatAndDel", "PyBytes_DecodeEscape", @@ -567,6 +568,7 @@ def test_windows_feature_macros(self): "PyProperty_Type", "PyRangeIter_Type", "PyRange_Type", + "PyResource_Close", "PyReversed_Type", "PySeqIter_New", "PySeqIter_Type", diff --git a/Makefile.pre.in b/Makefile.pre.in index 5f1988b0d17213..3a0365253faa5a 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1658,6 +1658,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/pymath.h \ $(srcdir)/Include/pymem.h \ $(srcdir)/Include/pyport.h \ + $(srcdir)/Include/pyresource.h \ $(srcdir)/Include/pystate.h \ $(srcdir)/Include/pystats.h \ $(srcdir)/Include/pystrcmp.h \ diff --git a/Misc/NEWS.d/next/C API/2023-07-24-22-30-00.gh-issue-106592.0UN_B6.rst b/Misc/NEWS.d/next/C API/2023-07-24-22-30-00.gh-issue-106592.0UN_B6.rst new file mode 100644 index 00000000000000..9f5d744198be53 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-07-24-22-30-00.gh-issue-106592.0UN_B6.rst @@ -0,0 +1 @@ +Added the :c:func:`PyResource_Close` function. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/C API/2023-07-24-22-30-22.gh-issue-106592.V8PaNO.rst b/Misc/NEWS.d/next/C API/2023-07-24-22-30-22.gh-issue-106592.V8PaNO.rst new file mode 100644 index 00000000000000..4b25beefcdc6e6 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-07-24-22-30-22.gh-issue-106592.V8PaNO.rst @@ -0,0 +1 @@ +Added the :c:func:`PyBytes_AsStringRes` function. Patch by Victor Stinner. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 16d5c1a07ae3e2..0c71f3a79ee687 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2450,3 +2450,10 @@ added = '3.13' [function.PyDict_GetItemStringRef] added = '3.13' +[struct.PyResource] + added = '3.13' + struct_abi_kind = 'full-abi' +[function.PyResource_Close] + added = '3.13' +[function.PyBytes_AsStringRes] + added = '3.13' diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 065a7fb733d432..1cf49e8de37ecf 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3654,6 +3654,33 @@ test_dict_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +static PyObject * +test_resource_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + const char *src = "test_resource_capi"; + PyObject *bytes = PyBytes_FromString(src); + if (bytes == NULL) { + return NULL; + } + + // test PyBytes_AsStringRes() + PyResource res; + const char *str = PyBytes_AsStringRes(bytes, &res); + // delete the bytes object, the resource keeps a strong reference to it + Py_CLEAR(bytes); + + // test PyResource_Close() + assert(memcmp(str, src, strlen(src)) == 0); + PyResource_Close(&res); + + // test PyResource_Close() with NULL callback: must not crash + PyResource res2 = {.close_func = NULL, .data = NULL}; + PyResource_Close(&res2); + + Py_RETURN_NONE; +} + + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -3800,6 +3827,7 @@ static PyMethodDef TestMethods[] = { {"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS}, {"test_weakref_capi", test_weakref_capi, METH_NOARGS}, {"test_dict_capi", test_dict_capi, METH_NOARGS}, + {"test_resource_capi", test_resource_capi, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 6b9231a9fa7693..04f73987bc82ed 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -1209,7 +1209,7 @@ PyBytes_Size(PyObject *op) return Py_SIZE(op); } -char * +char* PyBytes_AsString(PyObject *op) { if (!PyBytes_Check(op)) { @@ -1220,6 +1220,19 @@ PyBytes_AsString(PyObject *op) return ((PyBytesObject *)op)->ob_sval; } +const char* +PyBytes_AsStringRes(PyObject *op, PyResource *res) +{ + if (!PyBytes_Check(op)) { + PyErr_Format(PyExc_TypeError, + "expected bytes, %.200s found", Py_TYPE(op)->tp_name); + return NULL; + } + res->close_func = _PyResource_DECREF; + res->data = Py_NewRef(op); + return ((PyBytesObject *)op)->ob_sval; +} + int PyBytes_AsStringAndSize(PyObject *obj, char **s, diff --git a/Objects/object.c b/Objects/object.c index b724eb5f8f0799..dfff0fdf677303 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2689,6 +2689,22 @@ int Py_IsFalse(PyObject *x) return Py_Is(x, Py_False); } +void _PyResource_DECREF(void *data) +{ + PyObject *obj = _PyObject_CAST(data); + Py_DECREF(obj); +} + +void PyResource_Close(PyResource *res) +{ + if (res->close_func == NULL) { + return; + } + res->close_func(res->data); + res->close_func = NULL; + res->data = NULL; +} + #ifdef __cplusplus } #endif diff --git a/PC/python3dll.c b/PC/python3dll.c index 64dfbba3e424a1..788fc8091e97c0 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -110,6 +110,7 @@ EXPORT_FUNC(PyByteArray_Resize) EXPORT_FUNC(PyByteArray_Size) EXPORT_FUNC(PyBytes_AsString) EXPORT_FUNC(PyBytes_AsStringAndSize) +EXPORT_FUNC(PyBytes_AsStringRes) EXPORT_FUNC(PyBytes_Concat) EXPORT_FUNC(PyBytes_ConcatAndDel) EXPORT_FUNC(PyBytes_DecodeEscape) @@ -515,6 +516,7 @@ EXPORT_FUNC(PyOS_string_to_double) EXPORT_FUNC(PyOS_strtol) EXPORT_FUNC(PyOS_strtoul) EXPORT_FUNC(PyOS_vsnprintf) +EXPORT_FUNC(PyResource_Close) EXPORT_FUNC(PySeqIter_New) EXPORT_FUNC(PySequence_Check) EXPORT_FUNC(PySequence_Concat) diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 5ccc8958330650..1e32730d411aef 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -308,6 +308,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 54a77f81a9a1ab..e76d56c5b8999d 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -171,6 +171,9 @@ Include + + Include + Include