Skip to content

Commit fde4d34

Browse files
authored
Add PySys_GetAttr() function (#143)
1 parent ecf3cd4 commit fde4d34

File tree

4 files changed

+163
-0
lines changed

4 files changed

+163
-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: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,6 +2199,71 @@ 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+
*value = NULL;
2259+
return -1;
2260+
}
2261+
2262+
return PySys_GetOptionalAttrString(name_str, value);
2263+
}
2264+
#endif // PY_VERSION_HEX < 0x030F00A1
2265+
2266+
22022267
#ifdef __cplusplus
22032268
}
22042269
#endif

tests/test_pythoncapi_compat_cext.c

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,6 +2181,77 @@ 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 *error_obj = PyLong_FromLong(1);
2197+
PyObject *value;
2198+
2199+
// get sys.stdout
2200+
value = PySys_GetAttr(stdout_obj);
2201+
assert(value == sys_stdout);
2202+
Py_DECREF(value);
2203+
2204+
value = PySys_GetAttrString(stdout_str);
2205+
assert(value == sys_stdout);
2206+
Py_DECREF(value);
2207+
2208+
value = UNINITIALIZED_OBJ;
2209+
assert(PySys_GetOptionalAttr(stdout_obj, &value) == 1);
2210+
assert(value == sys_stdout);
2211+
Py_DECREF(value);
2212+
2213+
value = UNINITIALIZED_OBJ;
2214+
assert(PySys_GetOptionalAttrString(stdout_str, &value) == 1);
2215+
assert(value == sys_stdout);
2216+
Py_DECREF(value);
2217+
2218+
// non existent attribute
2219+
value = PySys_GetAttr(nonexistent_obj);
2220+
assert(value == NULL);
2221+
assert(PyErr_ExceptionMatches(PyExc_RuntimeError));
2222+
PyErr_Clear();
2223+
2224+
value = PySys_GetAttrString(nonexistent_str);
2225+
assert(value == NULL);
2226+
assert(PyErr_ExceptionMatches(PyExc_RuntimeError));
2227+
PyErr_Clear();
2228+
2229+
value = UNINITIALIZED_OBJ;
2230+
assert(PySys_GetOptionalAttr(nonexistent_obj, &value) == 0);
2231+
assert(value == NULL);
2232+
2233+
value = UNINITIALIZED_OBJ;
2234+
assert(PySys_GetOptionalAttrString(nonexistent_str, &value) == 0);
2235+
assert(value == NULL);
2236+
2237+
// invalid attribute type
2238+
value = PySys_GetAttr(error_obj);
2239+
assert(value == NULL);
2240+
assert(PyErr_ExceptionMatches(PyExc_TypeError));
2241+
PyErr_Clear();
2242+
2243+
value = UNINITIALIZED_OBJ;
2244+
assert(PySys_GetOptionalAttr(error_obj, &value) == -1);
2245+
assert(value == NULL);
2246+
assert(PyErr_ExceptionMatches(PyExc_TypeError));
2247+
PyErr_Clear();
2248+
2249+
Py_DECREF(stdout_obj);
2250+
Py_DECREF(nonexistent_obj);
2251+
Py_RETURN_NONE;
2252+
}
2253+
2254+
21842255
static struct PyMethodDef methods[] = {
21852256
{"test_object", test_object, METH_NOARGS, _Py_NULL},
21862257
{"test_py_is", test_py_is, METH_NOARGS, _Py_NULL},
@@ -2232,6 +2303,7 @@ static struct PyMethodDef methods[] = {
22322303
#if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
22332304
{"test_config", test_config, METH_NOARGS, _Py_NULL},
22342305
#endif
2306+
{"test_sys", test_sys, METH_NOARGS, _Py_NULL},
22352307
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
22362308
};
22372309

0 commit comments

Comments
 (0)