8000 gh-127350: Add Py_fopen() function · vstinner/cpython@77dbaea · GitHub
[go: up one dir, main page]

Skip to content

Commit 77dbaea

Browse files
committed
pythongh-127350: Add Py_fopen() function
Remove _Py_fopen_obj() private undocumented and untested function.
1 parent d5d84c3 commit 77dbaea

File tree

16 files changed

+161
-45
lines changed

16 files changed

+161
-45
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
225+
226+
On success, return the new file object on success.
227+
On error, set an exception and return ``NULL``.
228+
229+
The file descriptor is created non-inheritable.
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 :class:`str`
1039+
object and an 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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import unittest
2+
from test.support import import_helper
3+
4+
_testcapi = import_helper.import_module('_testcapi')
5+
6+
7+
class CAPIFileTest(unittest.TestCase):
8+
def test_py_fopen(self):
9+
content = _testcapi.py_fopen(__file__, "r")
10+
with open(__file__, "rb") as fp:
11+
self.assertEqual(fp.read(4096), content)
12+
13+
for invalid_type in (b"bytes", 123, object()):
14+
with self.subTest(filename=invalid_type):
15+
with self.assertRaises(TypeError):
16+
_testcapi.py_fopen(invalid_type, "r")
17+
18+
19+
if __name__ == "__main__":
20+
unittest.main()
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
2+
:c:func:`!fopen` function, but the *path* parameter is a Python :class:`str`
3+
object and an exception is set 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(4096).
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=e0e0bed40dc3a265]*/
26+
{
27+
FILE *fp = Py_fopen(path, mode);
28+
if (fp == NULL) {
29+
return NULL;
30+
}
31+
32+
char buffer[4096];
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

Modules/_testcapi/object.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ call_pyobject_print(PyObject *self, PyObject * args)
1515
return NULL;
1616
}
1717

18-
fp = _Py_fopen_obj(filename, "w+");
18+
fp = Py_fopen(filename, "w+");
1919

2020
if (Py_IsTrue(print_raw)) {
2121
flags = Py_PRINT_RAW;
@@ -41,7 +41,7 @@ pyobject_print_null(PyObject *self, PyObject *args)
4141
return NULL;
4242
}
4343

44-
fp = _Py_fopen_obj(filename, "w+");
44+
fp = Py_fopen(filename, "w+");
4545

4646
if (PyObject_Print(NULL, fp, 0) < 0) {
4747
fclose(fp);
@@ -72,7 +72,7 @@ pyobject_print_noref_object(PyObject *self, PyObject *args)
7272
return NULL;
7373
}
7474

75-
fp = _Py_fopen_obj(filename, "w+");
75+
fp = Py_fopen(filename, "w+");
7676

7777
if (PyObject_Print(test_string, fp, 0) < 0){
7878
fclose(fp);
@@ -103,7 +103,7 @@ pyobject_print_os_error(PyObject *self, PyObject *args)
103103
}
104104

105105
// open file in read mode to induce OSError
106-
fp = _Py_fopen_obj(filename, "r");
106+
fp = Py_fopen(filename, "r");
107107

108108
if (PyObject_Print(test_string, fp, 0) < 0) {
109109
fclose(fp);

0 commit comments

Comments
 (0)
0