Skip to content

Commit 33b7909

Browse files
vstinnerpicnixz
andauthored
gh-107954, PEP 741: Add PyConfig_Get()/Set() functions (#123472)
Add PyConfig_Get(), PyConfig_GetInt(), PyConfig_Set() and PyConfig_Names() functions to get and set the current runtime Python configuration. Add visibility and "sys spec" to config and preconfig specifications. _PyConfig_AsDict() now converts PyConfig.xoptions as a dictionary. Co-authored-by: Bénédikt Tran <[email protected]>
1 parent db42934 commit 33b7909

19 files changed

+1466
-257
lines changed

Doc/c-api/init_config.rst

+69
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,75 @@ customized Python always running in isolated mode using
16051605
:c:func:`Py_RunMain`.
16061606
16071607
1608+
Runtime Python configuration API
1609+
================================
1610+
1611+
The configuration option *name* parameter must be a non-NULL null-terminated
1612+
UTF-8 encoded string.
1613+
1614+
Some options are read from the :mod:`sys` attributes. For example, the option
1615+
``"argv"`` is read from :data:`sys.argv`.
1616+
1617+
1618+
.. c:function:: PyObject* PyConfig_Get(const char *name)
1619+
1620+
Get the current runtime value of a configuration option as a Python object.
1621+
1622+
* Return a new reference on success.
1623+
* Set an exception and return ``NULL`` on error.
1624+
1625+
The object type depends on the configuration option. It can be:
1626+
1627+
* ``bool``
1628+
* ``int``
1629+
* ``str``
1630+
* ``list[str]``
1631+
* ``dict[str, str]``
1632+
1633+
The caller must hold the GIL. The function cannot be called before
1634+
Python initialization nor after Python finalization.
1635+
1636+
.. versionadded:: 3.14
1637+
1638+
1639+
.. c:function:: int PyConfig_GetInt(const char *name, int *value)
1640+
1641+
Similar to :c:func:`PyConfig_Get`, but get the value as a C int.
1642+
1643+
* Return ``0`` on success.
1644+
* Set an exception and return ``-1`` on error.
1645+
1646+
.. versionadded:: 3.14
1647+
1648+
1649+
.. c:function:: PyObject* PyConfig_Names(void)
1650+
1651+
Get all configuration option names as a ``frozenset``.
1652+
1653+
* Return a new reference on success.
1654+
* Set an exception and return ``NULL`` on error.
1655+
1656+
The caller must hold the GIL. The function cannot be called before
1657+
Python initialization nor after Python finalization.
1658+
1659+
.. versionadded:: 3.14
1660+
1661+
1662+
.. c:function:: int PyConfig_Set(const char *name, PyObject *value)
1663+
1664+
Set the current runtime value of a configuration option.
1665+
1666+
* Raise a :exc:`ValueError` if there is no option *name*.
1667+
* Raise a :exc:`ValueError` if *value* is an invalid value.
1668+
* Raise a :exc:`ValueError` if the option is read-only (cannot be set).
1669+
* Raise a :exc:`TypeError` if *value* has not the proper type.
1670+
1671+
The caller must hold the GIL. The function cannot be called before
1672+
Python initialization nor after Python finalization.
1673+
1674+
.. versionadded:: 3.14
1675+
1676+
16081677
Py_GetArgcArgv()
16091678
================
16101679

Doc/whatsnew/3.14.rst

+9
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,15 @@ New Features
492492
* Add :c:func:`Py_HashBuffer` to compute and return the hash value of a buffer.
493493
(Contributed by Antoine Pitrou and Victor Stinner in :gh:`122854`.)
494494

495+
* Add functions to get and set the current runtime Python configuration:
496+
497+
* :c:func:`PyConfig_Get`
498+
* :c:func:`PyConfig_GetInt`
499+
* :c:func:`PyConfig_Set`
500+
* :c:func:`PyConfig_Names`
501+
502+
(Contributed by Victor Stinner in :gh:`107954`.)
503+
495504

496505
Porting to Python 3.14
497506
----------------------

Include/cpython/initconfig.h

+8
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,14 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config,
260260
Py_ssize_t length, wchar_t **items);
261261

262262

263+
/* --- PyConfig_Get() ----------------------------------------- */
264+
265+
PyAPI_FUNC(PyObject*) PyConfig_Get(const char *name);
266+
PyAPI_FUNC(int) PyConfig_GetInt(const char *name, int *value);
267+
PyAPI_FUNC(PyObject*) PyConfig_Names(void);
268+
PyAPI_FUNC(int) PyConfig_Set(const char *name, PyObject *value);
269+
270+
263271
/* --- Helper functions --------------------------------------- */
264272

265273
/* Get the original command line arguments, before Python modified them.

Include/internal/pycore_initconfig.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ extern PyStatus _PyConfig_Write(const PyConfig *config,
181181
extern PyStatus _PyConfig_SetPyArgv(
182182
PyConfig *config,
183183
const _PyArgv *args);
184-
184+
extern PyObject* _PyConfig_CreateXOptionsDict(const PyConfig *config);
185185

186186
extern void _Py_DumpPathConfig(PyThreadState *tstate);
187187

Include/internal/pycore_sysmodule.h

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ extern int _PySys_SetAttr(PyObject *, PyObject *);
2929
extern int _PySys_ClearAttrString(PyInterpreterState *interp,
3030
const char *name, int verbose);
3131

32+
extern int _PySys_SetFlagObj(Py_ssize_t pos, PyObject *new_value);
33+
extern int _PySys_SetIntMaxStrDigits(int maxdigits);
34+
3235
#ifdef __cplusplus
3336
}
3437
#endif

Lib/test/_test_embed_set_config.py

+12-11
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ def test_set_invalid(self):
137137
'warnoptions',
138138
'module_search_paths',
139139
):
140-
value_tests.append((key, invalid_wstrlist))
140+
if key != 'xoptions':
141+
value_tests.append((key, invalid_wstrlist))
141142
type_tests.append((key, 123))
142143
type_tests.append((key, "abc"))
143144
type_tests.append((key, [123]))
@@ -160,14 +161,14 @@ def test_set_invalid(self):
160161
def test_flags(self):
161162
bool_options = set(BOOL_OPTIONS)
162163
for sys_attr, key, value in (
163-
("debug", "parser_debug", 1),
164-
("inspect", "inspect", 2),
165-
("interactive", "interactive", 3),
166-
("optimize", "optimization_level", 4),
167-
("verbose", "verbose", 1),
168-
("bytes_warning", "bytes_warning", 10),
169-
("quiet", "quiet", 11),
170-
("isolated", "isolated", 12),
164+
("debug", "parser_debug", 2),
165+
("inspect", "inspect", 3),
166+
("interactive", "interactive", 4),
167+
("optimize", "optimization_level", 5),
168+
("verbose", "verbose", 6),
169+
("bytes_warning", "bytes_warning", 7),
170+
("quiet", "quiet", 8),
171+
("isolated", "isolated", 9),
171172
):
172173
with self.subTest(sys=sys_attr, key=key, value=value):
173174
self.set_config(**{key: value, 'parse_argv': 0})
@@ -228,9 +229,9 @@ def test_options(self):
228229
self.check(warnoptions=[])
229230
self.check(warnoptions=["default", "ignore"])
230231

231-
self.set_config(xoptions=[])
232+
self.set_config(xoptions={})
232233
self.assertEqual(sys._xoptions, {})
233-
self.set_config(xoptions=["dev", "tracemalloc=5"])
234+
self.set_config(xoptions={"dev": True, "tracemalloc": "5"})
234235
self.assertEqual(sys._xoptions, {"dev": True, "tracemalloc": "5"})
235236

236237
def test_pathconfig(self):

0 commit comments

Comments
 (0)