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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy