diff --git a/docs/api.rst b/docs/api.rst index 07b303a..a85a61d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -169,6 +169,14 @@ Python 3.14 See `PyLong_FromUInt64() documentation `__. +.. c:function:: FILE* Py_fopen(PyObject *path, const char *mode) + + See `Py_fopen() documentation `__. + +.. c:function:: int Py_fclose(FILE *file) + + See `Py_fclose() documentation `__. + Not supported: diff --git a/docs/changelog.rst b/docs/changelog.rst index 3500bd5..04a63b2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* 2025-01-06: Add ``Py_fopen()`` and ``Py_fclose()`` functions. * 2024-12-16: Add ``structmember.h`` constants: * ``Py_T_BOOL`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index cee282d..0ea8e80 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1933,6 +1933,47 @@ PyLongWriter_Finish(PyLongWriter *writer) #endif +// gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4 +#if PY_VERSION_HEX < 0x030E00A4 +static inline FILE* Py_fopen(PyObject *path, const char *mode) +{ +#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + extern FILE* _Py_fopen_obj(PyObject *path, const char *mode); + return _Py_fopen_obj(path, mode); +#else + FILE *f; + PyObject *bytes; +#if PY_VERSION_HEX >= 0x03000000 + if (!PyUnicode_FSConverter(path, &bytes)) { + return NULL; + } +#else + if (!PyString_Check(path)) { + PyErr_SetString(PyExc_TypeError, "except str"); + return NULL; + } + bytes = Py_NewRef(path); +#endif + const char *path_bytes = PyBytes_AS_STRING(bytes); + + f = fopen(path_bytes, mode); + Py_DECREF(bytes); + + if (f == NULL) { + PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path); + return NULL; + } + return f; +#endif +} + +int Py_fclose(FILE *file) +{ + return fclose(file); +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index aff19e0..f1557bc 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -2097,6 +2097,21 @@ test_structmember(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +static PyObject * +test_file(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + const char *filename = __FILE__; + PyObject *path = create_string(filename); + + FILE *fp = Py_fopen(path, "rb"); + Py_DECREF(path); + assert(fp != NULL); + Py_fclose(fp); + + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -2144,6 +2159,7 @@ static struct PyMethodDef methods[] = { {"test_iter", test_iter, METH_NOARGS, _Py_NULL}, {"test_long_stdint", test_long_stdint, METH_NOARGS, _Py_NULL}, {"test_structmember", test_structmember, METH_NOARGS, _Py_NULL}, + {"test_file", test_file, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} };