Skip to content

Commit 773c4d9

Browse files
committed
gh-127350: Add Py_fopen() function
Rename _Py_fopen_obj() to Py_fopen(). The function now also accepts bytes path on Windows. Remove the private, undocumented, and untested function _Py_fopen_obj().
1 parent d5d84c3 commit 773c4d9

File tree

17 files changed

+197
-75
lines changed

17 files changed

+197
-75
lines changed

Doc/c-api/sys.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,22 @@ Operating System Utilities
216216
The function now uses the UTF-8 encoding on Windows if
217217
:c:member:`PyPreConfig.legacy_windows_fs_encoding` is zero.
218218
219+
.. c:function:: FILE* Py_fopen(PyObject *path, const char *mode)
220+
221+
Similar to the :c:func:`!fopen` function, but *path* is a Python object and
222+
an exception is set on error.
223+
224+
*path* must be a :class:`str` object or a :class:`bytes` object.
225+
226+
On success, return the new file object.
227+
On error, set an exception and return ``NULL``.
228+
229+
The file descriptor is created non-inheritable (:pep:`446`).
230+
231+
The caller must hold the GIL.
232+
233+
.. versionadded:: next
234+
219235
220236
.. _systemfunctions:
221237

Doc/whatsnew/3.14.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,11 @@ New features
10341034
* Add :c:func:`PyUnstable_Object_EnableDeferredRefcount` for enabling
10351035
deferred reference counting, as outlined in :pep:`703`.
10361036

1037+
* Add :c:func:`Py_fopen` function to open a file. Similar to the
1038+
:c:func:`!fopen` function, but the *path* parameter is a Python object and an
1039+
exception is set on error.
1040+
(Contributed by Victor Stinner in :gh:`127350`.)
1041+
10371042
Porting to Python 3.14
10381043
----------------------
10391044

Include/cpython/fileutils.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# error "this header file must not be included directly"
33
#endif
44

5-
// Used by _testcapi which must not use the internal C API
6-
PyAPI_FUNC(FILE*) _Py_fopen_obj(
5+
PyAPI_FUNC(FILE*) Py_fopen(
76
PyObject *path,
87
const char *mode);

Lib/test/test_capi/test_file.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import os
2+
import unittest
3+
from test.support import import_helper
4+
5+
_testcapi = import_helper.import_module('_testcapi')
6+
7+
8+
class CAPIFileTest(unittest.TestCase):
9+
def test_py_fopen(self):
10+
for filename in (__file__, os.fsencode(__file__)):
11+
with self.subTest(filename=filename):
12+
content = _testcapi.py_fopen(filename, "rb")
13+
with open(filename, "rb") as fp:
14+
self.assertEqual(fp.read(256), content)
15+
16+
for invalid_type in (123, object()):
17+
with self.subTest(filename=invalid_type):
18+
with self.assertRaises(TypeError):
19+
_testcapi.py_fopen(invalid_type, "r")
20+
21+
22+
if __name__ == "__main__":
23+
unittest.main()

Lib/test/test_ssl.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,8 +1325,7 @@ def test_load_verify_cadata(self):
13251325
def test_load_dh_params(self):
13261326
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
13271327
ctx.load_dh_params(DHFILE)
1328-
if os.name != 'nt':
1329-
ctx.load_dh_params(BYTES_DHFILE)
1328+
ctx.load_dh_params(BYTES_DHFILE)
13301329
self.assertRaises(TypeError, ctx.load_dh_params)
13311330
self.assertRaises(TypeError, ctx.load_dh_params, None)
13321331
with self.assertRaises(FileNotFoundError) as cm:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :c:func:`Py_fopen` function to open a file. Similar to the :c:func:`!fopen`
2+
function, but the *path* parameter is a Python object and an exception is set
3+
on error. Patch by Victor Stinner.

Modules/_ssl.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4377,7 +4377,7 @@ _ssl__SSLContext_load_dh_params_impl(PySSLContext *self, PyObject *filepath)
43774377
FILE *f;
43784378
DH *dh;
43794379

4380-
f = _Py_fopen_obj(filepath, "rb");
4380+
f = Py_fopen(filepath, "rb");
43814381
if (f == NULL)
43824382
return NULL;
43834383

Modules/_ssl/debughelpers.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@ _PySSLContext_set_keylog_filename(PySSLContext *self, PyObject *arg, void *c) {
180180
return 0;
181181
}
182182

183-
/* _Py_fopen_obj() also checks that arg is of proper type. */
184-
fp = _Py_fopen_obj(arg, "a" PY_STDIOTEXTMODE);
183+
/* Py_fopen() also checks that arg is of proper type. */
184+
fp = Py_fopen(arg, "a" PY_STDIOTEXTMODE);
185185
if (fp == NULL)
186186
return -1;
187187

Modules/_testcapi/clinic/file.c.h

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/_testcapi/file.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,43 @@
1+
// clinic/file.c.h uses internal pycore_modsupport.h API
2+
#define PYTESTCAPI_NEED_INTERNAL_API
3+
14
#include "parts.h"
25
#include "util.h"
6+
#include "clinic/file.c.h"
7+
8+
/*[clinic input]
9+
module _testcapi
10+
[clinic start generated code]*/
11+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6361033e795369fc]*/
12+
13+
/*[clinic input]
14+
_testcapi.py_fopen
15+
16+
path: object
17+
mode: str
18+
/
19+
20+
Call Py_fopen() and return fread(256).
21+
[clinic start generated code]*/
322

23+
static PyObject *
24+
_testcapi_py_fopen_impl(PyObject *module, PyObject *path, const char *mode)
25+
/*[clinic end generated code: output=5a900af000f759de input=0878c2f9333abd60]*/
26+
{
27+
FILE *fp = Py_fopen(path, mode);
28+
if (fp == NULL) {
29+
return NULL;
30+
}
31+
32+
char buffer[256];
33+
size_t size = fread(buffer, 1, Py_ARRAY_LENGTH(buffer), fp);
34+
fclose(fp);
35+
36+
return PyBytes_FromStringAndSize(buffer, size);
37+
}
438

539
static PyMethodDef test_methods[] = {
40+
_TESTCAPI_PY_FOPEN_METHODDEF
641
{NULL},
742
};
843

0 commit comments

Comments
 (0)