Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Doc/c-api/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,22 @@ Operating System Utilities
The function now uses the UTF-8 encoding on Windows if
:c:member:`PyPreConfig.legacy_windows_fs_encoding` is zero.

.. c:function:: FILE* Py_fopen(PyObject *path, const char *mode)

Similar to the :c:func:`!fopen` function, but *path* is a Python object and
an exception is set on error.

*path* must be a :class:`str` object or a :class:`bytes` object.

On success, return the new file object.
On error, set an exception and return ``NULL``.

The file descriptor is created non-inheritable (:pep:`446`).

The caller must hold the GIL.

.. versionadded:: next


.. _systemfunctions:

Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,11 @@ New features
* Add :c:func:`PyUnstable_Object_EnableDeferredRefcount` for enabling
deferred reference counting, as outlined in :pep:`703`.

* Add :c:func:`Py_fopen` function to open a file. Similar to the
:c:func:`!fopen` function, but the *path* parameter is a Python object and an
exception is set on error.
(Contributed by Victor Stinner in :gh:`127350`.)

Porting to Python 3.14
----------------------

Expand Down
3 changes: 1 addition & 2 deletions Include/cpython/fileutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# error "this header file must not be included directly"
#endif

// Used by _testcapi which must not use the internal C API
PyAPI_FUNC(FILE*) _Py_fopen_obj(
PyAPI_FUNC(FILE*) Py_fopen(
PyObject *path,
const char *mode);
23 changes: 23 additions & 0 deletions Lib/test/test_capi/test_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os
import unittest
from test.support import import_helper

_testcapi = import_helper.import_module('_testcapi')


class CAPIFileTest(unittest.TestCase):
def test_py_fopen(self):
for filename in (__file__, os.fsencode(__file__)):
with self.subTest(filename=filename):
content = _testcapi.py_fopen(filename, "rb")
with open(filename, "rb") as fp:
self.assertEqual(fp.read(256), content)

for invalid_type in (123, object()):
with self.subTest(filename=invalid_type):
with self.assertRaises(TypeError):
_testcapi.py_fopen(invalid_type, "r")


if __name__ == "__main__":
unittest.main()
3 changes: 1 addition & 2 deletions Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1325,8 +1325,7 @@ def test_load_verify_cadata(self):
def test_load_dh_params(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_dh_params(DHFILE)
if os.name != 'nt':
ctx.load_dh_params(BYTES_DHFILE)
ctx.load_dh_params(BYTES_DHFILE)
self.assertRaises(TypeError, ctx.load_dh_params)
self.assertRaises(TypeError, ctx.load_dh_params, None)
with self.assertRaises(FileNotFoundError) as cm:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :c:func:`Py_fopen` function to open a file. Similar to the :c:func:`!fopen`
function, but the *path* parameter is a Python object and an exception is set
on error. Patch by Victor Stinner.
2 changes: 1 addition & 1 deletion Modules/_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -4377,7 +4377,7 @@ _ssl__SSLContext_load_dh_params_impl(PySSLContext *self, PyObject *filepath)
FILE *f;
DH *dh;

f = _Py_fopen_obj(filepath, "rb");
f = Py_fopen(filepath, "rb");
if (f == NULL)
return NULL;

Expand Down
4 changes: 2 additions & 2 deletions Modules/_ssl/debughelpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ _PySSLContext_set_keylog_filename(PySSLContext *self, PyObject *arg, void *c) {
return 0;
}

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

Expand Down
48 changes: 48 additions & 0 deletions Modules/_testcapi/clinic/file.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions Modules/_testcapi/file.c
Original file line number Diff line number Diff line change
@@ -1,8 +1,43 @@
// clinic/file.c.h uses internal pycore_modsupport.h API
#define PYTESTCAPI_NEED_INTERNAL_API

#include "parts.h"
#include "util.h"
#include "clinic/file.c.h"

/*[clinic input]
module _testcapi
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6361033e795369fc]*/

/*[clinic input]
_testcapi.py_fopen

path: object
mode: str
/

Call Py_fopen() and return fread(256).
[clinic start generated code]*/

static PyObject *
_testcapi_py_fopen_impl(PyObject *module, PyObject *path, const char *mode)
/*[clinic end generated code: output=5a900af000f759de input=0878c2f9333abd60]*/
{
FILE *fp = Py_fopen(path, mode);
if (fp == NULL) {
return NULL;
}

char buffer[256];
size_t size = fread(buffer, 1, Py_ARRAY_LENGTH(buffer), fp);
fclose(fp);

return PyBytes_FromStringAndSize(buffer, size);
}

static PyMethodDef test_methods[] = {
_TESTCAPI_PY_FOPEN_METHODDEF
{NULL},
};

Expand Down
8 changes: 4 additions & 4 deletions Modules/_testcapi/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ call_pyobject_print(PyObject *self, PyObject * args)
return NULL;
}

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

if (Py_IsTrue(print_raw)) {
flags = Py_PRINT_RAW;
Expand All @@ -41,7 +41,7 @@ pyobject_print_null(PyObject *self, PyObject *args)
return NULL;
}

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

if (PyObject_Print(NULL, fp, 0) < 0) {
fclose(fp);
Expand Down Expand Up @@ -72,7 +72,7 @@ pyobject_print_noref_object(PyObject *self, PyObject *args)
return NULL;
}

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

if (PyObject_Print(test_string, fp, 0) < 0){
fclose(fp);
Expand Down Expand Up @@ -103,7 +103,7 @@ pyobject_print_os_error(PyObject *self, PyObject *args)
}

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

if (PyObject_Print(test_string, fp, 0) < 0) {
fclose(fp);
Expand Down
12 changes: 6 additions & 6 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1744,7 +1744,7 @@ pymarshal_write_long_to_file(PyObject* self, PyObject *args)
&value, &filename, &version))
return NULL;

fp = _Py_fopen_obj(filename, "wb");
fp = Py_fopen(filename, "wb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
Expand All @@ -1769,7 +1769,7 @@ pymarshal_write_object_to_file(PyObject* self, PyObject *args)
&obj, &filename, &version))
return NULL;

fp = _Py_fopen_obj(filename, "wb");
fp = Py_fopen(filename, "wb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
Expand All @@ -1793,7 +1793,7 @@ pymarshal_read_short_from_file(PyObject* self, PyObject *args)
if (!PyArg_ParseTuple(args, "O:pymarshal_read_short_from_file", &filename))
return NULL;

fp = _Py_fopen_obj(filename, "rb");
fp = Py_fopen(filename, "rb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
Expand All @@ -1818,7 +1818,7 @@ pymarshal_read_long_from_file(PyObject* self, PyObject *args)
if (!PyArg_ParseTuple(args, "O:pymarshal_read_long_from_file", &filename))
return NULL;

fp = _Py_fopen_obj(filename, "rb");
fp = Py_fopen(filename, "rb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
Expand All @@ -1840,7 +1840,7 @@ pymarshal_read_last_object_from_file(PyObject* self, PyObject *args)
if (!PyArg_ParseTuple(args, "O:pymarshal_read_last_object_from_file", &filename))
return NULL;

FILE *fp = _Py_fopen_obj(filename, "rb");
FILE *fp = Py_fopen(filename, "rb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
Expand All @@ -1863,7 +1863,7 @@ pymarshal_read_object_from_file(PyObject* self, PyObject *args)
if (!PyArg_ParseTuple(args, "O:pymarshal_read_object_from_file", &filename))
return NULL;

FILE *fp = _Py_fopen_obj(filename, "rb");
FILE *fp = Py_fopen(filename, "rb");
if (fp == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
Expand Down
4 changes: 2 additions & 2 deletions Modules/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ pymain_run_file_obj(PyObject *program_name, PyObject *filename,
return pymain_exit_err_print();
}

FILE *fp = _Py_fopen_obj(filename, "rb");
FILE *fp = Py_fopen(filename, "rb");
if (fp == NULL) {
// Ignore the OSError
PyErr_Clear();
Expand Down Expand Up @@ -464,7 +464,7 @@ pymain_run_startup(PyConfig *config, int *exitcode)
goto error;
}

FILE *fp = _Py_fopen_obj(startup, "r");
FILE *fp = Py_fopen(startup, "r");
if (fp == NULL) {
int save_errno = errno;
PyErr_Clear();
Expand Down
2 changes: 1 addition & 1 deletion Python/errors.c
Original file line number Diff line number Diff line change
Expand Up @@ -1972,7 +1972,7 @@ _PyErr_ProgramDecodedTextObject(PyObject *filename, int lineno, const char* enco
return NULL;
}

FILE *fp = _Py_fopen_obj(filename, "r" PY_STDIOTEXTMODE);
FILE *fp = Py_fopen(filename, "r" PY_STDIOTEXTMODE);
if (fp == NULL) {
PyErr_Clear();
return NULL;
Expand Down
Loading
Loading