Skip to content

Commit c2683b1

Browse files
committed
Add PySys_GetAttr() function
1 parent 0a8b2c5 commit c2683b1

File tree

4 files changed

+149
-0
lines changed

4 files changed

+149
-0
lines changed

docs/api.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@ Latest version of the header file:
2626
`pythoncapi_compat.h <https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h>`_.
2727

2828

29+
Python 3.15
30+
-----------
31+
32+
.. c:function:: PyObject* PySys_GetAttr(const char *name)
33+
34+
See `PySys_GetAttr() documentation <https://docs.python.org/dev/c-api/sys.html#c.PySys_GetAttr>`__.
35+
36+
.. c:function:: PyObject* PySys_GetAttrString(const char *name)
37+
38+
See `PySys_GetAttrString() documentation <https://docs.python.org/dev/c-api/sys.html#c.PySys_GetAttrString>`__.
39+
40+
.. c:function:: PyObject* PySys_GetOptionalAttr(const char *name)
41+
42+
See `PySys_GetOptionalAttr() documentation <https://docs.python.org/dev/c-api/sys.html#c.PySys_GetOptionalAttr>`__.
43+
44+
.. c:function:: PyObject* PySys_GetOptionalAttrString(const char *name)
45+
46+
See `PySys_GetOptionalAttrString() documentation <https://docs.python.org/dev/c-api/sys.html#c.PySys_GetOptionalAttrString>`__.
47+
2948
Python 3.14
3049
-----------
3150

docs/changelog.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
Changelog
22
=========
33

4+
* 2025-06-03: Add functions:
5+
6+
* ``PySys_GetAttr()``
7+
* ``PySys_GetAttrString()``
8+
* ``PySys_GetOptionalAttr()``
9+
* ``PySys_GetOptionalAttrString()``
10+
411
* 2025-01-19: Add ``PyConfig_Get()`` functions.
512
* 2025-01-06: Add ``Py_fopen()`` and ``Py_fclose()`` functions.
613
* 2024-12-16: Add ``structmember.h`` constants:

pythoncapi_compat.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,6 +2199,70 @@ PyConfig_GetInt(const char *name, int *value)
21992199
#endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION)
22002200

22012201

2202+
#if PY_VERSION_HEX < 0x030F0000
2203+
static inline PyObject*
2204+
PySys_GetAttrString(const char *name)
2205+
{
2206+
#if PY_VERSION_HEX >= 0x03000000
2207+
PyObject *value = Py_XNewRef(PySys_GetObject(name));
2208+
#else
2209+
PyObject *value = Py_XNewRef(PySys_GetObject((char*)name));
2210+
#endif
2211+
if (value != NULL) {
2212+
return value;
2213+
}
2214+
if (!PyErr_Occurred()) {
2215+
PyErr_Format(PyExc_RuntimeError, "lost sys.%s", name);
2216+
}
2217+
return NULL;
2218+
}
2219+
2220+
static inline PyObject*
2221+
PySys_GetAttr(PyObject *name)
2222+
{
2223+
#if PY_VERSION_HEX >= 0x03000000
2224+
const char *name_str = PyUnicode_AsUTF8(name);
2225+
#else
2226+
const char *name_str = PyString_AsString(name);
2227+
#endif
2228+
if (name_str == NULL) {
2229+
return NULL;
2230+
}
2231+
2232+
return PySys_GetAttrString(name_str);
2233+
}
2234+
2235+
static inline int
2236+
PySys_GetOptionalAttrString(const char *name, PyObject **value)
2237+
{
2238+
#if PY_VERSION_HEX >= 0x03000000
2239+
*value = Py_XNewRef(PySys_GetObject(name));
2240+
#else
2241+
*value = Py_XNewRef(PySys_GetObject((char*)name));
2242+
#endif
2243+
if (*value != NULL) {
2244+
return 1;
2245+
}
2246+
return 0;
2247+
}
2248+
2249+
static inline int
2250+
PySys_GetOptionalAttr(PyObject *name, PyObject **value)
2251+
{
2252+
#if PY_VERSION_HEX >= 0x03000000
2253+
const char *name_str = PyUnicode_AsUTF8(name);
2254+
#else
2255+
const char *name_str = PyString_AsString(name);
2256+
#endif
2257+
if (name_str == NULL) {
2258+
return -1;
2259+
}
2260+
2261+
return PySys_GetOptionalAttrString(name_str, value);
2262+
}
2263+
#endif // PY_VERSION_HEX < 0x030F00A1
2264+
2265+
22022266
#ifdef __cplusplus
22032267
}
22042268
#endif

tests/test_pythoncapi_compat_cext.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,6 +2181,64 @@ test_config(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
21812181
#endif
21822182

21832183

2184+
static PyObject *
2185+
test_sys(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
2186+
{
2187+
const char *stdout_str = "stdout";
2188+
PyObject *stdout_obj = create_string(stdout_str);
2189+
#if PYTHON3
2190+
PyObject *sys_stdout = PySys_GetObject(stdout_str); // borrowed ref
2191+
#else
2192+
PyObject *sys_stdout = PySys_GetObject((char*)stdout_str); // borrowed ref
2193+
#endif
2194+
const char *nonexistent_str = "nonexistent";
2195+
PyObject *nonexistent_obj = create_string(nonexistent_str);
2196+
PyObject *value;
2197+
2198+
// get sys.stdout
2199+
value = PySys_GetAttr(stdout_obj);
2200+
assert(value == sys_stdout);
2201+
Py_DECREF(value);
2202+
2203+
value = PySys_GetAttrString(stdout_str);
2204+
assert(value == sys_stdout);
2205+
Py_DECREF(value);
2206+
2207+
value = UNINITIALIZED_OBJ;
2208+
assert(PySys_GetOptionalAttr(stdout_obj, &value) == 1);
2209+
assert(value == sys_stdout);
2210+
Py_DECREF(value);
2211+
2212+
value = UNINITIALIZED_OBJ;
2213+
assert(PySys_GetOptionalAttrString(stdout_str, &value) == 1);
2214+
assert(value == sys_stdout);
2215+
Py_DECREF(value);
2216+
2217+
// non existent attribute
2218+
value = PySys_GetAttr(nonexistent_obj);
2219+
assert(value == NULL);
2220+
assert(PyErr_ExceptionMatches(PyExc_RuntimeError));
2221+
PyErr_Clear();
2222+
2223+
value = PySys_GetAttrString(nonexistent_str);
2224+
assert(value == NULL);
2225+
assert(PyErr_ExceptionMatches(PyExc_RuntimeError));
2226+
PyErr_Clear();
2227+
2228+
value = UNINITIALIZED_OBJ;
2229+
assert(PySys_GetOptionalAttr(nonexistent_obj, &value) == 0);
2230+
assert(value == NULL);
2231+
2232+
value = UNINITIALIZED_OBJ;
2233+
assert(PySys_GetOptionalAttrString(nonexistent_str, &value) == 0);
2234+
assert(value == NULL);
2235+
2236+
Py_DECREF(stdout_obj);
2237+
Py_DECREF(nonexistent_obj);
2238+
Py_RETURN_NONE;
2239+
}
2240+
2241+
21842242
static struct PyMethodDef methods[] = {
21852243
{"test_object", test_object, METH_NOARGS, _Py_NULL},
21862244
{"test_py_is", test_py_is, METH_NOARGS, _Py_NULL},
@@ -2232,6 +2290,7 @@ static struct PyMethodDef methods[] = {
22322290
#if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
22332291
{"test_config", test_config, METH_NOARGS, _Py_NULL},
22342292
#endif
2293+
{"test_sys", test_sys, METH_NOARGS, _Py_NULL},
22352294
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
22362295
};
22372296

0 commit comments

Comments
 (0)