From 36dcd345c14705e52b6af605ddc2f57e19efe2e3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 1 Dec 2023 20:13:30 +0100 Subject: [PATCH] gh-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. --- Doc/c-api/init_config.rst | 47 ++ Doc/whatsnew/3.13.rst | 9 + Include/cpython/initconfig.h | 18 + Include/internal/pycore_initconfig.h | 2 +- Lib/test/_test_embed_set_config.py | 7 +- Lib/test/test_capi/test_config.py | 105 ++++ Lib/test/test_embed.py | 36 +- ...-12-01-21-13-32.gh-issue-107954.GO4oND.rst | 6 + Modules/Setup.stdlib.in | 2 +- Modules/_testcapi/config.c | 45 ++ Modules/_testcapi/parts.h | 1 + Modules/_testcapimodule.c | 3 + Python/initconfig.c | 505 ++++++++++++++---- Python/sysmodule.c | 60 +-- 14 files changed, 653 insertions(+), 193 deletions(-) create mode 100644 Lib/test/test_capi/test_config.py create mode 100644 Misc/NEWS.d/next/C API/2023-12-01-21-13-32.gh-issue-107954.GO4oND.rst create mode 100644 Modules/_testcapi/config.c diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index 47a8fbb2cd9c97..6eb6b98dd290b8 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -1602,6 +1602,53 @@ 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:: int PyConfig_Get(const char *name, PyObject **value) + + 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 ``0`` and set *\*value* on success. + * Raise an exception and return ``-1`` on error. + + +.. c:function:: int PyConfig_GetInt(const char *name, int *value) + + Similar to :c:func:`PyConfig_Get`, but get the value as a C int. + + +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/Python/initconfig.c b/Python/initconfig.c index d7f3195ed5fcf0..8963ed42e621f1 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -40,93 +40,100 @@ 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), + 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 +1058,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 +1242,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 +1362,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 +3257,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) {