extern const PyConfig* _PyInterpreterState_GetConfig(PyInterpreterState *interp);
-// Get a copy of the current interpreter configuration.
-//
-// Return 0 on success. Raise an exception and return -1 on error.
-//
-// The caller must initialize 'config', using PyConfig_InitPythonConfig()
-// for example.
-//
-// Python must be preinitialized to call this method.
-// The caller must hold the GIL.
-//
-// Once done with the configuration, PyConfig_Clear() must be called to clear
-// it.
-//
-// Export for '_testinternalcapi' shared extension.
-PyAPI_FUNC(int) _PyInterpreterState_GetConfigCopy(
- struct PyConfig *config);
-
-// Set the configuration of the current interpreter.
-//
-// This function should be called during or just after the Python
-// initialization.
-//
-// Update the sys module with the new configuration. If the sys module was
-// modified directly after the Python initialization, these changes are lost.
-//
-// Some configuration like faulthandler or warnoptions can be updated in the
-// configuration, but don't reconfigure Python (don't enable/disable
-// faulthandler and don't reconfigure warnings filters).
-//
-// Return 0 on success. Raise an exception and return -1 on error.
-//
-// The configuration should come from _PyInterpreterState_GetConfigCopy().
-//
-// Export for '_testinternalcapi' shared extension.
-PyAPI_FUNC(int) _PyInterpreterState_SetConfig(
- const struct PyConfig *config);
-
/*
Runtime Feature Flags
+++ /dev/null
-# bpo-42260: Test _PyInterpreterState_GetConfigCopy()
-# and _PyInterpreterState_SetConfig().
-#
-# Test run in a subprocess since set_config(get_config())
-# does reset sys attributes to their state of the Python startup
-# (before the site module is run).
-
-import _testinternalcapi
-import sys
-import unittest
-from test import support
-from test.support import MS_WINDOWS
-
-
-MAX_HASH_SEED = 4294967295
-
-
-BOOL_OPTIONS = [
- 'isolated',
- 'use_environment',
- 'dev_mode',
- 'install_signal_handlers',
- 'use_hash_seed',
- 'faulthandler',
- 'import_time',
- 'code_debug_ranges',
- 'show_ref_count',
- 'dump_refs',
- 'malloc_stats',
- 'parse_argv',
- 'site_import',
- 'warn_default_encoding',
- 'inspect',
- 'interactive',
- 'parser_debug',
- 'write_bytecode',
- 'quiet',
- 'user_site_directory',
- 'configure_c_stdio',
- 'buffered_stdio',
- 'use_frozen_modules',
- 'safe_path',
- 'pathconfig_warnings',
- 'module_search_paths_set',
- 'skip_source_first_line',
- '_install_importlib',
- '_init_main',
- '_is_python_build',
-]
-if MS_WINDOWS:
- BOOL_OPTIONS.append('legacy_windows_stdio')
-
-
-class SetConfigTests(unittest.TestCase):
- def setUp(self):
- self.old_config = _testinternalcapi.get_config()
- self.sys_copy = dict(sys.__dict__)
-
- def tearDown(self):
- _testinternalcapi.reset_path_config()
- _testinternalcapi.set_config(self.old_config)
- sys.__dict__.clear()
- sys.__dict__.update(self.sys_copy)
-
- def set_config(self, **kwargs):
- _testinternalcapi.set_config(self.old_config | kwargs)
-
- def check(self, **kwargs):
- self.set_config(**kwargs)
- for key, value in kwargs.items():
- self.assertEqual(getattr(sys, key), value,
- (key, value))
-
- def test_set_invalid(self):
- invalid_uint = -1
- NULL = None
- invalid_wstr = NULL
- # PyWideStringList strings must be non-NULL
- invalid_wstrlist = ["abc", NULL, "def"]
-
- type_tests = []
- value_tests = [
- # enum
- ('_config_init', 0),
- ('_config_init', 4),
- # unsigned long
- ("hash_seed", -1),
- ("hash_seed", MAX_HASH_SEED + 1),
- ]
-
- # int (unsigned)
- int_options = [
- '_config_init',
- 'bytes_warning',
- 'optimization_level',
- 'tracemalloc',
- 'verbose',
- ]
- int_options.extend(BOOL_OPTIONS)
- for key in int_options:
- value_tests.append((key, invalid_uint))
- type_tests.append((key, "abc"))
- type_tests.append((key, 2.0))
-
- # wchar_t*
- for key in (
- 'filesystem_encoding',
- 'filesystem_errors',
- 'stdio_encoding',
- 'stdio_errors',
- 'check_hash_pycs_mode',
- 'program_name',
- 'platlibdir',
- # optional wstr:
- # 'pythonpath_env'
- # 'home'
- # 'pycache_prefix'
- # 'run_command'
- # 'run_module'
- # 'run_filename'
- # 'executable'
- # 'prefix'
- # 'exec_prefix'
- # 'base_executable'
- # 'base_prefix'
- # 'base_exec_prefix'
- ):
- value_tests.append((key, invalid_wstr))
- type_tests.append((key, b'bytes'))
- type_tests.append((key, 123))
-
- # PyWideStringList
- for key in (
- 'orig_argv',
- 'argv',
- 'xoptions',
- 'warnoptions',
- 'module_search_paths',
- ):
- if key != 'xoptions':
- value_tests.append((key, invalid_wstrlist))
- type_tests.append((key, 123))
- type_tests.append((key, "abc"))
- type_tests.append((key, [123]))
- type_tests.append((key, [b"bytes"]))
-
-
- if MS_WINDOWS:
- value_tests.append(('legacy_windows_stdio', invalid_uint))
-
- for exc_type, tests in (
- (ValueError, value_tests),
- (TypeError, type_tests),
- ):
- for key, value in tests:
- config = self.old_config | {key: value}
- with self.subTest(key=key, value=value, exc_type=exc_type):
- with self.assertRaises(exc_type):
- _testinternalcapi.set_config(config)
-
- def test_flags(self):
- bool_options = set(BOOL_OPTIONS)
- for sys_attr, key, value in (
- ("debug", "parser_debug", 2),
- ("inspect", "inspect", 3),
- ("interactive", "interactive", 4),
- ("optimize", "optimization_level", 5),
- ("verbose", "verbose", 6),
- ("bytes_warning", "bytes_warning", 7),
- ("quiet", "quiet", 8),
- ("isolated", "isolated", 9),
- ):
- with self.subTest(sys=sys_attr, key=key, value=value):
- self.set_config(**{key: value, 'parse_argv': 0})
- if key in bool_options:
- self.assertEqual(getattr(sys.flags, sys_attr), int(bool(value)))
- else:
- self.assertEqual(getattr(sys.flags, sys_attr), value)
-
- self.set_config(write_bytecode=0)
- self.assertEqual(sys.flags.dont_write_bytecode, True)
- self.assertEqual(sys.dont_write_bytecode, True)
-
- self.set_config(write_bytecode=1)
- self.assertEqual(sys.flags.dont_write_bytecode, False)
- self.assertEqual(sys.dont_write_bytecode, False)
-
- self.set_config(user_site_directory=0, isolated=0)
- self.assertEqual(sys.flags.no_user_site, 1)
- self.set_config(user_site_directory=1, isolated=0)
- self.assertEqual(sys.flags.no_user_site, 0)
-
- self.set_config(site_import=0)
- self.assertEqual(sys.flags.no_site, 1)
- self.set_config(site_import=1)
- self.assertEqual(sys.flags.no_site, 0)
-
- self.set_config(dev_mode=0)
- self.assertEqual(sys.flags.dev_mode, False)
- self.set_config(dev_mode=1)
- self.assertEqual(sys.flags.dev_mode, True)
-
- self.set_config(use_environment=0, isolated=0)
- self.assertEqual(sys.flags.ignore_environment, 1)
- self.set_config(use_environment=1, isolated=0)
- self.assertEqual(sys.flags.ignore_environment, 0)
-
- self.set_config(use_hash_seed=1, hash_seed=0)
- self.assertEqual(sys.flags.hash_randomization, 0)
- self.set_config(use_hash_seed=0, hash_seed=0)
- self.assertEqual(sys.flags.hash_randomization, 1)
- self.set_config(use_hash_seed=1, hash_seed=123)
- self.assertEqual(sys.flags.hash_randomization, 1)
-
- if support.Py_GIL_DISABLED:
- self.set_config(enable_gil=-1)
- self.assertEqual(sys.flags.gil, None)
- self.set_config(enable_gil=0)
- self.assertEqual(sys.flags.gil, 0)
- self.set_config(enable_gil=1)
- self.assertEqual(sys.flags.gil, 1)
- else:
- # Builds without Py_GIL_DISABLED don't have
- # PyConfig.enable_gil. sys.flags.gil is always defined to 1, for
- # consistency.
- self.assertEqual(sys.flags.gil, 1)
-
- def test_options(self):
- self.check(warnoptions=[])
- self.check(warnoptions=["default", "ignore"])
-
- self.set_config(xoptions={})
- self.assertEqual(sys._xoptions, {})
- self.set_config(xoptions={"dev": True, "tracemalloc": "5"})
- self.assertEqual(sys._xoptions, {"dev": True, "tracemalloc": "5"})
-
- def test_pathconfig(self):
- self.check(
- executable='executable',
- prefix="prefix",
- base_prefix="base_prefix",
- exec_prefix="exec_prefix",
- base_exec_prefix="base_exec_prefix",
- platlibdir="platlibdir")
-
- self.set_config(base_executable="base_executable")
- self.assertEqual(sys._base_executable, "base_executable")
-
- # When base_xxx is NULL, value is copied from xxxx
- self.set_config(
- executable='executable',
- prefix="prefix",
- exec_prefix="exec_prefix",
- base_executable=None,
- base_prefix=None,
- base_exec_prefix=None)
- self.assertEqual(sys._base_executable, "executable")
- self.assertEqual(sys.base_prefix, "prefix")
- self.assertEqual(sys.base_exec_prefix, "exec_prefix")
-
- def test_path(self):
- self.set_config(module_search_paths_set=1,
- module_search_paths=['a', 'b', 'c'])
- self.assertEqual(sys.path, ['a', 'b', 'c'])
-
- # sys.path is reset if module_search_paths_set=0
- self.set_config(module_search_paths_set=0,
- module_search_paths=['new_path'])
- self.assertNotEqual(sys.path, ['a', 'b', 'c'])
- self.assertNotEqual(sys.path, ['new_path'])
-
- def test_argv(self):
- self.set_config(parse_argv=0,
- argv=['python_program', 'args'],
- orig_argv=['orig', 'orig_args'])
- self.assertEqual(sys.argv, ['python_program', 'args'])
- self.assertEqual(sys.orig_argv, ['orig', 'orig_args'])
-
- self.set_config(parse_argv=0,
- argv=[],
- orig_argv=[])
- self.assertEqual(sys.argv, [''])
- self.assertEqual(sys.orig_argv, [])
-
- def test_pycache_prefix(self):
- self.check(pycache_prefix=None)
- self.check(pycache_prefix="pycache_prefix")
-
-
-if __name__ == "__main__":
- unittest.main()
def has_no_debug_ranges():
try:
- import _testinternalcapi
+ import _testcapi
except ImportError:
raise unittest.SkipTest("_testinternalcapi required")
- config = _testinternalcapi.get_config()
+ return not _testcapi.config_get('code_debug_ranges')
return not bool(config['code_debug_ranges'])
def requires_debug_ranges(reason='requires co_positions / debug_ranges'):
self.assertEqual(ret, 0)
self.assertEqual(pickle.load(f), {'a': '123x', 'b': '123'})
+ # _testcapi cannot be imported in a subinterpreter on a Free Threaded build
+ @support.requires_gil_enabled()
def test_py_config_isoloated_per_interpreter(self):
# A config change in one interpreter must not leak to out to others.
#
# This test could verify ANY config value, it just happens to have been
# written around the time of int_max_str_digits. Refactoring is okay.
code = """if 1:
- import sys, _testinternalcapi
+ import sys, _testcapi
# Any config value would do, this happens to be the one being
# double checked at the time this test was written.
- config = _testinternalcapi.get_config()
- config['int_max_str_digits'] = 55555
- config['parse_argv'] = 0
- _testinternalcapi.set_config(config)
- sub_value = _testinternalcapi.get_config()['int_max_str_digits']
+ _testcapi.config_set('int_max_str_digits', 55555)
+ sub_value = _testcapi.config_get('int_max_str_digits')
assert sub_value == 55555, sub_value
"""
- before_config = _testinternalcapi.get_config()
- assert before_config['int_max_str_digits'] != 55555
+ before_config = _testcapi.config_get('int_max_str_digits')
+ assert before_config != 55555
self.assertEqual(support.run_in_subinterp(code), 0,
'subinterp code failure, check stderr.')
- after_config = _testinternalcapi.get_config()
+ after_config = _testcapi.config_get('int_max_str_digits')
self.assertIsNot(
before_config, after_config,
"Expected get_config() to return a new dict on each call")
self.check_all_configs("test_init_warnoptions", config, preconfig,
api=API_PYTHON)
- def test_init_set_config(self):
- config = {
- 'bytes_warning': 2,
- 'warnoptions': ['error::BytesWarning'],
- }
- self.check_all_configs("test_init_set_config", config,
- api=API_ISOLATED)
-
@unittest.skipIf(support.check_bolt_optimized, "segfaults on BOLT instrumented binaries")
def test_initconfig_api(self):
preconfig = {
self.assertEqual(err, "")
-class SetConfigTests(unittest.TestCase):
- def test_set_config(self):
- # bpo-42260: Test _PyInterpreterState_SetConfig()
- import_helper.import_module('_testcapi')
- cmd = [sys.executable, '-X', 'utf8', '-I', '-m', 'test._test_embed_set_config']
- proc = subprocess.run(cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- encoding='utf-8', errors='backslashreplace')
- if proc.returncode and support.verbose:
- print(proc.stdout)
- print(proc.stderr)
- self.assertEqual(proc.returncode, 0,
- (proc.returncode, proc.stdout, proc.stderr))
-
-
class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
def test_open_code_hook(self):
self.run_embedded_interpreter("test_open_code_hook")
import unittest
from test import support
try:
- from _testinternalcapi import get_config
+ from _testcapi import config_get
except ImportError:
- get_config = None
+ config_get = None
# WASI/WASM buildbots don't use -E option
use_environment = (support.is_emscripten or support.is_wasi)
class WorkerTests(unittest.TestCase):
- @unittest.skipUnless(get_config is None, 'need get_config()')
+ @unittest.skipUnless(config_get is None, 'need config_get()')
def test_config(self):
- config = get_config()['config']
+ config = config_get()
# -u option
- self.assertEqual(config['buffered_stdio'], 0)
+ self.assertEqual(config_get('buffered_stdio'), 0)
# -W default option
- self.assertTrue(config['warnoptions'], ['default'])
+ self.assertTrue(config_get('warnoptions'), ['default'])
# -bb option
- self.assertTrue(config['bytes_warning'], 2)
+ self.assertTrue(config_get('bytes_warning'), 2)
# -E option
- self.assertTrue(config['use_environment'], use_environment)
+ self.assertTrue(config_get('use_environment'), use_environment)
def test_python_opts(self):
# -u option
--- /dev/null
+Remove ``_PyInterpreterState_GetConfigCopy()`` and
+``_PyInterpreterState_SetConfig()`` private functions. Use instead
+:c:func:`PyConfig_Get` and :c:func:`PyConfig_Set`, public C API added by
+:pep:`741` "Python Configuration C API". Patch by Victor Stinner.
#include "pycore_hashtable.h" // _Py_hashtable_new()
#include "pycore_initconfig.h" // _Py_GetConfigsAsDict()
#include "pycore_instruction_sequence.h" // _PyInstructionSequence_New()
-#include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy()
#include "pycore_long.h" // _PyLong_Sign()
#include "pycore_object.h" // _PyObject_IsFreed()
#include "pycore_optimizer.h" // _Py_UopsSymbol, etc.
}
-static PyObject *
-test_get_config(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
-{
- PyConfig config;
- PyConfig_InitIsolatedConfig(&config);
- if (_PyInterpreterState_GetConfigCopy(&config) < 0) {
- PyConfig_Clear(&config);
- return NULL;
- }
- PyObject *dict = _PyConfig_AsDict(&config);
- PyConfig_Clear(&config);
- return dict;
-}
-
-
-static PyObject *
-test_set_config(PyObject *Py_UNUSED(self), PyObject *dict)
-{
- PyConfig config;
- PyConfig_InitIsolatedConfig(&config);
- if (_PyConfig_FromDict(&config, dict) < 0) {
- goto error;
- }
- if (_PyInterpreterState_SetConfig(&config) < 0) {
- goto error;
- }
- PyConfig_Clear(&config);
- Py_RETURN_NONE;
-
-error:
- PyConfig_Clear(&config);
- return NULL;
-}
-
-
static PyObject *
test_reset_path_config(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(arg))
{
{"test_popcount", test_popcount, METH_NOARGS},
{"test_bit_length", test_bit_length, METH_NOARGS},
{"test_hashtable", test_hashtable, METH_NOARGS},
- {"get_config", test_get_config, METH_NOARGS},
- {"set_config", test_set_config, METH_O},
{"reset_path_config", test_reset_path_config, METH_NOARGS},
{"test_edit_cost", test_edit_cost, METH_NOARGS},
{"test_bytes_find", test_bytes_find, METH_NOARGS},
}
-static int tune_config(void)
-{
- PyConfig config;
- PyConfig_InitPythonConfig(&config);
- if (_PyInterpreterState_GetConfigCopy(&config) < 0) {
- PyConfig_Clear(&config);
- PyErr_Print();
- return -1;
- }
-
- config.bytes_warning = 2;
-
- if (_PyInterpreterState_SetConfig(&config) < 0) {
- PyConfig_Clear(&config);
- return -1;
- }
- PyConfig_Clear(&config);
- return 0;
-}
-
-
-static int test_init_set_config(void)
-{
- // Initialize core
- PyConfig config;
- PyConfig_InitIsolatedConfig(&config);
- config_set_string(&config, &config.program_name, PROGRAM_NAME);
- config.bytes_warning = 0;
- init_from_config_clear(&config);
-
- // Tune the configuration using _PyInterpreterState_SetConfig()
- if (tune_config() < 0) {
- PyErr_Print();
- return 1;
- }
-
- dump_config();
- Py_Finalize();
- return 0;
-}
-
-
static int initconfig_getint(PyInitConfig *config, const char *name)
{
int64_t value;
{"test_init_setpythonhome", test_init_setpythonhome},
{"test_init_is_python_build", test_init_is_python_build},
{"test_init_warnoptions", test_init_warnoptions},
- {"test_init_set_config", test_init_set_config},
{"test_initconfig_api", test_initconfig_api},
{"test_initconfig_get_api", test_initconfig_get_api},
{"test_initconfig_exit", test_initconfig_exit},
}
-int
-_PyInterpreterState_SetConfig(const PyConfig *src_config)
-{
- PyThreadState *tstate = _PyThreadState_GET();
- int res = -1;
-
- PyConfig config;
- PyConfig_InitPythonConfig(&config);
- PyStatus status = _PyConfig_Copy(&config, src_config);
- if (_PyStatus_EXCEPTION(status)) {
- _PyErr_SetFromPyStatus(status);
- goto done;
- }
-
- status = _PyConfig_Read(&config, 1);
- if (_PyStatus_EXCEPTION(status)) {
- _PyErr_SetFromPyStatus(status);
- goto done;
- }
-
- status = _PyConfig_Copy(&tstate->interp->config, &config);
- if (_PyStatus_EXCEPTION(status)) {
- _PyErr_SetFromPyStatus(status);
- goto done;
- }
-
- res = interpreter_update_config(tstate, 0);
-
-done:
- PyConfig_Clear(&config);
- return res;
-}
-
-
/* Global initializations. Can be undone by Py_Finalize(). Don't
call this twice without an intervening Py_Finalize() call.
}
-int
-_PyInterpreterState_GetConfigCopy(PyConfig *config)
-{
- PyInterpreterState *interp = _PyInterpreterState_GET();
-
- PyStatus status = _PyConfig_Copy(config, &interp->config);
- if (PyStatus_Exception(status)) {
- _PyErr_SetFromPyStatus(status);
- return -1;
- }
- return 0;
-}
-
-
const PyConfig*
_Py_GetConfig(void)
{