Skip to content

Commit fba1c0f

Browse files
committed
pythongh-107954: Add PyConfig_Get() function
1 parent 5c5022b commit fba1c0f

File tree

10 files changed

+556
-133
lines changed

10 files changed

+556
-133
lines changed

Doc/c-api/init_config.rst

+46
Original file line numberDiff line numberDiff line change
@@ -1602,6 +1602,52 @@ 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+
.. c:function:: int PyConfig_Get(const char *name, PyObject **value)
1612+
1613+
Get a configuration option as a Python object.
1614+
1615+
The object type depends on the configuration option. It can be:
1616+
1617+
* ``int``
1618+
* ``str``
1619+
* ``list[str]``
1620+
* ``dict[str, str]``
1621+
1622+
* Return ``0`` and set *\*value* to a Python list of strings on success.
1623+
* Raise an exception and return ``-1`` on error.
1624+
1625+
1626+
.. c:function:: int PyConfig_GetInt(const char *name, int *value)
1627+
1628+
Get an integer configuration option.
1629+
1630+
* Return ``0`` and set *\*value* on success.
1631+
* Raise an exception and return ``-1`` on error.
1632+
1633+
1634+
Example
1635+
-------
1636+
1637+
Code::
1638+
1639+
static int get_bytes_warning(void)
1640+
{
1641+
int64_t bytes_warning;
1642+
if (PyConfig_GetInt("bytes_warning", &bytes_warning) < 0) {
1643+
// Silently ignore the error
1644+
PyErr_Clear();
1645+
return -1;
1646+
}
1647+
return (int)bytes_warning;
1648+
}
1649+
1650+
16051651
Py_GetArgcArgv()
16061652
================
16071653

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

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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 test_config_get(self):
17+
config_get = _testcapi.config_get
18+
19+
# PyConfig_MEMBER_WSTR type
20+
self.assertEqual(config_get('platlibdir'), sys.platlibdir)
21+
22+
if 'PYTHONDUMPREFSFILE' not in os.environ:
23+
# PyConfig_MEMBER_WSTR_OPT type
24+
self.assertIsNone(config_get('dump_refs_file'))
25+
26+
# PyConfig_MEMBER_WSTR_LIST type
27+
self.assertEqual(config_get('argv'), sys.argv)
28+
29+
# attributes read from sys
30+
value = "TEST_MARKER_STR"
31+
for name in (
32+
"base_exec_prefix",
33+
"base_prefix",
34+
"exec_prefix",
35+
"executable",
36+
"platlibdir",
37+
"prefix",
38+
"pycache_prefix",
39+
):
40+
with self.subTest(name=name):
41+
with support.swap_attr(sys, name, value):
42+
self.assertEqual(config_get(name), value)
43+
44+
# attributes read from sys with a different name
45+
# (add underscore prefix)
46+
for config_name, sys_name in (
47+
("base_executable", "_base_executable"),
48+
("stdlib_dir", "_stdlib_dir"),
49+
):
50+
with self.subTest(config_name=config_name, sys_name=sys_name):
51+
with support.swap_attr(sys, sys_name, value):
52+
self.assertEqual(config_get(config_name), value)
53+
54+
# attributes read from sys
55+
value = ["TEST_MARKER_STRLIST"]
56+
for name in (
57+
"argv",
58+
"orig_argv",
59+
"warnoptions",
60+
):
61+
with self.subTest(name=name):
62+
with support.swap_attr(sys, name, value):
63+
self.assertEqual(config_get(name), value)
64+
65+
with support.swap_attr(sys, "path", value):
66+
self.assertEqual(config_get("module_search_paths"), value)
67+
68+
# sys.xoptions should be a dictionary, but config_get() returns
69+
# any sys.xoptions value.
70+
for value in (
71+
{"x": "value", "y": True},
72+
"not_a_dict",
73+
):
74+
with self.subTest(value=value):
75+
with support.swap_attr(sys, "_xoptions", value):
76+
self.assertEqual(config_get("xoptions"), value)
77+
78+
# write_bytecode is read from sys.dont_write_bytecode as int
79+
with support.swap_attr(sys, "dont_write_bytecode", 0):
80+
self.assertEqual(config_get('write_bytecode'), 1)
81+
with support.swap_attr(sys, "dont_write_bytecode", "yes"):
82+
self.assertEqual(config_get('write_bytecode'), 0)
83+
with support.swap_attr(sys, "dont_write_bytecode", []):
84+
self.assertEqual(config_get('write_bytecode'), 1)
85+
86+
def test_config_getint(self):
87+
config_getint = _testcapi.config_getint
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+
NONEXISTENT_KEY = 'NONEXISTENT_KEY'
104+
err_msg = f'unknown config option name: {NONEXISTENT_KEY}'
105+
with self.assertRaisesRegex(ValueError, err_msg):
106+
config_getint('NONEXISTENT_KEY')
107+
108+
# write_bytecode is read from sys.dont_write_bytecode
109+
with support.swap_attr(sys, "dont_write_bytecode", 0):
110+
self.assertEqual(config_getint('write_bytecode'), 1)
111+
with support.swap_attr(sys, "dont_write_bytecode", "yes"):
112+
self.assertEqual(config_getint('write_bytecode'), 0)
113+
with support.swap_attr(sys, "dont_write_bytecode", []):
114+
self.assertEqual(config_getint('write_bytecode'), 1)
115+
116+
117+
if __name__ == "__main__":
118+
unittest.main()

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)