Skip to content

gh-108512: Add and use new replacements for PySys_GetObject() (alt) #129736

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2098,7 +2098,7 @@ initialization::

/* Specify sys.path explicitly */
/* If you want to modify the default set of paths, finish
initialization first and then use PySys_GetObject("path") */
initialization first and then use PySys_GetAttrString("path", ...) */
config.module_search_paths_set = 1;
status = PyWideStringList_Append(&config.module_search_paths,
L"/path/to/stdlib");
Expand Down
25 changes: 25 additions & 0 deletions Doc/c-api/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,36 @@ 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:: int PySys_GetAttr(PyObject *name, PyObject **result);

Get the attribute *name* of the :mod:`sys` module.

If the object exists, set *\*result* to a new :term:`strong reference`
to the object and return ``1``.
If the object does not exist, set *\*result* to ``NULL`` and return ``0``,
without setting an exception.
If other error occurred, set an exception, set *\*result* to ``NULL`` and
return ``-1``.

.. versionadded:: next

.. c:function:: int PySys_GetAttrString(const char *name, PyObject **result);

This is the same as :c:func:`PySys_GetAttr`, but *name* is
specified as a :c:expr:`const char*` UTF-8 encoded bytes string,
rather than a :c:expr:`PyObject*`.

.. versionadded:: next

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

Return the object *name* from the :mod:`sys` module or ``NULL`` if it does
not exist, without setting an exception.

Preserves exception that was set before the call.

It is recommended to use :c:func:`PySys_GetAttrString` instead.

.. c:function:: int PySys_SetObject(const char *name, PyObject *v)

Set *name* in the :mod:`sys` module to *v* unless *v* is ``NULL``, in which
Expand Down
2 changes: 2 additions & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1491,6 +1491,10 @@ New features

(Contributed by Sergey B Kirpichev and Victor Stinner in :gh:`102471`.)

* Add :c:func:`PySys_GetAttr` and :c:func:`PySys_GetAttrString`
functions as replacements for :c:func:`PySys_GetObject`.
(Contributed by Serhiy Storchaka in :gh:`108512`.)

* Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier
superclass identification, which attempts to resolve the `type checking issue
<https://peps.python.org/pep-0630/#type-checking>`__ mentioned in :pep:`630`
Expand Down
2 changes: 0 additions & 2 deletions Include/internal/pycore_sysmodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

PyAPI_FUNC(int) _PySys_GetOptionalAttr(PyObject *, PyObject **);
PyAPI_FUNC(int) _PySys_GetOptionalAttrString(const char *, PyObject **);
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttr(PyObject *);
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttrString(const char *);

Expand Down
4 changes: 4 additions & 0 deletions Include/sysmodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
extern "C" {
#endif

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
PyAPI_FUNC(int) PySys_GetAttr(PyObject *, PyObject **);
PyAPI_FUNC(int) PySys_GetAttrString(const char *, PyObject **);
#endif
PyAPI_FUNC(PyObject *) PySys_GetObject(const char *);
PyAPI_FUNC(int) PySys_SetObject(const char *, PyObject *);

Expand Down
32 changes: 32 additions & 0 deletions Lib/test/test_capi/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
except ImportError:
_testlimitedcapi = None

try:
import _testinternalcapi
except ImportError:
_testinternalcapi = None

NULL = None

class CAPITest(unittest.TestCase):
Expand All @@ -19,6 +24,33 @@ class CAPITest(unittest.TestCase):

maxDiff = None

@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_sys_getattr(self):
# Test PySys_GetAttr()
sys_getattr = _testlimitedcapi.sys_getattr

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

self.assertIs(sys_getattr('nonexisting'), AttributeError)
self.assertRaises(TypeError, sys_getattr, 1)
self.assertRaises(TypeError, sys_getattr, [])
# CRASHES sys_getattr(NULL)

@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_sys_getattrstring(self):
# Test PySys_GetAttrString()
getattrstring = _testlimitedcapi.sys_getattrstring

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

self.assertIs(getattrstring(b'nonexisting'), AttributeError)
self.assertRaises(UnicodeDecodeError, getattrstring, b'\xff')
# CRASHES getattrstring(NULL)

@support.cpython_only
@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_sys_getobject(self):
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add functions :c:func:`PySys_GetAttr` and :c:func:`PySys_GetAttrString`.
4 changes: 4 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2545,3 +2545,7 @@
added = '3.14'
[function.Py_PACK_VERSION]
added = '3.14'
[function.PySys_GetAttr]
added = '3.14'
[function.PySys_GetAttrString]
added = '3.14'
4 changes: 2 additions & 2 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ static const char PyCursesVersion[] = "2.2";
#include "pycore_capsule.h" // _PyCapsule_SetTraverse()
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_structseq.h" // _PyStructSequence_NewType()
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
#include "pycore_sysmodule.h" // PySys_GetAttrString()

#ifdef __hpux
#define STRICT_SYSV_CURSES
Expand Down Expand Up @@ -3543,7 +3543,7 @@ _curses_setupterm_impl(PyObject *module, const char *term, int fd)
if (fd == -1) {
PyObject* sys_stdout;

if (_PySys_GetOptionalAttrString("stdout", &sys_stdout) < 0) {
if (PySys_GetAttrString("stdout", &sys_stdout) < 0) {
return NULL;
}

Expand Down
7 changes: 4 additions & 3 deletions Modules/_lsprof.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_ceval.h" // _PyEval_SetProfile()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_sysmodule.h" // _PySys_GetRequiredAttrString()
#include "pycore_time.h" // _PyTime_FromLong()

#include "rotatingtree.h"
Expand Down Expand Up @@ -779,7 +780,7 @@ _lsprof_Profiler_enable_impl(ProfilerObject *self, int subcalls,
return NULL;
}

PyObject* monitoring = PyImport_ImportModuleAttrString("sys", "monitoring");
PyObject* monitoring = _PySys_GetRequiredAttrString("monitoring");
if (!monitoring) {
return NULL;
}
Expand Down Expand Up @@ -861,7 +862,7 @@ _lsprof_Profiler_disable_impl(ProfilerObject *self)
}
if (self->flags & POF_ENABLED) {
PyObject* result = NULL;
PyObject* monitoring = PyImport_ImportModuleAttrString("sys", "monitoring");
PyObject* monitoring = _PySys_GetRequiredAttrString("monitoring");

if (!monitoring) {
return NULL;
Expand Down Expand Up @@ -980,7 +981,7 @@ profiler_init_impl(ProfilerObject *self, PyObject *timer, double timeunit,
Py_XSETREF(self->externalTimer, Py_XNewRef(timer));
self->tool_id = PY_MONITORING_PROFILER_ID;

PyObject* monitoring = PyImport_ImportModuleAttrString("sys", "monitoring");
PyObject* monitoring = _PySys_GetRequiredAttrString("monitoring");
if (!monitoring) {
return -1;
}
Expand Down
53 changes: 53 additions & 0 deletions Modules/_testlimitedcapi/sys.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,58 @@
#include "pyconfig.h" // Py_GIL_DISABLED
// Need limited C API version 3.14 for PySys_GetAttr() etc
#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API)
# define Py_LIMITED_API 0x030e0000
#endif
#include "parts.h"
#include "util.h"


static PyObject *
sys_getattr(PyObject *Py_UNUSED(module), PyObject *name)
{
PyObject *value = UNINITIALIZED_PTR;
NULLABLE(name);

switch (PySys_GetAttr(name, &value)) {
case -1:
assert(value == NULL);
assert(PyErr_Occurred());
return NULL;
case 0:
assert(value == NULL);
return Py_NewRef(PyExc_AttributeError);
case 1:
return value;
default:
Py_FatalError("PySys_GetAttr() returned invalid code");
}
}

static PyObject *
sys_getattrstring(PyObject *Py_UNUSED(module), PyObject *arg)
{
PyObject *value = UNINITIALIZED_PTR;
const char *name;
Py_ssize_t size;
if (!PyArg_Parse(arg, "z#", &name, &size)) {
return NULL;
}

switch (PySys_GetAttrString(name, &value)) {
case -1:
assert(value == NULL);
assert(PyErr_Occurred());
return NULL;
case 0:
assert(value == NULL);
return Py_NewRef(PyExc_AttributeError);
case 1:
return value;
default:
Py_FatalError("PySys_GetAttrString() returned invalid code");
}
}

static PyObject *
sys_getobject(PyObject *Py_UNUSED(module), PyObject *arg)
{
Expand Down Expand Up @@ -39,6 +90,8 @@ sys_getxoptions(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(ignored))


static PyMethodDef test_methods[] = {
{"sys_getattr", sys_getattr, METH_O},
{"sys_getattrstring", sys_getattrstring, METH_O},
{"sys_getobject", sys_getobject, METH_O},
{"sys_setobject", sys_setobject, METH_VARARGS},
{"sys_getxoptions", sys_getxoptions, METH_NOARGS},
Expand Down
3 changes: 1 addition & 2 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
#include "pycore_pylifecycle.h"
#include "pycore_pystate.h" // _PyThreadState_SetCurrent()
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttr()
#include "pycore_time.h" // _PyTime_FromSeconds()
#include "pycore_weakref.h" // _PyWeakref_GET_REF()

Expand Down Expand Up @@ -2250,7 +2249,7 @@ thread_excepthook(PyObject *module, PyObject *args)
PyObject *thread = PyStructSequence_GET_ITEM(args, 3);

PyObject *file;
if (_PySys_GetOptionalAttr( &_Py_ID(stderr), &file) < 0) {
if (PySys_GetAttr( &_Py_ID(stderr), &file) < 0) {
return NULL;
}
if (file == NULL || file == Py_None) {
Expand Down
5 changes: 2 additions & 3 deletions Modules/_tkinter.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ Copyright (C) 1994 Steen Lumholt.
#endif

#include "pycore_long.h" // _PyLong_IsNegative()
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()

#ifdef MS_WINDOWS
# include <windows.h>
Expand Down Expand Up @@ -145,7 +144,7 @@ _get_tcl_lib_path(void)
int stat_return_value;
PyObject *prefix;

(void) _PySys_GetOptionalAttrString("base_prefix", &prefix);
(void) PySys_GetAttrString("base_prefix", &prefix);
if (prefix == NULL) {
return NULL;
}
Expand Down Expand Up @@ -3520,7 +3519,7 @@ PyInit__tkinter(void)

/* This helps the dynamic loader; in Unicode aware Tcl versions
it also helps Tcl find its encodings. */
(void) _PySys_GetOptionalAttrString("executable", &uexe);
(void) PySys_GetAttrString("executable", &uexe);
if (uexe && PyUnicode_Check(uexe)) { // sys.executable can be None
cexe = PyUnicode_EncodeFSDefault(uexe);
Py_DECREF(uexe);
Expand Down
13 changes: 5 additions & 8 deletions Modules/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -481,16 +481,13 @@ pymain_run_startup(PyConfig *config, int *exitcode)
static int
pymain_run_interactive_hook(int *exitcode)
{
PyObject *hook = PyImport_ImportModuleAttrString("sys",
"__interactivehook__");
if (hook == NULL) {
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
// no sys.__interactivehook__ attribute
PyErr_Clear();
return 0;
}
PyObject *hook;
if (PySys_GetAttrString("__interactivehook__", &hook) < 0) {
goto error;
}
if (hook == NULL) {
return 0;
}

if (PySys_Audit("cpython.run_interactivehook", "O", hook) < 0) {
goto error;
Expand Down
3 changes: 1 addition & 2 deletions Modules/syslogmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ Revision history:

#include "Python.h"
#include "osdefs.h" // SEP
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()

#include <syslog.h>

Expand Down Expand Up @@ -92,7 +91,7 @@ syslog_get_argv(void)
Py_ssize_t slash;
PyObject *argv;

if (_PySys_GetOptionalAttrString("argv", &argv) <= 0) {
if (PySys_GetAttrString("argv", &argv) <= 0) {
return NULL;
}

Expand Down
4 changes: 2 additions & 2 deletions Objects/moduleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#include "pycore_object.h" // _PyType_AllocNoTrack
#include "pycore_pyerrors.h" // _PyErr_FormatFromCause()
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
#include "pycore_sysmodule.h" // PySys_GetAttrString()

#include "osdefs.h" // MAXPATHLEN

Expand Down Expand Up @@ -1005,7 +1005,7 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
int is_possibly_shadowing_stdlib = 0;
if (is_possibly_shadowing) {
PyObject *stdlib_modules;
if (_PySys_GetOptionalAttrString("stdlib_module_names", &stdlib_modules) < 0) {
if (PySys_GetAttrString("stdlib_module_names", &stdlib_modules) < 0) {
goto done;
}
if (stdlib_modules && PyAnySet_Check(stdlib_modules)) {
Expand Down
2 changes: 2 additions & 0 deletions PC/python3dll.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Python/_warnings.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#include "pycore_pyerrors.h" // _PyErr_Occurred()
#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttr()
#include "pycore_traceback.h" // _Py_DisplaySourceLine()

#include <stdbool.h>
Expand Down Expand Up @@ -563,7 +562,7 @@ show_warning(PyThreadState *tstate, PyObject *filename, int lineno,
goto error;
}

if (_PySys_GetOptionalAttr(&_Py_ID(stderr), &f_stderr) <= 0) {
if (PySys_GetAttr(&_Py_ID(stderr), &f_stderr) <= 0) {
fprintf(stderr, "lost sys.stderr\n");
goto error;
}
Expand Down
Loading
Loading