diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst
index 47a8fbb2cd9c97..e77a59a7d5d2cc 100644
--- a/Doc/c-api/init_config.rst
+++ b/Doc/c-api/init_config.rst
@@ -1602,6 +1602,62 @@ customized Python always running in isolated mode using
:c:func:`Py_RunMain`.
+Get the current Python configuration
+====================================
+
+Get a configuration option where *name* is the name of a :c:type:`PyConfig`
+member.
+
+Some options are read from the :mod:`sys` attributes. For example, the option
+``"argv"`` is read from :data:`sys.argv`.
+
+
+.. c:function:: PyObject* PyConfig_Get(const char *name)
+
+ Get a configuration option as a Python object.
+
+ The object type depends on the configuration option. It can be:
+
+ * ``int``
+ * ``str``
+ * ``list[str]``
+ * ``dict[str, str]``
+
+ Return value:
+
+ * Return a new reference on success.
+ * Set an exception and return ``NULL`` on error.
+
+ The function must not be called before Python is initialized or after
+ Python is finalized. The caller must hold the GIL.
+
+ .. versionadded:: 3.13
+
+
+.. c:function:: int PyConfig_GetInt(const char *name, int *value)
+
+ Similar to :c:func:`PyConfig_Get`, but get the value as a C int.
+
+ .. versionadded:: 3.13
+
+
+Example
+-------
+
+Code::
+
+ int get_verbose(void)
+ {
+ int verbose;
+ if (PyConfig_GetInt("verbose", &verbose) < 0) {
+ // Silently ignore the error
+ PyErr_Clear();
+ return -1;
+ }
+ return verbose;
+ }
+
+
Py_GetArgcArgv()
================
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index 372e4a45468e68..43a1638a27487f 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -1230,6 +1230,12 @@ New Features
:exc:`KeyError` if the key missing.
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
+* Add functions to get the current Python configuration:
+
+ * :c:func:`PyConfig_Get`
+ * :c:func:`PyConfig_GetInt`
+
+ (Contributed by Victor Stinner in :gh:`107954`.)
Porting to Python 3.13
----------------------
@@ -1498,6 +1504,9 @@ Pending Removal in Python 3.14
* :c:var:`!Py_FileSystemDefaultEncodeErrors`: use :c:member:`PyConfig.filesystem_errors`
* :c:var:`!Py_UTF8Mode`: use :c:member:`PyPreConfig.utf8_mode` (see :c:func:`Py_PreInitialize`)
+ Use :c:func:`PyConfig_GetInt` and :c:func:`PyConfig_Get` functions to get
+ these configuration options.
+
The :c:func:`Py_InitializeFromConfig` API should be used with
:c:type:`PyConfig` instead.
diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h
index 87c059c521cbc9..e23d267e58b86a 100644
--- a/Include/cpython/initconfig.h
+++ b/Include/cpython/initconfig.h
@@ -257,6 +257,24 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config,
Py_ssize_t length, wchar_t **items);
+/* --- PyConfig_Get() ----------------------------------------- */
+
+// Get a configuration option as a Python object.
+// Return a new reference on success.
+// Set an exception and return NULL on error.
+//
+// The object type depends on the configuration option. It can be:
+// int, str, list[str] and dict[str, str].
+PyAPI_FUNC(PyObject*) PyConfig_Get(const char *name);
+
+// Get an configuration option as an integer.
+// Return 0 and set '*value' on success.
+// Raise an exception return -1 on error.
+PyAPI_FUNC(int) PyConfig_GetInt(
+ const char *name,
+ int *value);
+
+
/* --- Helper functions --------------------------------------- */
/* Get the original command line arguments, before Python modified them.
diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h
index c86988234f6a05..7e2add7e8a819b 100644
--- a/Include/internal/pycore_initconfig.h
+++ b/Include/internal/pycore_initconfig.h
@@ -169,7 +169,7 @@ extern PyStatus _PyConfig_Write(const PyConfig *config,
extern PyStatus _PyConfig_SetPyArgv(
PyConfig *config,
const _PyArgv *args);
-
+extern PyObject* _PyConfig_CreateXOptionsDict(const PyConfig *config);
extern void _Py_DumpPathConfig(PyThreadState *tstate);
diff --git a/Lib/test/_test_embed_set_config.py b/Lib/test/_test_embed_set_config.py
index a2ddd133cf47c8..50600ef785bb2d 100644
--- a/Lib/test/_test_embed_set_config.py
+++ b/Lib/test/_test_embed_set_config.py
@@ -127,7 +127,8 @@ def test_set_invalid(self):
'warnoptions',
'module_search_paths',
):
- value_tests.append((key, invalid_wstrlist))
+ if key != 'xoptions':
+ value_tests.append((key, invalid_wstrlist))
type_tests.append((key, 123))
type_tests.append((key, "abc"))
type_tests.append((key, [123]))
@@ -201,9 +202,9 @@ def test_options(self):
self.check(warnoptions=[])
self.check(warnoptions=["default", "ignore"])
- self.set_config(xoptions=[])
+ self.set_config(xoptions={})
self.assertEqual(sys._xoptions, {})
- self.set_config(xoptions=["dev", "tracemalloc=5"])
+ self.set_config(xoptions={"dev": True, "tracemalloc": "5"})
self.assertEqual(sys._xoptions, {"dev": True, "tracemalloc": "5"})
def test_pathconfig(self):
diff --git a/Lib/test/test_capi/test_config.py b/Lib/test/test_capi/test_config.py
new file mode 100644
index 00000000000000..573242da9dac88
--- /dev/null
+++ b/Lib/test/test_capi/test_config.py
@@ -0,0 +1,105 @@
+"""
+Tests on PyConfig API (PEP 587).
+"""
+import os
+import sys
+import unittest
+from test import support
+try:
+ import _testcapi
+except ImportError:
+ _testcapi = None
+
+
+@unittest.skipIf(_testcapi is None, 'need _testcapi')
+class CAPITests(unittest.TestCase):
+ def check_config_get(self, get_func):
+ # write_bytecode is read from sys.dont_write_bytecode as int
+ with support.swap_attr(sys, "dont_write_bytecode", 0):
+ self.assertEqual(get_func('write_bytecode'), 1)
+ with support.swap_attr(sys, "dont_write_bytecode", "yes"):
+ self.assertEqual(get_func('write_bytecode'), 0)
+ with support.swap_attr(sys, "dont_write_bytecode", []):
+ self.assertEqual(get_func('write_bytecode'), 1)
+
+ # non-existent config option name
+ NONEXISTENT_KEY = 'NONEXISTENT_KEY'
+ err_msg = f'unknown config option name: {NONEXISTENT_KEY}'
+ with self.assertRaisesRegex(ValueError, err_msg):
+ get_func('NONEXISTENT_KEY')
+
+ def test_config_get(self):
+ config_get = _testcapi.config_get
+
+ self.check_config_get(config_get)
+
+ for name, config_type, expected in (
+ ('verbose', int, sys.flags.verbose), # PyConfig_MEMBER_INT
+ ('isolated', int, sys.flags.isolated), # PyConfig_MEMBER_UINT
+ ('platlibdir', str, sys.platlibdir), # PyConfig_MEMBER_WSTR
+ ('argv', list, sys.argv), # PyConfig_MEMBER_WSTR_LIST
+ ('xoptions', dict, sys._xoptions), # xoptions dict
+ ):
+ with self.subTest(name=name):
+ value = config_get(name)
+ self.assertEqual(type(value), config_type)
+ self.assertEqual(value, expected)
+
+ # PyConfig_MEMBER_ULONG type
+ hash_seed = config_get('hash_seed')
+ self.assertIsInstance(hash_seed, int)
+ self.assertGreaterEqual(hash_seed, 0)
+
+ # PyConfig_MEMBER_WSTR_OPT type
+ if 'PYTHONDUMPREFSFILE' not in os.environ:
+ self.assertIsNone(config_get('dump_refs_file'))
+
+ # attributes read from sys
+ value_str = "TEST_MARKER_STR"
+ value_list = ["TEST_MARKER_STRLIST"]
+ value_dict = {"x": "value", "y": True}
+ for name, sys_name, value in (
+ ("base_exec_prefix", None, value_str),
+ ("base_prefix", None, value_str),
+ ("exec_prefix", None, value_str),
+ ("executable", None, value_str),
+ ("platlibdir", None, value_str),
+ ("prefix", None, value_str),
+ ("pycache_prefix", None, value_str),
+ ("base_executable", "_base_executable", value_str),
+ ("stdlib_dir", "_stdlib_dir", value_str),
+ ("argv", None, value_list),
+ ("orig_argv", None, value_list),
+ ("warnoptions", None, value_list),
+ ("module_search_paths", "path", value_list),
+ ("xoptions", "_xoptions", value_dict),
+ ):
+ with self.subTest(name=name):
+ if sys_name is None:
+ sys_name = name
+ with support.swap_attr(sys, sys_name, value):
+ self.assertEqual(config_get(name), value)
+
+ def test_config_getint(self):
+ config_getint = _testcapi.config_getint
+
+ self.check_config_get(config_getint)
+
+ # PyConfig_MEMBER_INT type
+ self.assertEqual(config_getint('verbose'), sys.flags.verbose)
+
+ # PyConfig_MEMBER_UINT type
+ self.assertEqual(config_getint('isolated'), sys.flags.isolated)
+
+ # PyConfig_MEMBER_ULONG type
+ hash_seed = config_getint('hash_seed')
+ self.assertIsInstance(hash_seed, int)
+ self.assertGreaterEqual(hash_seed, 0)
+
+ # platlibdir is a str
+ with self.assertRaises(TypeError):
+ config_getint('platlibdir')
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index d2d6c1b61e46f0..9dc20ff28c4475 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -465,7 +465,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'argv': [""],
'orig_argv': [],
- 'xoptions': [],
+ 'xoptions': {},
'warnoptions': [],
'pythonpath_env': None,
@@ -509,7 +509,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'check_hash_pycs_mode': 'default',
'pathconfig_warnings': 1,
'_init_main': 1,
- 'use_frozen_modules': not support.Py_DEBUG,
+ 'use_frozen_modules': int(not support.Py_DEBUG),
'safe_path': 0,
'_is_python_build': IGNORE_CONFIG,
}
@@ -867,12 +867,12 @@ def test_init_from_config(self):
'-c', 'pass',
'arg2'],
'parse_argv': 2,
- 'xoptions': [
- 'config_xoption1=3',
- 'config_xoption2=',
- 'config_xoption3',
- 'cmdline_xoption',
- ],
+ 'xoptions': {
+ 'config_xoption1': '3',
+ 'config_xoption2': '',
+ 'config_xoption3': True,
+ 'cmdline_xoption': True,
+ },
'warnoptions': [
'cmdline_warnoption',
'default::BytesWarning',
@@ -1016,7 +1016,7 @@ def test_preinit_parse_argv(self):
'dev_mode': 1,
'faulthandler': 1,
'warnoptions': ['default'],
- 'xoptions': ['dev'],
+ 'xoptions': {'dev': True},
'safe_path': 1,
}
self.check_all_configs("test_preinit_parse_argv", config, preconfig,
@@ -1108,12 +1108,12 @@ def modify_path(path):
def test_init_sys_add(self):
config = {
'faulthandler': 1,
- 'xoptions': [
- 'config_xoption',
- 'cmdline_xoption',
- 'sysadd_xoption',
- 'faulthandler',
- ],
+ 'xoptions': {
+ 'config_xoption': True,
+ 'cmdline_xoption': True,
+ 'sysadd_xoption': True,
+ 'faulthandler': True,
+ },
'warnoptions': [
'ignore:::cmdline_warnoption',
'ignore:::sysadd_warnoption',
@@ -1654,6 +1654,10 @@ def test_init_use_frozen_modules(self):
}
for raw, expected in tests:
optval = f'frozen_modules{raw}'
+ if raw.startswith('='):
+ xoption_value = raw[1:]
+ else:
+ xoption_value = True
config = {
'parse_argv': 2,
'argv': ['-c'],
@@ -1661,7 +1665,7 @@ def test_init_use_frozen_modules(self):
'program_name': './argv0',
'run_command': 'pass\n',
'use_environment': 1,
- 'xoptions': [optval],
+ 'xoptions': {'frozen_modules': xoption_value},
'use_frozen_modules': expected,
}
env = {'TESTFROZEN': raw[1:]} if raw else None
diff --git a/Misc/NEWS.d/next/C API/2023-12-01-21-13-32.gh-issue-107954.GO4oND.rst b/Misc/NEWS.d/next/C API/2023-12-01-21-13-32.gh-issue-107954.GO4oND.rst
new file mode 100644
index 00000000000000..90cba24323f3c1
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2023-12-01-21-13-32.gh-issue-107954.GO4oND.rst
@@ -0,0 +1,6 @@
+Add functions to get the current Python configuration:
+
+* :c:func:`PyConfig_Get`
+* :c:func:`PyConfig_GetInt`
+
+Patch by Victor Stinner.
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 54650ea9c1d4ac..504192754134f2 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -159,7 +159,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
-@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
+@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
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testcapi/config.c b/Modules/_testcapi/config.c
new file mode 100644
index 00000000000000..779cfbbf93023b
--- /dev/null
+++ b/Modules/_testcapi/config.c
@@ -0,0 +1,45 @@
+#include "parts.h"
+
+
+static PyObject *
+_testcapi_config_get(PyObject *module, PyObject *name_obj)
+{
+ const char *name;
+ if (PyArg_Parse(name_obj, "s", &name) < 0) {
+ return NULL;
+ }
+
+ return PyConfig_Get(name);
+}
+
+
+static PyObject *
+_testcapi_config_getint(PyObject *module, PyObject *name_obj)
+{
+ const char *name;
+ if (PyArg_Parse(name_obj, "s", &name) < 0) {
+ return NULL;
+ }
+
+ int value;
+ if (PyConfig_GetInt(name, &value) < 0) {
+ return NULL;
+ }
+ return PyLong_FromLong(value);
+}
+
+
+static PyMethodDef test_methods[] = {
+ {"config_get", _testcapi_config_get, METH_O},
+ {"config_getint", _testcapi_config_getint, METH_O},
+ {NULL}
+};
+
+int _PyTestCapi_Init_Config(PyObject *mod)
+{
+ if (PyModule_AddFunctions(mod, test_methods) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index 29817edd69b134..83d93ae565d4fa 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -62,5 +62,6 @@ int _PyTestCapi_Init_Hash(PyObject *module);
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);
+int _PyTestCapi_Init_Config(PyObject *mod);
#endif // Py_TESTCAPI_PARTS_H
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 9fdd67093338e4..2bfb54b81a8d52 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -4005,6 +4005,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_Hash(m) < 0) {
return NULL;
}
+ if (_PyTestCapi_Init_Config(m) < 0) {
+ return NULL;
+ }
PyState_AddModule(m, &_testcapimodule);
return m;
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 1c15541d3ec735..51c4ca74ac83c4 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -127,6 +127,7 @@
+
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index 6059959bb9a040..1f43a686477853 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -108,6 +108,9 @@
Source Files
+
+ Source Files
+
diff --git a/Python/initconfig.c b/Python/initconfig.c
index d7f3195ed5fcf0..3fbc9684f20c4e 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -40,93 +40,101 @@ typedef struct {
const char *name;
size_t offset;
PyConfigMemberType type;
+ const char *sys_attr;
} PyConfigSpec;
-#define SPEC(MEMBER, TYPE) \
- {#MEMBER, offsetof(PyConfig, MEMBER), PyConfig_MEMBER_##TYPE}
+#define SPEC(MEMBER, TYPE, SYS_ATTR) \
+ {#MEMBER, offsetof(PyConfig, MEMBER), PyConfig_MEMBER_##TYPE, SYS_ATTR}
static const PyConfigSpec PYCONFIG_SPEC[] = {
- SPEC(_config_init, UINT),
- SPEC(isolated, UINT),
- SPEC(use_environment, UINT),
- SPEC(dev_mode, UINT),
- SPEC(install_signal_handlers, UINT),
- SPEC(use_hash_seed, UINT),
- SPEC(hash_seed, ULONG),
- SPEC(faulthandler, UINT),
- SPEC(tracemalloc, UINT),
- SPEC(perf_profiling, UINT),
- SPEC(import_time, UINT),
- SPEC(code_debug_ranges, UINT),
- SPEC(show_ref_count, UINT),
- SPEC(dump_refs, UINT),
- SPEC(dump_refs_file, WSTR_OPT),
- SPEC(malloc_stats, UINT),
- SPEC(filesystem_encoding, WSTR),
- SPEC(filesystem_errors, WSTR),
- SPEC(pycache_prefix, WSTR_OPT),
- SPEC(parse_argv, UINT),
- SPEC(orig_argv, WSTR_LIST),
- SPEC(argv, WSTR_LIST),
- SPEC(xoptions, WSTR_LIST),
- SPEC(warnoptions, WSTR_LIST),
- SPEC(site_import, UINT),
- SPEC(bytes_warning, UINT),
- SPEC(warn_default_encoding, UINT),
- SPEC(inspect, UINT),
- SPEC(interactive, UINT),
- SPEC(optimization_level, UINT),
- SPEC(parser_debug, UINT),
- SPEC(write_bytecode, UINT),
- SPEC(verbose, UINT),
- SPEC(quiet, UINT),
- SPEC(user_site_directory, UINT),
- SPEC(configure_c_stdio, UINT),
- SPEC(buffered_stdio, UINT),
- SPEC(stdio_encoding, WSTR),
- SPEC(stdio_errors, WSTR),
+ SPEC(_config_init, UINT, NULL),
+ SPEC(isolated, UINT, NULL),
+ SPEC(use_environment, UINT, NULL),
+ SPEC(dev_mode, UINT, NULL),
+ SPEC(install_signal_handlers, UINT, NULL),
+ SPEC(use_hash_seed, UINT, NULL),
+ SPEC(hash_seed, ULONG, NULL),
+ SPEC(faulthandler, UINT, NULL),
+ SPEC(tracemalloc, UINT, NULL),
+ SPEC(perf_profiling, UINT, NULL),
+ SPEC(import_time, UINT, NULL),
+ SPEC(code_debug_ranges, UINT, NULL),
+ SPEC(show_ref_count, UINT, NULL),
+ SPEC(dump_refs, UINT, NULL),
+ SPEC(dump_refs_file, WSTR_OPT, NULL),
+ SPEC(malloc_stats, UINT, NULL),
+ SPEC(filesystem_encoding, WSTR, NULL),
+ SPEC(filesystem_errors, WSTR, NULL),
+ SPEC(pycache_prefix, WSTR_OPT, "pycache_prefix"),
+ SPEC(parse_argv, UINT, NULL),
+ SPEC(orig_argv, WSTR_LIST, "orig_argv"),
+ SPEC(argv, WSTR_LIST, "argv"),
+ SPEC(xoptions, WSTR_LIST, "_xoptions"),
+ SPEC(warnoptions, WSTR_LIST, "warnoptions"),
+ SPEC(site_import, UINT, NULL),
+ SPEC(bytes_warning, UINT, NULL),
+ SPEC(warn_default_encoding, UINT, NULL),
+ SPEC(inspect, UINT, NULL),
+ SPEC(interactive, UINT, NULL),
+ SPEC(optimization_level, UINT, NULL),
+ SPEC(parser_debug, UINT, NULL),
+ // config_get_sys_write_bytecode() gets sys.dont_write_bytecode
+ SPEC(write_bytecode, UINT, NULL),
+ SPEC(verbose, UINT, NULL),
+ SPEC(quiet, UINT, NULL),
+ SPEC(user_site_directory, UINT, NULL),
+ SPEC(configure_c_stdio, UINT, NULL),
+ SPEC(buffered_stdio, UINT, NULL),
+ SPEC(stdio_encoding, WSTR, NULL),
+ SPEC(stdio_errors, WSTR, NULL),
#ifdef MS_WINDOWS
- SPEC(legacy_windows_stdio, UINT),
+ SPEC(legacy_windows_stdio, UINT, NULL),
#endif
- SPEC(check_hash_pycs_mode, WSTR),
- SPEC(use_frozen_modules, UINT),
- SPEC(safe_path, UINT),
- SPEC(int_max_str_digits, INT),
- SPEC(cpu_count, INT),
- SPEC(pathconfig_warnings, UINT),
- SPEC(program_name, WSTR),
- SPEC(pythonpath_env, WSTR_OPT),
- SPEC(home, WSTR_OPT),
- SPEC(platlibdir, WSTR),
- SPEC(sys_path_0, WSTR_OPT),
- SPEC(module_search_paths_set, UINT),
- SPEC(module_search_paths, WSTR_LIST),
- SPEC(stdlib_dir, WSTR_OPT),
- SPEC(executable, WSTR_OPT),
- SPEC(base_executable, WSTR_OPT),
- SPEC(prefix, WSTR_OPT),
- SPEC(base_prefix, WSTR_OPT),
- SPEC(exec_prefix, WSTR_OPT),
- SPEC(base_exec_prefix, WSTR_OPT),
- SPEC(skip_source_first_line, UINT),
- SPEC(run_command, WSTR_OPT),
- SPEC(run_module, WSTR_OPT),
- SPEC(run_filename, WSTR_OPT),
- SPEC(_install_importlib, UINT),
- SPEC(_init_main, UINT),
- SPEC(_is_python_build, UINT),
+ SPEC(check_hash_pycs_mode, WSTR, NULL),
+ SPEC(use_frozen_modules, UINT, NULL),
+ SPEC(safe_path, UINT, NULL),
+ SPEC(int_max_str_digits, INT, NULL),
+ SPEC(cpu_count, INT, NULL),
+ SPEC(pathconfig_warnings, UINT, NULL),
+ SPEC(program_name, WSTR, NULL),
+ SPEC(pythonpath_env, WSTR_OPT, NULL),
+ SPEC(home, WSTR_OPT, NULL),
+ SPEC(platlibdir, WSTR, "platlibdir"),
+ SPEC(sys_path_0, WSTR_OPT, NULL),
+ SPEC(module_search_paths_set, UINT, NULL),
+ SPEC(module_search_paths, WSTR_LIST, "path"),
+ SPEC(stdlib_dir, WSTR_OPT, "_stdlib_dir"),
+ SPEC(executable, WSTR_OPT, "executable"),
+ SPEC(base_executable, WSTR_OPT, "_base_executable"),
+ SPEC(prefix, WSTR_OPT, "prefix"),
+ SPEC(base_prefix, WSTR_OPT, "base_prefix"),
+ SPEC(exec_prefix, WSTR_OPT, "exec_prefix"),
+ SPEC(base_exec_prefix, WSTR_OPT, "base_exec_prefix"),
+ SPEC(skip_source_first_line, UINT, NULL),
+ SPEC(run_command, WSTR_OPT, NULL),
+ SPEC(run_module, WSTR_OPT, NULL),
+ SPEC(run_filename, WSTR_OPT, NULL),
+ SPEC(_install_importlib, UINT, NULL),
+ SPEC(_init_main, UINT, NULL),
+ SPEC(_is_python_build, UINT, NULL),
#ifdef Py_STATS
- SPEC(_pystats, UINT),
+ SPEC(_pystats, UINT, NULL),
#endif
#ifdef Py_DEBUG
- SPEC(run_presite, WSTR_OPT),
+ SPEC(run_presite, WSTR_OPT, NULL),
#endif
- {NULL, 0, 0},
+ {NULL, 0, 0, NULL},
};
#undef SPEC
+// Forward declarations
+static PyObject*
+config_get(const PyConfig *config, const PyConfigSpec *spec,
+ int use_sys);
+
+
/* --- Command line options --------------------------------------- */
/* Short usage message (with %s for argv0) */
@@ -1051,48 +1059,12 @@ _PyConfig_AsDict(const PyConfig *config)
const PyConfigSpec *spec = PYCONFIG_SPEC;
for (; spec->name != NULL; spec++) {
- char *member = (char *)config + spec->offset;
- PyObject *obj;
- switch (spec->type) {
- case PyConfig_MEMBER_INT:
- case PyConfig_MEMBER_UINT:
- {
- int value = *(int*)member;
- obj = PyLong_FromLong(value);
- break;
- }
- case PyConfig_MEMBER_ULONG:
- {
- unsigned long value = *(unsigned long*)member;
- obj = PyLong_FromUnsignedLong(value);
- break;
- }
- case PyConfig_MEMBER_WSTR:
- case PyConfig_MEMBER_WSTR_OPT:
- {
- const wchar_t *wstr = *(const wchar_t**)member;
- if (wstr != NULL) {
- obj = PyUnicode_FromWideChar(wstr, -1);
- }
- else {
- obj = Py_NewRef(Py_None);
- }
- break;
- }
- case PyConfig_MEMBER_WSTR_LIST:
- {
- const PyWideStringList *list = (const PyWideStringList*)member;
- obj = _PyWideStringList_AsList(list);
- break;
- }
- default:
- Py_UNREACHABLE();
- }
-
+ PyObject *obj = config_get(config, spec, 0);
if (obj == NULL) {
Py_DECREF(dict);
return NULL;
}
+
int res = PyDict_SetItemString(dict, spec->name, obj);
Py_DECREF(obj);
if (res < 0) {
@@ -1271,6 +1243,66 @@ config_dict_get_wstrlist(PyObject *dict, const char *name, PyConfig *config,
}
+static int
+config_dict_get_xoptions(PyObject *dict, const char *name, PyConfig *config,
+ PyWideStringList *result)
+{
+ PyObject *xoptions = config_dict_get(dict, name);
+ if (xoptions == NULL) {
+ return -1;
+ }
+
+ if (!PyDict_CheckExact(xoptions)) {
+ Py_DECREF(xoptions);
+ config_dict_invalid_type(name);
+ return -1;
+ }
+
+ Py_ssize_t pos = 0;
+ PyObject *key, *value;
+ PyWideStringList wstrlist = _PyWideStringList_INIT;
+ while (PyDict_Next(xoptions, &pos, &key, &value)) {
+ PyObject *item;
+
+ if (value != Py_True) {
+ item = PyUnicode_FromFormat("%S=%S", key, value);
+ if (item == NULL) {
+ goto error;
+ }
+ }
+ else {
+ item = Py_NewRef(key);
+ }
+
+ wchar_t *wstr = PyUnicode_AsWideCharString(item, NULL);
+ Py_DECREF(item);
+ if (wstr == NULL) {
+ goto error;
+ }
+
+ PyStatus status = PyWideStringList_Append(&wstrlist, wstr);
+ PyMem_Free(wstr);
+ if (_PyStatus_EXCEPTION(status)) {
+ PyErr_NoMemory();
+ goto error;
+ }
+ }
+
+ if (_PyWideStringList_Copy(result, &wstrlist) < 0) {
+ PyErr_NoMemory();
+ goto error;
+ }
+ _PyWideStringList_Clear(&wstrlist);
+ Py_DECREF(xoptions);
+ return 0;
+
+error:
+ _PyWideStringList_Clear(&wstrlist);
+ Py_DECREF(xoptions);
+ return -1;
+}
+
+
int
_PyConfig_FromDict(PyConfig *config, PyObject *dict)
{
@@ -1331,9 +1363,17 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict)
}
case PyConfig_MEMBER_WSTR_LIST:
{
- if (config_dict_get_wstrlist(dict, spec->name, config,
- (PyWideStringList*)member) < 0) {
- return -1;
+ if (strcmp(spec->name, "xoptions") == 0) {
+ if (config_dict_get_xoptions(dict, spec->name, config,
+ (PyWideStringList*)member) < 0) {
+ return -1;
+ }
+ }
+ else {
+ if (config_dict_get_wstrlist(dict, spec->name, config,
+ (PyWideStringList*)member) < 0) {
+ return -1;
+ }
}
break;
}
@@ -3218,3 +3258,243 @@ _Py_DumpPathConfig(PyThreadState *tstate)
_PyErr_SetRaisedException(tstate, exc);
}
+
+
+// --- PyConfig_Get() -------------------------------------------------------
+
+static void*
+config_spec_get_member(const PyConfigSpec *spec, const PyConfig *config)
+{
+ return (char *)config + spec->offset;
+}
+
+
+static const PyConfigSpec*
+config_find_spec(const char *name)
+{
+ const PyConfigSpec *spec;
+ for (spec = PYCONFIG_SPEC; spec->name != NULL; spec++) {
+ if (strcmp(name, spec->name) == 0) {
+ return spec;
+ }
+ }
+ PyErr_Format(PyExc_ValueError,
+ "unknown config option name: %s", name);
+ return NULL;
+}
+
+
+static int
+config_add_xoption(PyObject *dict, const wchar_t *str)
+{
+ PyObject *name = NULL, *value = NULL;
+
+ const wchar_t *name_end = wcschr(str, L'=');
+ if (!name_end) {
+ name = PyUnicode_FromWideChar(str, -1);
+ if (name == NULL) {
+ goto error;
+ }
+ value = Py_NewRef(Py_True);
+ }
+ else {
+ name = PyUnicode_FromWideChar(str, name_end - str);
+ if (name == NULL) {
+ goto error;
+ }
+ value = PyUnicode_FromWideChar(name_end + 1, -1);
+ if (value == NULL) {
+ goto error;
+ }
+ }
+ if (PyDict_SetItem(dict, name, value) < 0) {
+ goto error;
+ }
+ Py_DECREF(name);
+ Py_DECREF(value);
+ return 0;
+
+error:
+ Py_XDECREF(name);
+ Py_XDECREF(value);
+ return -1;
+}
+
+
+PyObject*
+_PyConfig_CreateXOptionsDict(const PyConfig *config)
+{
+ PyObject *dict = PyDict_New();
+ if (dict == NULL) {
+ return NULL;
+ }
+
+ Py_ssize_t nxoption = config->xoptions.length;
+ wchar_t **xoptions = config->xoptions.items;
+ for (Py_ssize_t i=0; i < nxoption; i++) {
+ const wchar_t *option = xoptions[i];
+ if (config_add_xoption(dict, option) < 0) {
+ Py_DECREF(dict);
+ return NULL;
+ }
+ }
+ return dict;
+}
+
+
+static PyObject*
+config_get_sys(const char *name)
+{
+ PyObject *value = PySys_GetObject(name);
+ if (value == NULL) {
+ PyErr_Format(PyExc_RuntimeError, "lost sys.%s", name);
+ return NULL;
+ }
+ return Py_NewRef(value);
+}
+
+
+static int
+config_can_use_sys(void)
+{
+ return _PyRuntime.initialized;
+}
+
+
+static int
+config_get_sys_write_bytecode(const PyConfig *config, int *value)
+{
+ PyObject *attr = config_get_sys("dont_write_bytecode");
+ if (attr == NULL) {
+ return -1;
+ }
+
+ int is_true = PyObject_IsTrue(attr);
+ Py_DECREF(attr);
+ if (is_true < 0) {
+ return -1;
+ }
+ *value = (!is_true);
+ return 0;
+}
+
+
+static PyObject*
+config_get(const PyConfig *config, const PyConfigSpec *spec,
+ int use_sys)
+{
+ if (use_sys && config_can_use_sys()) {
+ if (spec->sys_attr != NULL) {
+ return config_get_sys(spec->sys_attr);
+ }
+
+ if (strcmp(spec->name, "write_bytecode") == 0) {
+ int value;
+ if (config_get_sys_write_bytecode(config, &value) < 0) {
+ return NULL;
+ }
+ return PyLong_FromLong(value);
+ }
+ }
+
+ char *member = config_spec_get_member(spec, config);
+ switch (spec->type) {
+ case PyConfig_MEMBER_INT:
+ case PyConfig_MEMBER_UINT:
+ {
+ int value = *(int *)member;
+ return PyLong_FromLong(value);
+ }
+
+ case PyConfig_MEMBER_ULONG:
+ {
+ unsigned long value = *(unsigned long *)member;
+ return PyLong_FromUnsignedLong(value);
+ }
+
+ case PyConfig_MEMBER_WSTR:
+ case PyConfig_MEMBER_WSTR_OPT:
+ {
+ wchar_t *wstr = *(wchar_t **)member;
+ if (wstr != NULL) {
+ return PyUnicode_FromWideChar(wstr, -1);
+ }
+ else {
+ return Py_NewRef(Py_None);
+ }
+ }
+
+ case PyConfig_MEMBER_WSTR_LIST:
+ {
+ if (strcmp(spec->name, "xoptions") == 0) {
+ return _PyConfig_CreateXOptionsDict(config);
+ }
+ else {
+ const PyWideStringList *list = (const PyWideStringList *)member;
+ return _PyWideStringList_AsList(list);
+ }
+ }
+ default:
+ PyErr_Format(PyExc_TypeError,
+ "config option %s is not a strings list", spec->name);
+ return NULL;
+ }
+}
+
+
+PyObject*
+PyConfig_Get(const char *name)
+{
+ const PyConfigSpec *spec = config_find_spec(name);
+ if (spec == NULL) {
+ return NULL;
+ }
+ const PyConfig *config = _Py_GetConfig();
+ return config_get(config, spec, 1);
+}
+
+
+int
+PyConfig_GetInt(const char *name, int *value)
+{
+ const PyConfigSpec *spec = config_find_spec(name);
+ if (spec == NULL) {
+ return -1;
+ }
+ const PyConfig *config = _Py_GetConfig();
+
+ if (config_can_use_sys() && strcmp(spec->name, "write_bytecode") == 0) {
+ if (config_get_sys_write_bytecode(config, value) < 0) {
+ return -1;
+ }
+ return 0;
+ }
+
+ char *member = config_spec_get_member(spec, config);
+
+ switch (spec->type) {
+ case PyConfig_MEMBER_INT:
+ case PyConfig_MEMBER_UINT:
+ *value = *(int *)member;
+ break;
+
+ case PyConfig_MEMBER_ULONG:
+ {
+ unsigned long ulong_value = *(unsigned long *)member;
+ if (ulong_value > (unsigned long)INT_MAX) {
+ PyErr_Format(PyExc_OverflowError,
+ "config option %s value doesn't fit into int",
+ spec->name);
+ return -1;
+ }
+ *value = (int)ulong_value;
+ break;
+ }
+
+ default:
+ PyErr_Format(PyExc_TypeError, "config option %s is not an int",
+ spec->name);
+ return -1;
+ }
+ return 0;
+}
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index c17de44731b703..c5247baafe8456 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -3462,64 +3462,6 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict)
return _PyStatus_ERR("can't initialize sys module");
}
-static int
-sys_add_xoption(PyObject *opts, const wchar_t *s)
-{
- PyObject *name, *value = NULL;
-
- const wchar_t *name_end = wcschr(s, L'=');
- if (!name_end) {
- name = PyUnicode_FromWideChar(s, -1);
- if (name == NULL) {
- goto error;
- }
- value = Py_NewRef(Py_True);
- }
- else {
- name = PyUnicode_FromWideChar(s, name_end - s);
- if (name == NULL) {
- goto error;
- }
- value = PyUnicode_FromWideChar(name_end + 1, -1);
- if (value == NULL) {
- goto error;
- }
- }
- if (PyDict_SetItem(opts, name, value) < 0) {
- goto error;
- }
- Py_DECREF(name);
- Py_DECREF(value);
- return 0;
-
-error:
- Py_XDECREF(name);
- Py_XDECREF(value);
- return -1;
-}
-
-
-static PyObject*
-sys_create_xoptions_dict(const PyConfig *config)
-{
- Py_ssize_t nxoption = config->xoptions.length;
- wchar_t * const * xoptions = config->xoptions.items;
- PyObject *dict = PyDict_New();
- if (dict == NULL) {
- return NULL;
- }
-
- for (Py_ssize_t i=0; i < nxoption; i++) {
- const wchar_t *option = xoptions[i];
- if (sys_add_xoption(dict, option) < 0) {
- Py_DECREF(dict);
- return NULL;
- }
- }
-
- return dict;
-}
-
// Update sys attributes for a new PyConfig configuration.
// This function also adds attributes that _PySys_InitCore() didn't add.
@@ -3566,7 +3508,7 @@ _PySys_UpdateConfig(PyThreadState *tstate)
COPY_LIST("orig_argv", config->orig_argv);
COPY_LIST("warnoptions", config->warnoptions);
- SET_SYS("_xoptions", sys_create_xoptions_dict(config));
+ SET_SYS("_xoptions", _PyConfig_CreateXOptionsDict(config));
const wchar_t *stdlibdir = _Py_GetStdlibDir();
if (stdlibdir != NULL) {