Skip to content

Commit bac3fcb

Browse files
gh-108512: Add and use new replacements for PySys_GetObject() (GH-111035)
Add functions PySys_GetAttr(), PySys_GetAttrString(), PySys_GetOptionalAttr() and PySys_GetOptionalAttrString().
1 parent b265a7d commit bac3fcb

32 files changed

+287
-93
lines changed

Doc/c-api/init_config.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2111,7 +2111,7 @@ initialization::
21112111
21122112
/* Specify sys.path explicitly */
21132113
/* If you want to modify the default set of paths, finish
2114-
initialization first and then use PySys_GetObject("path") */
2114+
initialization first and then use PySys_GetAttrString("path") */
21152115
config.module_search_paths_set = 1;
21162116
status = PyWideStringList_Append(&config.module_search_paths,
21172117
L"/path/to/stdlib");

Doc/c-api/sys.rst

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,57 @@ These are utility functions that make functionality from the :mod:`sys` module
258258
accessible to C code. They all work with the current interpreter thread's
259259
:mod:`sys` module's dict, which is contained in the internal thread state structure.
260260
261+
.. c:function:: PyObject *PySys_GetAttr(PyObject *name)
262+
263+
Get the attribute *name* of the :mod:`sys` module.
264+
Return a :term:`strong reference`.
265+
Raise :exc:`RuntimeError` and return ``NULL`` if it does not exist or
266+
if the :mod:`sys` module cannot be found.
267+
268+
If the non-existing object should not be treated as a failure, you can use
269+
:c:func:`PySys_GetOptionalAttr` instead.
270+
271+
.. versionadded:: next
272+
273+
.. c:function:: PyObject *PySys_GetAttrString(const char *name)
274+
275+
This is the same as :c:func:`PySys_GetAttr`, but *name* is
276+
specified as a :c:expr:`const char*` UTF-8 encoded bytes string,
277+
rather than a :c:expr:`PyObject*`.
278+
279+
If the non-existing object should not be treated as a failure, you can use
280+
:c:func:`PySys_GetOptionalAttrString` instead.
281+
282+
.. versionadded:: next
283+
284+
.. c:function:: int PySys_GetOptionalAttr(PyObject *name, PyObject **result)
285+
286+
Variant of :c:func:`PySys_GetAttr` which doesn't raise
287+
exception if the object does not exist.
288+
289+
* Set *\*result* to a new :term:`strong reference` to the object and
290+
return ``1`` if the object exists.
291+
* Set *\*result* to ``NULL`` and return ``0`` without setting an exception
292+
if the object does not exist.
293+
* Set an exception, set *\*result* to ``NULL``, and return ``-1``,
294+
if an error occurred.
295+
296+
.. versionadded:: next
297+
298+
.. c:function:: int PySys_GetOptionalAttrString(const char *name, PyObject **result)
299+
300+
This is the same as :c:func:`PySys_GetOptionalAttr`, but *name* is
301+
specified as a :c:expr:`const char*` UTF-8 encoded bytes string,
302+
rather than a :c:expr:`PyObject*`.
303+
304+
.. versionadded:: next
305+
261306
.. c:function:: PyObject *PySys_GetObject(const char *name)
262307
263-
Return the object *name* from the :mod:`sys` module or ``NULL`` if it does
264-
not exist, without setting an exception.
308+
Similar to :c:func:`PySys_GetAttrString`, but return a :term:`borrowed
309+
reference` and return ``NULL`` *without* setting exception on failure.
310+
311+
Preserves exception that was set before the call.
265312
266313
.. c:function:: int PySys_SetObject(const char *name, PyObject *v)
267314

Doc/data/stable_abi.dat

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.15.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,10 @@ C API changes
213213
New features
214214
------------
215215

216-
* TODO
216+
* Add :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`,
217+
:c:func:`PySys_GetOptionalAttr`, and :c:func:`PySys_GetOptionalAttrString`
218+
functions as replacements for :c:func:`PySys_GetObject`.
219+
(Contributed by Serhiy Storchaka in :gh:`108512`.)
217220

218221
Porting to Python 3.15
219222
----------------------

Include/internal/pycore_sysmodule.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11-
PyAPI_FUNC(int) _PySys_GetOptionalAttr(PyObject *, PyObject **);
12-
PyAPI_FUNC(int) _PySys_GetOptionalAttrString(const char *, PyObject **);
13-
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttr(PyObject *);
14-
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttrString(const char *);
15-
1611
// Export for '_pickle' shared extension
1712
PyAPI_FUNC(size_t) _PySys_GetSizeOf(PyObject *);
1813

Include/sysmodule.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
extern "C" {
55
#endif
66

7+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030f0000
8+
PyAPI_FUNC(PyObject *) PySys_GetAttr(PyObject *);
9+
PyAPI_FUNC(PyObject *) PySys_GetAttrString(const char *);
10+
PyAPI_FUNC(int) PySys_GetOptionalAttr(PyObject *, PyObject **);
11+
PyAPI_FUNC(int) PySys_GetOptionalAttrString(const char *, PyObject **);
12+
#endif
713
PyAPI_FUNC(PyObject *) PySys_GetObject(const char *);
814
PyAPI_FUNC(int) PySys_SetObject(const char *, PyObject *);
915

Lib/test/test_capi/test_sys.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,68 @@ class CAPITest(unittest.TestCase):
1919

2020
maxDiff = None
2121

22+
@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
23+
def test_sys_getattr(self):
24+
# Test PySys_GetAttr()
25+
sys_getattr = _testlimitedcapi.sys_getattr
26+
27+
self.assertIs(sys_getattr('stdout'), sys.stdout)
28+
with support.swap_attr(sys, '\U0001f40d', 42):
29+
self.assertEqual(sys_getattr('\U0001f40d'), 42)
30+
31+
with self.assertRaisesRegex(RuntimeError, r'lost sys\.nonexistent'):
32+
sys_getattr('nonexistent')
33+
with self.assertRaisesRegex(RuntimeError, r'lost sys\.\U0001f40d'):
34+
sys_getattr('\U0001f40d')
35+
self.assertRaises(TypeError, sys_getattr, 1)
36+
self.assertRaises(TypeError, sys_getattr, [])
37+
# CRASHES sys_getattr(NULL)
38+
39+
@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
40+
def test_sys_getattrstring(self):
41+
# Test PySys_GetAttrString()
42+
getattrstring = _testlimitedcapi.sys_getattrstring
43+
44+
self.assertIs(getattrstring(b'stdout'), sys.stdout)
45+
with support.swap_attr(sys, '\U0001f40d', 42):
46+
self.assertEqual(getattrstring('\U0001f40d'.encode()), 42)
47+
48+
with self.assertRaisesRegex(RuntimeError, r'lost sys\.nonexistent'):
49+
getattrstring(b'nonexistent')
50+
with self.assertRaisesRegex(RuntimeError, r'lost sys\.\U0001f40d'):
51+
getattrstring('\U0001f40d'.encode())
52+
self.assertRaises(UnicodeDecodeError, getattrstring, b'\xff')
53+
# CRASHES getattrstring(NULL)
54+
55+
@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
56+
def test_sys_getoptionalattr(self):
57+
# Test PySys_GetOptionalAttr()
58+
getoptionalattr = _testlimitedcapi.sys_getoptionalattr
59+
60+
self.assertIs(getoptionalattr('stdout'), sys.stdout)
61+
with support.swap_attr(sys, '\U0001f40d', 42):
62+
self.assertEqual(getoptionalattr('\U0001f40d'), 42)
63+
64+
self.assertIs(getoptionalattr('nonexistent'), AttributeError)
65+
self.assertIs(getoptionalattr('\U0001f40d'), AttributeError)
66+
self.assertRaises(TypeError, getoptionalattr, 1)
67+
self.assertRaises(TypeError, getoptionalattr, [])
68+
# CRASHES getoptionalattr(NULL)
69+
70+
@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
71+
def test_sys_getoptionalattrstring(self):
72+
# Test PySys_GetOptionalAttrString()
73+
getoptionalattrstring = _testlimitedcapi.sys_getoptionalattrstring
74+
75+
self.assertIs(getoptionalattrstring(b'stdout'), sys.stdout)
76+
with support.swap_attr(sys, '\U0001f40d', 42):
77+
self.assertEqual(getoptionalattrstring('\U0001f40d'.encode()), 42)
78+
79+
self.assertIs(getoptionalattrstring(b'nonexistent'), AttributeError)
80+
self.assertIs(getoptionalattrstring('\U0001f40d'.encode()), AttributeError)
81+
self.assertRaises(UnicodeDecodeError, getoptionalattrstring, b'\xff')
82+
# CRASHES getoptionalattrstring(NULL)
83+
2284
@support.cpython_only
2385
@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
2486
def test_sys_getobject(self):
@@ -29,7 +91,7 @@ def test_sys_getobject(self):
2991
with support.swap_attr(sys, '\U0001f40d', 42):
3092
self.assertEqual(getobject('\U0001f40d'.encode()), 42)
3193

32-
self.assertIs(getobject(b'nonexisting'), AttributeError)
94+
self.assertIs(getobject(b'nonexistent'), AttributeError)
3395
with support.catch_unraisable_exception() as cm:
3496
self.assertIs(getobject(b'\xff'), AttributeError)
3597
self.assertEqual(cm.unraisable.exc_type, UnicodeDecodeError)

Lib/test/test_stable_abi_ctypes.py

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add functions :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`,
2+
:c:func:`PySys_GetOptionalAttr` and :c:func:`PySys_GetOptionalAttrString`.

Misc/stable_abi.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2575,3 +2575,11 @@
25752575
added = '3.14'
25762576
[function.Py_PACK_VERSION]
25772577
added = '3.14'
2578+
[function.PySys_GetAttr]
2579+
added = '3.15'
2580+
[function.PySys_GetAttrString]
2581+
added = '3.15'
2582+
[function.PySys_GetOptionalAttr]
2583+
added = '3.15'
2584+
[function.PySys_GetOptionalAttrString]
2585+
added = '3.15'

0 commit comments

Comments
 (0)