Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-129367: Add PySys_GetAttr() function #129369

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Doc/c-api/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,24 @@ These are utility functions that make functionality from the :mod:`sys` module
accessible to C code. They all work with the current interpreter thread's
:mod:`sys` module's dict, which is contained in the internal thread state structure.

.. c:function:: PyObject *PySys_GetAttr(PyObject *name)

Return the object *name* from the :mod:`sys` module.

Return a new object on success. Set an exception and return ``NULL`` on
vstinner marked this conversation as resolved.
Show resolved Hide resolved
error.

.. versionadded:: next


.. c:function:: PyObject *PySys_GetAttrString(const char *name)

Similar to :c:func:`PySys_GetAttrString`, but *name* is an UTF-8 encoded
vstinner marked this conversation as resolved.
Show resolved Hide resolved
string.

.. versionadded:: next


.. c:function:: PyObject *PySys_GetObject(const char *name)

Return the object *name* from the :mod:`sys` module or ``NULL`` if it does
Expand Down
6 changes: 6 additions & 0 deletions Doc/data/refcounts.dat
Original file line number Diff line number Diff line change
Expand Up @@ -3052,3 +3052,9 @@ _Py_c_quot:Py_complex:divisor::
_Py_c_sum:Py_complex:::
_Py_c_sum:Py_complex:left::
_Py_c_sum:Py_complex:right::

PySys_GetAttr:PyObject*::+1:
PySys_GetAttr:PyObject*:name:0:

PySys_GetAttrString:PyObject*::+1:
PySys_GetAttrString:const char*:name::
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,12 @@ New features
bit-packing Python version numbers.
(Contributed by Petr Viktorin in :gh:`128629`.)

* Add :c:func:`PySys_GetAttr` and :c:func:`PySys_GetAttrString` functions to
get an attribute of the :mod:`sys` module. Compared to
:c:func:`PySys_GetObject`, they don't ignore errors and return a
:term:`strong reference`.
(Contributed by Victor Stinner in :gh:`129367`.)


Porting to Python 3.14
----------------------
Expand Down
6 changes: 6 additions & 0 deletions Include/cpython/sysmodule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef Py_CPYTHON_SYS_H
# error "this header file must not be included directly"
#endif

PyAPI_FUNC(PyObject *) PySys_GetAttr(PyObject *name);
PyAPI_FUNC(PyObject *) PySys_GetAttrString(const char *name);
6 changes: 6 additions & 0 deletions Include/sysmodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Py_DEPRECATED(3.13) PyAPI_FUNC(void) PySys_ResetWarnOptions(void);

PyAPI_FUNC(PyObject *) PySys_GetXOptions(void);

#ifndef Py_LIMITED_API
# define Py_CPYTHON_SYS_H
# include "cpython/sysmodule.h"
# undef Py_CPYTHON_SYS_H
#endif

#ifdef __cplusplus
}
#endif
Expand Down
50 changes: 40 additions & 10 deletions Lib/test/test_capi/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
from test import support
from test.support import import_helper

try:
import _testlimitedcapi
except ImportError:
_testlimitedcapi = None
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
_testcapi = import_helper.import_module('_testcapi')

NULL = None


class CAPITest(unittest.TestCase):
# TODO: Test the following functions:
#
Expand All @@ -19,15 +18,18 @@ class CAPITest(unittest.TestCase):

maxDiff = None

def check_sys_getattr_common(self, sys_getattr):
self.assertIs(sys_getattr('stdout'), sys.stdout)

with support.swap_attr(sys, '\U0001f40d', 42):
self.assertEqual(sys_getattr('\U0001f40d'), 42)

@support.cpython_only
@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_sys_getobject(self):
# Test PySys_GetObject()
getobject = _testlimitedcapi.sys_getobject

self.assertIs(getobject(b'stdout'), sys.stdout)
with support.swap_attr(sys, '\U0001f40d', 42):
self.assertEqual(getobject('\U0001f40d'.encode()), 42)
self.check_sys_getattr_common(getobject)

self.assertIs(getobject(b'nonexisting'), AttributeError)
with support.catch_unraisable_exception() as cm:
Expand All @@ -37,8 +39,37 @@ def test_sys_getobject(self):
"'utf-8' codec can't decode")
# CRASHES getobject(NULL)

def check_sys_getattr(self, sys_getattr):
self.check_sys_getattr_common(sys_getattr)

with self.assertRaises(AttributeError):
sys_getattr(b'nonexisting')

def test_sys_getattr(self):
# Test PySys_GetAttr()
sys_getattr = _testcapi.PySys_GetAttr

self.check_sys_getattr(sys_getattr)

with self.assertRaises(TypeError):
for invalid_name in (123, [], object()):
with self.assertRaises(AttributeError):
sys_getattr(invalid_name)

# CRASHES sys_getattr(NULL)

def test_sys_getattrstring(self):
# Test PySys_GetAttr()
sys_getattrstring = _testcapi.PySys_GetAttrString

self.check_sys_getattr(sys_getattrstring)

with self.assertRaises(UnicodeDecodeError):
self.assertIs(sys_getattrstring(b'\xff'), AttributeError)

# CRASHES sys_getattrstring(NULL)

@support.cpython_only
@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_sys_setobject(self):
# Test PySys_SetObject()
setobject = _testlimitedcapi.sys_setobject
Expand Down Expand Up @@ -70,7 +101,6 @@ def test_sys_setobject(self):
# CRASHES setobject(NULL, value)

@support.cpython_only
@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_sys_getxoptions(self):
# Test PySys_GetXOptions()
getxoptions = _testlimitedcapi.sys_getxoptions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add :c:func:`PySys_GetAttr` and :c:func:`PySys_GetAttrString` functions to get
an attribute of the :mod:`sys` module. Compared to :c:func:`PySys_GetObject`,
they don't ignore errors and return a :term:`strong reference`. Patch by Victor
Stinner.
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c _testcapi/sys.c
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
Expand Down
1 change: 1 addition & 0 deletions Modules/_testcapi/parts.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,6 @@ int _PyTestCapi_Init_Time(PyObject *module);
int _PyTestCapi_Init_Monitoring(PyObject *module);
int _PyTestCapi_Init_Object(PyObject *module);
int _PyTestCapi_Init_Config(PyObject *mod);
int _PyTestCapi_Init_Sys(PyObject *mod);

#endif // Py_TESTCAPI_PARTS_H
36 changes: 36 additions & 0 deletions Modules/_testcapi/sys.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include "parts.h"
#include "util.h"


static PyObject *
pysys_getattr(PyObject *self, PyObject *name)
{
NULLABLE(name);
return PySys_GetAttr(name);
}


static PyObject *
pysys_getattrstring(PyObject *self, PyObject *arg)
{
const char *name;
Py_ssize_t size;
if (!PyArg_Parse(arg, "z#", &name, &size)) {
return NULL;
}

return PySys_GetAttrString(name);
}


static PyMethodDef test_methods[] = {
{"PySys_GetAttr", pysys_getattr, METH_O},
{"PySys_GetAttrString", pysys_getattrstring, METH_O},
{NULL},
};

int
_PyTestCapi_Init_Sys(PyObject *m)
{
return PyModule_AddFunctions(m, test_methods);
}
3 changes: 3 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4401,6 +4401,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_Config(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_Sys(m) < 0) {
return NULL;
}

PyState_AddModule(m, &_testcapimodule);
return m;
Expand Down
1 change: 1 addition & 0 deletions PCbuild/_testcapi.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
<ClCompile Include="..\Modules\_testcapi\run.c" />
<ClCompile Include="..\Modules\_testcapi\monitoring.c" />
<ClCompile Include="..\Modules\_testcapi\config.c" />
<ClCompile Include="..\Modules\_testcapi\sys.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
Expand Down
3 changes: 3 additions & 0 deletions PCbuild/_testcapi.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@
<ClCompile Include="..\Modules\_testcapi\config.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Modules\_testcapi\sys.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc">
Expand Down
48 changes: 40 additions & 8 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,42 @@ _PySys_GetAttr(PyThreadState *tstate, PyObject *name)
return value;
}


PyObject*
PySys_GetAttr(PyObject *name)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
PyObject *sys_dict = interp->sysdict;
if (sys_dict == NULL) {
PyErr_SetString(PyExc_RuntimeError, "lost sys module");
return NULL;
}
PyObject *value;
if (PyDict_GetItemRef(sys_dict, name, &value) < 0) {
return NULL;
}
if (value == NULL) {
PyErr_Format(PyExc_AttributeError, "sys has no attribute %s", name);
return NULL;
}
return value;
}


PyObject*
PySys_GetAttrString(const char *name)
{
PyObject *name_obj = PyUnicode_FromString(name);
if (name_obj == NULL) {
return NULL;
}

PyObject *value = PySys_GetAttr(name_obj);
Py_DECREF(name_obj);
return value;
}


static PyObject *
_PySys_GetObject(PyInterpreterState *interp, const char *name)
{
Expand Down Expand Up @@ -3150,11 +3186,8 @@ sys_set_flag(PyObject *flags, Py_ssize_t pos, PyObject *value)
int
_PySys_SetFlagObj(Py_ssize_t pos, PyObject *value)
{
PyObject *flags = Py_XNewRef(PySys_GetObject("flags"));
PyObject *flags = PySys_GetAttrString("flags");
if (flags == NULL) {
if (!PyErr_Occurred()) {
PyErr_SetString(PyExc_RuntimeError, "lost sys.flags");
}
return -1;
}

Expand Down Expand Up @@ -3713,16 +3746,15 @@ _PySys_UpdateConfig(PyThreadState *tstate)
#undef COPY_WSTR

// sys.flags
PyObject *flags = _PySys_GetObject(interp, "flags"); // borrowed ref
PyObject *flags = PySys_GetAttrString("flags");
if (flags == NULL) {
if (!_PyErr_Occurred(tstate)) {
_PyErr_SetString(tstate, PyExc_RuntimeError, "lost sys.flags");
}
return -1;
}
if (set_flags_from_config(interp, flags) < 0) {
Py_DECREF(flags);
return -1;
}
Py_DECREF(flags);

SET_SYS("dont_write_bytecode", PyBool_FromLong(!config->write_bytecode));

Expand Down