Skip to content

Commit

Permalink
pythongh-107954: Add PyConfig_Get() function
Browse files Browse the repository at this point in the history
Add PyConfig_Get() and PyConfig_GetInt() functions to get the current
Python configuration.

_PyConfig_AsDict() now converts PyConfig.xoptions as a dictionary.
  • Loading branch information
vstinner committed Dec 1, 2023
1 parent 5c5022b commit c20baf0
Show file tree
Hide file tree
Showing 12 changed files with 629 additions and 174 deletions.
47 changes: 47 additions & 0 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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()
================
Expand Down
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------------
Expand Down Expand Up @@ -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.

Expand Down
18 changes: 18 additions & 0 deletions Include/cpython/initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
105 changes: 105 additions & 0 deletions Lib/test/test_capi/test_config.py
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Add functions to get the current Python configuration:

* :c:func:`PyConfig_Get`
* :c:func:`PyConfig_GetInt`

Patch by Victor Stinner.
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
45 changes: 45 additions & 0 deletions Modules/_testcapi/config.c
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions Modules/_testcapi/parts.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit c20baf0

Please sign in to comment.