Skip to content

Commit 79404e9

Browse files
authored
Add Py_fopen() and Py_fclose() (#127)
1 parent 7eb512b commit 79404e9

File tree

4 files changed

+66
-0
lines changed

4 files changed

+66
-0
lines changed

docs/api.rst

+8
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,14 @@ Python 3.14
169169

170170
See `PyLong_FromUInt64() documentation <https://docs.python.org/dev/c-api/long.html#c.PyLong_FromUInt64>`__.
171171

172+
.. c:function:: FILE* Py_fopen(PyObject *path, const char *mode)
173+
174+
See `Py_fopen() documentation <https://docs.python.org/dev/c-api/sys.html#c.Py_fopen>`__.
175+
176+
.. c:function:: int Py_fclose(FILE *file)
177+
178+
See `Py_fclose() documentation <https://docs.python.org/dev/c-api/sys.html#c.Py_fclose>`__.
179+
172180

173181
Not supported:
174182

docs/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Changelog
22
=========
33

4+
* 2025-01-06: Add ``Py_fopen()`` and ``Py_fclose()`` functions.
45
* 2024-12-16: Add ``structmember.h`` constants:
56

67
* ``Py_T_BOOL``

pythoncapi_compat.h

+41
Original file line numberDiff line numberDiff line change
@@ -1933,6 +1933,47 @@ PyLongWriter_Finish(PyLongWriter *writer)
19331933
#endif
19341934

19351935

1936+
// gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4
1937+
#if PY_VERSION_HEX < 0x030E00A4
1938+
static inline FILE* Py_fopen(PyObject *path, const char *mode)
1939+
{
1940+
#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
1941+
extern FILE* _Py_fopen_obj(PyObject *path, const char *mode);
1942+
return _Py_fopen_obj(path, mode);
1943+
#else
1944+
FILE *f;
1945+
PyObject *bytes;
1946+
#if PY_VERSION_HEX >= 0x03000000
1947+
if (!PyUnicode_FSConverter(path, &bytes)) {
1948+
return NULL;
1949+
}
1950+
#else
1951+
if (!PyString_Check(path)) {
1952+
PyErr_SetString(PyExc_TypeError, "except str");
1953+
return NULL;
1954+
}
1955+
bytes = Py_NewRef(path);
1956+
#endif
1957+
const char *path_bytes = PyBytes_AS_STRING(bytes);
1958+
1959+
f = fopen(path_bytes, mode);
1960+
Py_DECREF(bytes);
1961+
1962+
if (f == NULL) {
1963+
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path);
1964+
return NULL;
1965+
}
1966+
return f;
1967+
#endif
1968+
}
1969+
1970+
int Py_fclose(FILE *file)
1971+
{
1972+
return fclose(file);
1973+
}
1974+
#endif
1975+
1976+
19361977
#ifdef __cplusplus
19371978
}
19381979
#endif

tests/test_pythoncapi_compat_cext.c

+16
Original file line numberDiff line numberDiff line change
@@ -2097,6 +2097,21 @@ test_structmember(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
20972097
}
20982098

20992099

2100+
static PyObject *
2101+
test_file(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
2102+
{
2103+
const char *filename = __FILE__;
2104+
PyObject *path = create_string(filename);
2105+
2106+
FILE *fp = Py_fopen(path, "rb");
2107+
Py_DECREF(path);
2108+
assert(fp != NULL);
2109+
Py_fclose(fp);
2110+
2111+
Py_RETURN_NONE;
2112+
}
2113+
2114+
21002115
static struct PyMethodDef methods[] = {
21012116
{"test_object", test_object, METH_NOARGS, _Py_NULL},
21022117
{"test_py_is", test_py_is, METH_NOARGS, _Py_NULL},
@@ -2144,6 +2159,7 @@ static struct PyMethodDef methods[] = {
21442159
{"test_iter", test_iter, METH_NOARGS, _Py_NULL},
21452160
{"test_long_stdint", test_long_stdint, METH_NOARGS, _Py_NULL},
21462161
{"test_structmember", test_structmember, METH_NOARGS, _Py_NULL},
2162+
{"test_file", test_file, METH_NOARGS, _Py_NULL},
21472163
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
21482164
};
21492165

0 commit comments

Comments
 (0)