Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f3cb814

Browse files
authored
bpo-42260: Add _PyConfig_FromDict() (GH-23167)
* Rename config_as_dict() to _PyConfig_AsDict(). * Add 'module_search_paths_set' to _PyConfig_AsDict(). * Add _PyConfig_FromDict(). * Add get_config() and set_config() to _testinternalcapi. * Add config_check_consistency().
1 parent 4662fa9 commit f3cb814

File tree

6 files changed

+653
-60
lines changed

6 files changed

+653
-60
lines changed

Include/internal/pycore_initconfig.h

+3
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ extern PyStatus _PyConfig_SetPyArgv(
158158
PyConfig *config,
159159
const _PyArgv *args);
160160

161+
PyAPI_FUNC(PyObject*) _PyConfig_AsDict(const PyConfig *config);
162+
PyAPI_FUNC(int) _PyConfig_FromDict(PyConfig *config, PyObject *dict);
163+
161164

162165
/* --- Function used for testing ---------------------------------- */
163166

Lib/test/_test_embed_set_config.py

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# bpo-42260: Test _PyInterpreterState_GetConfigCopy()
2+
# and _PyInterpreterState_SetConfig().
3+
#
4+
# Test run in a subinterpreter since set_config(get_config())
5+
# does reset sys attributes to their state of the Python startup
6+
# (before the site module is run).
7+
8+
import _testinternalcapi
9+
import os
10+
import sys
11+
import unittest
12+
13+
14+
MS_WINDOWS = (os.name == 'nt')
15+
MAX_HASH_SEED = 4294967295
16+
17+
class SetConfigTests(unittest.TestCase):
18+
def setUp(self):
19+
self.old_config = _testinternalcapi.get_config()
20+
self.sys_copy = dict(sys.__dict__)
21+
22+
def tearDown(self):
23+
self.set_config(parse_argv=0)
24+
sys.__dict__.clear()
25+
sys.__dict__.update(self.sys_copy)
26+
27+
def set_config(self, **kwargs):
28+
_testinternalcapi.set_config(self.old_config | kwargs)
29+
30+
def check(self, **kwargs):
31+
self.set_config(**kwargs)
32+
for key, value in kwargs.items():
33+
self.assertEqual(getattr(sys, key), value,
34+
(key, value))
35+
36+
def test_set_invalid(self):
37+
invalid_uint = -1
38+
NULL = None
39+
invalid_wstr = NULL
40+
# PyWideStringList strings must be non-NULL
41+
invalid_wstrlist = ["abc", NULL, "def"]
42+
43+
type_tests = []
44+
value_tests = [
45+
# enum
46+
('_config_init', 0),
47+
('_config_init', 4),
48+
# unsigned long
49+
("hash_seed", -1),
50+
("hash_seed", MAX_HASH_SEED + 1),
51+
]
52+
53+
# int (unsigned)
54+
options = [
55+
'_config_init',
56+
'isolated',
57+
'use_environment',
58+
'dev_mode',
59+
'install_signal_handlers',
60+
'use_hash_seed',
61+
'faulthandler',
62+
'tracemalloc',
63+
'import_time',
64+
'show_ref_count',
65+
'dump_refs',
66+
'malloc_stats',
67+
'parse_argv',
68+
'site_import',
69+
'bytes_warning',
70+
'inspect',
71+
'interactive',
72+
'optimization_level',
73+
'parser_debug',
74+
'write_bytecode',
75+
'verbose',
76+
'quiet',
77+
'user_site_directory',
78+
'configure_c_stdio',
79+
'buffered_stdio',
80+
'pathconfig_warnings',
81+
'module_search_paths_set',
82+
'skip_source_first_line',
83+
'_install_importlib',
84+
'_init_main',
85+
'_isolated_interpreter',
86+
]
87+
if MS_WINDOWS:
88+
options.append('legacy_windows_stdio')
89+
for key in options:
90+
value_tests.append((key, invalid_uint))
91+
type_tests.append((key, "abc"))
92+
type_tests.append((key, 2.0))
93+
94+
# wchar_t*
95+
for key in (
96+
'filesystem_encoding',
97+
'filesystem_errors',
98+
'stdio_encoding',
99+
'stdio_errors',
100+
'check_hash_pycs_mode',
101+
'program_name',
102+
'platlibdir',
103+
'executable',
104+
'base_executable',
105+
'prefix',
106+
'base_prefix',
107+
'exec_prefix',
108+
'base_exec_prefix',
109+
# optional wstr:
110+
# 'pythonpath_env'
111+
# 'home',
112+
# 'pycache_prefix'
113+
# 'run_command'
114+
# 'run_module'
115+
# 'run_filename'
116+
):
117+
value_tests.append((key, invalid_wstr))
118+
type_tests.append((key, b'bytes'))
119+
type_tests.append((key, 123))
120+
121+
# PyWideStringList
122+
for key in (
123+
'orig_argv',
124+
'argv',
125+
'xoptions',
126+
'warnoptions',
127+
'module_search_paths',
128+
):
129+
value_tests.append((key, invalid_wstrlist))
130+
type_tests.append((key, 123))
131+
type_tests.append((key, "abc"))
132+
type_tests.append((key, [123]))
133+
type_tests.append((key, [b"bytes"]))
134+
135+
136+
if MS_WINDOWS:
137+
value_tests.append(('legacy_windows_stdio', invalid_uint))
138+
139+
for exc_type, tests in (
140+
(ValueError, value_tests),
141+
(TypeError, type_tests),
142+
):
143+
for key, value in tests:
144+
config = self.old_config | {key: value}
145+
with self.subTest(key=key, value=value, exc_type=exc_type):
146+
with self.assertRaises(exc_type):
147+
_testinternalcapi.set_config(config)
148+
149+
def test_flags(self):
150+
for sys_attr, key, value in (
151+
("debug", "parser_debug", 1),
152+
("inspect", "inspect", 2),
153+
("interactive", "interactive", 3),
154+
("optimize", "optimization_level", 4),
155+
("verbose", "verbose", 1),
156+
("bytes_warning", "bytes_warning", 10),
157+
("quiet", "quiet", 11),
158+
("isolated", "isolated", 12),
159+
):
160+
with self.subTest(sys=sys_attr, key=key, value=value):
161+
self.set_config(**{key: value, 'parse_argv': 0})
162+
self.assertEqual(getattr(sys.flags, sys_attr), value)
163+
164+
self.set_config(write_bytecode=0)
165+
self.assertEqual(sys.flags.dont_write_bytecode, True)
166+
self.assertEqual(sys.dont_write_bytecode, True)
167+
168+
self.set_config(write_bytecode=1)
169+
self.assertEqual(sys.flags.dont_write_bytecode, False)
170+
self.assertEqual(sys.dont_write_bytecode, False)
171+
172+
self.set_config(user_site_directory=0, isolated=0)
173+
self.assertEqual(sys.flags.no_user_site, 1)
174+
self.set_config(user_site_directory=1, isolated=0)
175+
self.assertEqual(sys.flags.no_user_site, 0)
176+
177+
self.set_config(site_import=0)
178+
self.assertEqual(sys.flags.no_site, 1)
179+
self.set_config(site_import=1)
180+
self.assertEqual(sys.flags.no_site, 0)
181+
182+
self.set_config(dev_mode=0)
183+
self.assertEqual(sys.flags.dev_mode, False)
184+
self.set_config(dev_mode=1)
185+
self.assertEqual(sys.flags.dev_mode, True)
186+
187+
self.set_config(use_environment=0, isolated=0)
188+
self.assertEqual(sys.flags.ignore_environment, 1)
189+
self.set_config(use_environment=1, isolated=0)
190+
self.assertEqual(sys.flags.ignore_environment, 0)
191+
192+
self.set_config(use_hash_seed=1, hash_seed=0)
193+
self.assertEqual(sys.flags.hash_randomization, 0)
194+
self.set_config(use_hash_seed=0, hash_seed=0)
195+
self.assertEqual(sys.flags.hash_randomization, 1)
196+
self.set_config(use_hash_seed=1, hash_seed=123)
197+
self.assertEqual(sys.flags.hash_randomization, 1)
198+
199+
def test_options(self):
200+
self.check(warnoptions=[])
201+
self.check(warnoptions=["default", "ignore"])
202+
203+
self.set_config(xoptions=[])
204+
self.assertEqual(sys._xoptions, {})
205+
self.set_config(xoptions=["dev", "tracemalloc=5"])
206+
self.assertEqual(sys._xoptions, {"dev": True, "tracemalloc": "5"})
207+
208+
def test_pathconfig(self):
209+
self.check(
210+
executable='executable',
211+
prefix="prefix",
212+
base_prefix="base_prefix",
213+
exec_prefix="exec_prefix",
214+
base_exec_prefix="base_exec_prefix",
215+
platlibdir="platlibdir")
216+
217+
self.set_config(base_executable="base_executable")
218+
self.assertEqual(sys._base_executable, "base_executable")
219+
220+
def test_path(self):
221+
self.set_config(module_search_paths_set=1,
222+
module_search_paths=['a', 'b', 'c'])
223+
self.assertEqual(sys.path, ['a', 'b', 'c'])
224+
225+
# Leave sys.path unchanged if module_search_paths_set=0
226+
self.set_config(module_search_paths_set=0,
227+
module_search_paths=['new_path'])
228+
self.assertEqual(sys.path, ['a', 'b', 'c'])
229+
230+
def test_argv(self):
231+
self.set_config(parse_argv=0,
232+
argv=['python_program', 'args'],
233+
orig_argv=['orig', 'orig_args'])
234+
self.assertEqual(sys.argv, ['python_program', 'args'])
235+
self.assertEqual(sys.orig_argv, ['orig', 'orig_args'])
236+
237+
def test_pycache_prefix(self):
238+
self.check(pycache_prefix=None)
239+
self.check(pycache_prefix="pycache_prefix")
240+
241+
242+
if __name__ == "__main__":
243+
unittest.main()

Lib/test/test_embed.py

+14
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
# _PyCoreConfig_InitIsolatedConfig()
3131
API_ISOLATED = 3
3232

33+
MAX_HASH_SEED = 4294967295
34+
3335

3436
def debug_build(program):
3537
program = os.path.basename(program)
@@ -382,6 +384,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
382384
'exec_prefix': GET_DEFAULT_CONFIG,
383385
'base_exec_prefix': GET_DEFAULT_CONFIG,
384386
'module_search_paths': GET_DEFAULT_CONFIG,
387+
'module_search_paths_set': 1,
385388
'platlibdir': sys.platlibdir,
386389

387390
'site_import': 1,
@@ -1408,6 +1411,17 @@ def test_get_argc_argv(self):
14081411
# ignore output
14091412

14101413

1414+
class SetConfigTests(unittest.TestCase):
1415+
def test_set_config(self):
1416+
# bpo-42260: Test _PyInterpreterState_SetConfig()
1417+
cmd = [sys.executable, '-I', '-m', 'test._test_embed_set_config']
1418+
proc = subprocess.run(cmd,
1419+
stdout=subprocess.PIPE,
1420+
stderr=subprocess.PIPE)
1421+
self.assertEqual(proc.returncode, 0,
1422+
(proc.returncode, proc.stdout, proc.stderr))
1423+
1424+
14111425
class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
14121426
def test_open_code_hook(self):
14131427
self.run_embedded_interpreter("test_open_code_hook")

Modules/_testinternalcapi.c

+37-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313

1414
#include "Python.h"
1515
#include "pycore_bitutils.h" // _Py_bswap32()
16-
#include "pycore_initconfig.h" // _Py_GetConfigsAsDict()
17-
#include "pycore_hashtable.h" // _Py_hashtable_new()
1816
#include "pycore_gc.h" // PyGC_Head
17+
#include "pycore_hashtable.h" // _Py_hashtable_new()
18+
#include "pycore_initconfig.h" // _Py_GetConfigsAsDict()
19+
#include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy()
1920

2021

2122
static PyObject *
@@ -231,13 +232,47 @@ test_hashtable(PyObject *self, PyObject *Py_UNUSED(args))
231232
}
232233

233234

235+
static PyObject *
236+
test_get_config(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
237+
{
238+
PyConfig config;
239+
PyConfig_InitIsolatedConfig(&config);
240+
if (_PyInterpreterState_GetConfigCopy(&config) < 0) {
241+
PyConfig_Clear(&config);
242+
return NULL;
243+
}
244+
PyObject *dict = _PyConfig_AsDict(&config);
245+
PyConfig_Clear(&config);
246+
return dict;
247+
}
248+
249+
250+
static PyObject *
251+
test_set_config(PyObject *Py_UNUSED(self), PyObject *dict)
252+
{
253+
PyConfig config;
254+
PyConfig_InitIsolatedConfig(&config);
255+
if (_PyConfig_FromDict(&config, dict) < 0) {
256+
PyConfig_Clear(&config);
257+
return NULL;
258+
}
259+
if (_PyInterpreterState_SetConfig(&config) < 0) {
260+
return NULL;
261+
}
262+
PyConfig_Clear(&config);
263+
Py_RETURN_NONE;
264+
}
265+
266+
234267
static PyMethodDef TestMethods[] = {
235268
{"get_configs", get_configs, METH_NOARGS},
236269
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
237270
{"test_bswap", test_bswap, METH_NOARGS},
238271
{"test_popcount", test_popcount, METH_NOARGS},
239272
{"test_bit_length", test_bit_length, METH_NOARGS},
240273
{"test_hashtable", test_hashtable, METH_NOARGS},
274+
{"get_config", test_get_config, METH_NOARGS},
275+
{"set_config", test_set_config, METH_O},
241276
{NULL, NULL} /* sentinel */
242277
};
243278

0 commit comments

Comments
 (0)