From 6418654d3665034d7b58412dc06740321dfb3df8 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 3 Jun 2025 16:24:04 +0200 Subject: [PATCH] Add PySys_GetAttr() function --- docs/api.rst | 19 ++++++++ docs/changelog.rst | 7 +++ pythoncapi_compat.h | 65 ++++++++++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 72 +++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index f2dd262..609edbd 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -26,6 +26,25 @@ Latest version of the header file: `pythoncapi_compat.h `_. +Python 3.15 +----------- + +.. c:function:: PyObject* PySys_GetAttr(const char *name) + + See `PySys_GetAttr() documentation `__. + +.. c:function:: PyObject* PySys_GetAttrString(const char *name) + + See `PySys_GetAttrString() documentation `__. + +.. c:function:: PyObject* PySys_GetOptionalAttr(const char *name) + + See `PySys_GetOptionalAttr() documentation `__. + +.. c:function:: PyObject* PySys_GetOptionalAttrString(const char *name) + + See `PySys_GetOptionalAttrString() documentation `__. + Python 3.14 ----------- diff --git a/docs/changelog.rst b/docs/changelog.rst index 74e046b..bb981c4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,13 @@ Changelog ========= +* 2025-06-03: Add functions: + + * ``PySys_GetAttr()`` + * ``PySys_GetAttrString()`` + * ``PySys_GetOptionalAttr()`` + * ``PySys_GetOptionalAttrString()`` + * 2025-01-19: Add ``PyConfig_Get()`` functions. * 2025-01-06: Add ``Py_fopen()`` and ``Py_fclose()`` functions. * 2024-12-16: Add ``structmember.h`` constants: diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 4b179e4..eb84307 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -2199,6 +2199,71 @@ PyConfig_GetInt(const char *name, int *value) #endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x030F0000 +static inline PyObject* +PySys_GetAttrString(const char *name) +{ +#if PY_VERSION_HEX >= 0x03000000 + PyObject *value = Py_XNewRef(PySys_GetObject(name)); +#else + PyObject *value = Py_XNewRef(PySys_GetObject((char*)name)); +#endif + if (value != NULL) { + return value; + } + if (!PyErr_Occurred()) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", name); + } + return NULL; +} + +static inline PyObject* +PySys_GetAttr(PyObject *name) +{ +#if PY_VERSION_HEX >= 0x03000000 + const char *name_str = PyUnicode_AsUTF8(name); +#else + const char *name_str = PyString_AsString(name); +#endif + if (name_str == NULL) { + return NULL; + } + + return PySys_GetAttrString(name_str); +} + +static inline int +PySys_GetOptionalAttrString(const char *name, PyObject **value) +{ +#if PY_VERSION_HEX >= 0x03000000 + *value = Py_XNewRef(PySys_GetObject(name)); +#else + *value = Py_XNewRef(PySys_GetObject((char*)name)); +#endif + if (*value != NULL) { + return 1; + } + return 0; +} + +static inline int +PySys_GetOptionalAttr(PyObject *name, PyObject **value) +{ +#if PY_VERSION_HEX >= 0x03000000 + const char *name_str = PyUnicode_AsUTF8(name); +#else + const char *name_str = PyString_AsString(name); +#endif + if (name_str == NULL) { + *value = NULL; + return -1; + } + + return PySys_GetOptionalAttrString(name_str, value); +} +#endif // PY_VERSION_HEX < 0x030F00A1 + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 9d324a4..82cf387 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -2181,6 +2181,77 @@ test_config(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) #endif +static PyObject * +test_sys(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + const char *stdout_str = "stdout"; + PyObject *stdout_obj = create_string(stdout_str); +#if PYTHON3 + PyObject *sys_stdout = PySys_GetObject(stdout_str); // borrowed ref +#else + PyObject *sys_stdout = PySys_GetObject((char*)stdout_str); // borrowed ref +#endif + const char *nonexistent_str = "nonexistent"; + PyObject *nonexistent_obj = create_string(nonexistent_str); + PyObject *error_obj = PyLong_FromLong(1); + PyObject *value; + + // get sys.stdout + value = PySys_GetAttr(stdout_obj); + assert(value == sys_stdout); + Py_DECREF(value); + + value = PySys_GetAttrString(stdout_str); + assert(value == sys_stdout); + Py_DECREF(value); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(stdout_obj, &value) == 1); + assert(value == sys_stdout); + Py_DECREF(value); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttrString(stdout_str, &value) == 1); + assert(value == sys_stdout); + Py_DECREF(value); + + // non existent attribute + value = PySys_GetAttr(nonexistent_obj); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_RuntimeError)); + PyErr_Clear(); + + value = PySys_GetAttrString(nonexistent_str); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_RuntimeError)); + PyErr_Clear(); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(nonexistent_obj, &value) == 0); + assert(value == NULL); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttrString(nonexistent_str, &value) == 0); + assert(value == NULL); + + // invalid attribute type + value = PySys_GetAttr(error_obj); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(error_obj, &value) == -1); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + Py_DECREF(stdout_obj); + Py_DECREF(nonexistent_obj); + 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}, @@ -2232,6 +2303,7 @@ static struct PyMethodDef methods[] = { #if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION) {"test_config", test_config, METH_NOARGS, _Py_NULL}, #endif + {"test_sys", test_sys, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} };