Skip to content

Commit e8d143e

Browse files
committed
pythongh-107954: Add PyConfig_Get() function
Add PyConfig_Get() and PyConfig_GetInt() functions to get the current Python configuration. _PyConfig_AsDict() now converts PyConfig.xoptions as a dictionary.
1 parent 5c5022b commit e8d143e

File tree

12 files changed

+626
-174
lines changed

12 files changed

+626
-174
lines changed

Doc/c-api/init_config.rst

+47
Original file line numberDiff line numberDiff line change
@@ -1602,6 +1602,53 @@ customized Python always running in isolated mode using
16021602
:c:func:`Py_RunMain`.
16031603
16041604
1605+
Get the current Python configuration
1606+
====================================
1607+
1608+
Get a configuration option where *name* is the name of a :c:type:`PyConfig`
1609+
member.
1610+
1611+
Some options are read from the :mod:`sys` attributes. For example,
1612+
:c:func:`PyConfig_Get("argv", &value) <PyConfig_Get>` reads :data:`sys.argv`.
1613+
1614+
1615+
.. c:function:: int PyConfig_Get(const char *name, PyObject **value)
1616+
1617+
Get a configuration option as a Python object.
1618+
1619+
The object type depends on the configuration option. It can be:
1620+
1621+
* ``int``
1622+
* ``str``
1623+
* ``list[str]``
1624+
* ``dict[str, str]``
1625+
1626+
* Return ``0`` and set *\*value* on success.
1627+
* Raise an exception and return ``-1`` on error.
1628+
1629+
1630+
.. c:function:: int PyConfig_GetInt(const char *name, int *value)
1631+
1632+
Similar to :c:func:`PyConfig_Get`, but get the value as a C int.
1633+
1634+
1635+
Example
1636+
-------
1637+
1638+
Code::
1639+
1640+
int get_verbose(void)
1641+
{
1642+
int verbose;
1643+
if (PyConfig_GetInt("verbose", &verbose) < 0) {
1644+
// Silently ignore the error
1645+
PyErr_Clear();
1646+
return -1;
1647+
}
1648+
return verbose;
1649+
}
1650+
1651+
16051652
Py_GetArgcArgv()
16061653
================
16071654

Doc/whatsnew/3.13.rst

+6
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,12 @@ New Features
12301230
:exc:`KeyError` if the key missing.
12311231
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
12321232

1233+
* Add functions to get the current Python configuration:
1234+
1235+
* :c:func:`PyConfig_Get`
1236+
* :c:func:`PyConfig_GetInt`
1237+
1238+
(Contributed by Victor Stinner in :gh:`107954`.)
12331239

12341240
Porting to Python 3.13
12351241
----------------------

Include/cpython/initconfig.h

+18
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,24 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config,
257257
Py_ssize_t length, wchar_t **items);
258258

259259

260+
/* --- PyConfig_Get() ----------------------------------------- */
261+
262+
// Get a configuration option as a Python object.
263+
// Return a new reference on success.
264+
// Set an exception and return NULL on error.
265+
//
266+
// The object type depends on the configuration option. It can be:
267+
// int, str, list[str] and dict[str, str].
268+
PyAPI_FUNC(PyObject*) PyConfig_Get(const char *name);
269+
270+
// Get an integer configuration option.
271+
// Return 0 and set '*value' on success.
272+
// Raise an exception return -1 on error.
273+
PyAPI_FUNC(int) PyConfig_GetInt(
274+
const char *name,
275+
int *value);
276+
277+
260278
/* --- Helper functions --------------------------------------- */
261279

262280
/* 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
@@ -169,7 +169,7 @@ extern PyStatus _PyConfig_Write(const PyConfig *config,
169169
extern PyStatus _PyConfig_SetPyArgv(
170170
PyConfig *config,
171171
const _PyArgv *args);
172-
172+
extern PyObject* _PyConfig_CreateXOptionsDict(const PyConfig *config);
173173

174174
extern void _Py_DumpPathConfig(PyThreadState *tstate);
175175

Lib/test/test_capi/test_config.py

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""
2+
Tests on PyConfig API (PEP 587).
3+
"""
4+
import os
5+
import sys
6+
import unittest
7+
from test import support
8+
try:
9+
import _testcapi
10+
except ImportError:
11+
_testcapi = None
12+
13+
14+
@unittest.skipIf(_testcapi is None, 'need _testcapi')
15+
class CAPITests(unittest.TestCase):
16+
def check_config_get(self, get_func):
17+
# write_bytecode is read from sys.dont_write_bytecode as int
18+
with support.swap_attr(sys, "dont_write_bytecode", 0):
19+
self.assertEqual(get_func('write_bytecode'), 1)
20+
with support.swap_attr(sys, "dont_write_bytecode", "yes"):
21+
self.assertEqual(get_func('write_bytecode'), 0)
22+
with support.swap_attr(sys, "dont_write_bytecode", []):
23+
self.assertEqual(get_func('write_bytecode'), 1)
24+
25+
# non-existent config option name
26+
NONEXISTENT_KEY = 'NONEXISTENT_KEY'
27+
err_msg = f'unknown config option name: {NONEXISTENT_KEY}'
28+
with self.assertRaisesRegex(ValueError, err_msg):
29+
get_func('NONEXISTENT_KEY')
30+
31+
def test_config_get(self):
32+
config_get = _testcapi.config_get
33+
34+
self.check_config_get(config_get)
35+
36+
for name, config_type, expected in (
37+
('verbose', int, sys.flags.verbose), # PyConfig_MEMBER_INT
38+
('isolated', int, sys.flags.isolated), # PyConfig_MEMBER_UINT
39+
('platlibdir', str, sys.platlibdir), # PyConfig_MEMBER_WSTR
40+
('argv', list, sys.argv), # PyConfig_MEMBER_WSTR_LIST
41+
('xoptions', dict, sys._xoptions), # xoptions dict
42+
):
43+
with self.subTest(name=name):
44+
value = config_get(name)
45+
self.assertEqual(type(value), config_type)
46+
self.assertEqual(value, expected)
47+
48+
# PyConfig_MEMBER_ULONG type
49+
hash_seed = config_get('hash_seed')
50+
self.assertIsInstance(hash_seed, int)
51+
self.assertGreaterEqual(hash_seed, 0)
52+
53+
# PyConfig_MEMBER_WSTR_OPT type
54+
if 'PYTHONDUMPREFSFILE' not in os.environ:
55+
self.assertIsNone(config_get('dump_refs_file'))
56+
57+
# attributes read from sys
58+
value_str = "TEST_MARKER_STR"
59+
value_list = ["TEST_MARKER_STRLIST"]
60+
value_dict = {"x": "value", "y": True}
61+
for name, sys_name, value in (
62+
("base_exec_prefix", None, value_str),
63+
("base_prefix", None, value_str),
64+
("exec_prefix", None, value_str),
65+
("executable", None, value_str),
66+
("platlibdir", None, value_str),
67+
("prefix", None, value_str),
68+
("pycache_prefix", None, value_str),
69+
("base_executable", "_base_executable", value_str),
70+
("stdlib_dir", "_stdlib_dir", value_str),
71+
("argv", None, value_list),
72+
("orig_argv", None, value_list),
73+
("warnoptions", None, value_list),
74+
("module_search_paths", "path", value_list),
75+
("xoptions", "_xoptions", value_dict),
76+
):
77+
with self.subTest(name=name):
78+
if sys_name is None:
79+
sys_name = name
80+
with support.swap_attr(sys, sys_name, value):
81+
self.assertEqual(config_get(name), value)
82+
83+
def test_config_getint(self):
84+
config_getint = _testcapi.config_getint
85+
86+
self.check_config_get(config_getint)
87+
88+
# PyConfig_MEMBER_INT type
89+
self.assertEqual(config_getint('verbose'), sys.flags.verbose)
90+
91+
# PyConfig_MEMBER_UINT type
92+
self.assertEqual(config_getint('isolated'), sys.flags.isolated)
93+
94+
# PyConfig_MEMBER_ULONG type
95+
hash_seed = config_getint('hash_seed')
96+
self.assertIsInstance(hash_seed, int)
97+
self.assertGreaterEqual(hash_seed, 0)
98+
99+
# platlibdir is a str
100+
with self.assertRaises(TypeError):
101+
config_getint('platlibdir')
102+
103+
104+
if __name__ == "__main__":
105+
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Add functions to get the current Python configuration:
2+
3+
* :c:func:`PyConfig_Get`
4+
* :c:func:`PyConfig_GetInt`
5+
6+
Patch by Victor Stinner.

Modules/Setup.stdlib.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
160160
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
161161
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
162-
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.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/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c
162+
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.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/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c _testcapi/config.c
163163
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
164164
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
165165

Modules/_testcapi/config.c

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include "parts.h"
2+
3+
4+
static PyObject *
5+
_testcapi_config_get(PyObject *module, PyObject *name_obj)
6+
{
7+
const char *name;
8+
if (PyArg_Parse(name_obj, "s", &name) < 0) {
9+
return NULL;
10+
}
11+
12+
return PyConfig_Get(name);
13+
}
14+
15+
16+
static PyObject *
17+
_testcapi_config_getint(PyObject *module, PyObject *name_obj)
18+
{
19+
const char *name;
20+
if (PyArg_Parse(name_obj, "s", &name) < 0) {
21+
return NULL;
22+
}
23+
24+
int value;
25+
if (PyConfig_GetInt(name, &value) < 0) {
26+
return NULL;
27+
}
28+
return PyLong_FromLong(value);
29+
}
30+
31+
32+
static PyMethodDef test_methods[] = {
33+
{"config_get", _testcapi_config_get, METH_O},
34+
{"config_getint", _testcapi_config_getint, METH_O},
35+
{NULL}
36+
};
37+
38+
int _PyTestCapi_Init_Config(PyObject *mod)
39+
{
40+
if (PyModule_AddFunctions(mod, test_methods) < 0) {
41+
return -1;
42+
}
43+
44+
return 0;
45+
}

Modules/_testcapi/parts.h

+1
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,6 @@ int _PyTestCapi_Init_Hash(PyObject *module);
6262

6363
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
6464
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);
65+
int _PyTestCapi_Init_Config(PyObject *mod);
6566

6667
#endif // Py_TESTCAPI_PARTS_H

Modules/_testcapimodule.c

+3
Original file line numberDiff line numberDiff line change
@@ -4005,6 +4005,9 @@ PyInit__testcapi(void)
40054005
if (_PyTestCapi_Init_Hash(m) < 0) {
40064006
return NULL;
40074007
}
4008+
if (_PyTestCapi_Init_Config(m) < 0) {
4009+
return NULL;
4010+
}
40084011

40094012
PyState_AddModule(m, &_testcapimodule);
40104013
return m;

0 commit comments

Comments
 (0)