Skip to content

Commit 8a52253

Browse files
authored
Add PyConfig_Get() (#128)
1 parent b1b2071 commit 8a52253

File tree

4 files changed

+296
-2
lines changed

4 files changed

+296
-2
lines changed

docs/api.rst

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,17 @@ Python 3.14
177177

178178
See `Py_fclose() documentation <https://docs.python.org/dev/c-api/sys.html#c.Py_fclose>`__.
179179

180+
.. c:function:: PyObject* PyConfig_Get(const char *name)
181+
182+
See `PyConfig_Get() documentation <https://docs.python.org/dev/c-api/init_config.html#c.PyConfig_Get>`__.
183+
184+
.. c:function:: int PyConfig_GetInt(const char *name, int *value)
185+
186+
See `PyConfig_GetInt() documentation <https://docs.python.org/dev/c-api/init_config.html#c.PyConfig_GetInt>`__.
187+
180188

181189
Not supported:
182190

183-
* ``PyConfig_Get()``
184-
* ``PyConfig_GetInt()``
185191
* ``PyConfig_Names()``
186192
* ``PyConfig_Set()``
187193
* ``PyInitConfig_AddModule()``

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Changelog
22
=========
33

4+
* 2025-01-19: Add ``PyConfig_Get()`` functions.
45
* 2025-01-06: Add ``Py_fopen()`` and ``Py_fclose()`` functions.
56
* 2024-12-16: Add ``structmember.h`` constants:
67

pythoncapi_compat.h

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ extern "C" {
1919
#endif
2020

2121
#include <Python.h>
22+
#include <stddef.h> // offsetof()
2223

2324
// Python 3.11.0b4 added PyFrame_Back() to Python.h
2425
#if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION)
@@ -1974,6 +1975,229 @@ int Py_fclose(FILE *file)
19741975
#endif
19751976

19761977

1978+
#if 0x03090000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION)
1979+
static inline PyObject*
1980+
PyConfig_Get(const char *name)
1981+
{
1982+
typedef enum {
1983+
_PyConfig_MEMBER_INT,
1984+
_PyConfig_MEMBER_UINT,
1985+
_PyConfig_MEMBER_ULONG,
1986+
_PyConfig_MEMBER_BOOL,
1987+
_PyConfig_MEMBER_WSTR,
1988+
_PyConfig_MEMBER_WSTR_OPT,
1989+
_PyConfig_MEMBER_WSTR_LIST,
1990+
} PyConfigMemberType;
1991+
1992+
typedef struct {
1993+
const char *name;
1994+
size_t offset;
1995+
PyConfigMemberType type;
1996+
const char *sys_attr;
1997+
} PyConfigSpec;
1998+
1999+
#define PYTHONCAPI_COMPAT_SPEC(MEMBER, TYPE, sys_attr) \
2000+
{#MEMBER, offsetof(PyConfig, MEMBER), \
2001+
_PyConfig_MEMBER_##TYPE, sys_attr}
2002+
2003+
static const PyConfigSpec config_spec[] = {
2004+
PYTHONCAPI_COMPAT_SPEC(argv, WSTR_LIST, "argv"),
2005+
PYTHONCAPI_COMPAT_SPEC(base_exec_prefix, WSTR_OPT, "base_exec_prefix"),
2006+
PYTHONCAPI_COMPAT_SPEC(base_executable, WSTR_OPT, "_base_executable"),
2007+
PYTHONCAPI_COMPAT_SPEC(base_prefix, WSTR_OPT, "base_prefix"),
2008+
PYTHONCAPI_COMPAT_SPEC(bytes_warning, UINT, _Py_NULL),
2009+
PYTHONCAPI_COMPAT_SPEC(exec_prefix, WSTR_OPT, "exec_prefix"),
2010+
PYTHONCAPI_COMPAT_SPEC(executable, WSTR_OPT, "executable"),
2011+
PYTHONCAPI_COMPAT_SPEC(inspect, BOOL, _Py_NULL),
2012+
#if 0x030C0000 <= PY_VERSION_HEX
2013+
PYTHONCAPI_COMPAT_SPEC(int_max_str_digits, UINT, _Py_NULL),
2014+
#endif
2015+
PYTHONCAPI_COMPAT_SPEC(interactive, BOOL, _Py_NULL),
2016+
PYTHONCAPI_COMPAT_SPEC(module_search_paths, WSTR_LIST, "path"),
2017+
PYTHONCAPI_COMPAT_SPEC(optimization_level, UINT, _Py_NULL),
2018+
PYTHONCAPI_COMPAT_SPEC(parser_debug, BOOL, _Py_NULL),
2019+
PYTHONCAPI_COMPAT_SPEC(platlibdir, WSTR, "platlibdir"),
2020+
PYTHONCAPI_COMPAT_SPEC(prefix, WSTR_OPT, "prefix"),
2021+
PYTHONCAPI_COMPAT_SPEC(pycache_prefix, WSTR_OPT, "pycache_prefix"),
2022+
PYTHONCAPI_COMPAT_SPEC(quiet, BOOL, _Py_NULL),
2023+
#if 0x030B0000 <= PY_VERSION_HEX
2024+
PYTHONCAPI_COMPAT_SPEC(stdlib_dir, WSTR_OPT, "_stdlib_dir"),
2025+
#endif
2026+
PYTHONCAPI_COMPAT_SPEC(use_environment, BOOL, _Py_NULL),
2027+
PYTHONCAPI_COMPAT_SPEC(verbose, UINT, _Py_NULL),
2028+
PYTHONCAPI_COMPAT_SPEC(warnoptions, WSTR_LIST, "warnoptions"),
2029+
PYTHONCAPI_COMPAT_SPEC(write_bytecode, BOOL, _Py_NULL),
2030+
PYTHONCAPI_COMPAT_SPEC(xoptions, WSTR_LIST, "_xoptions"),
2031+
PYTHONCAPI_COMPAT_SPEC(buffered_stdio, BOOL, _Py_NULL),
2032+
PYTHONCAPI_COMPAT_SPEC(check_hash_pycs_mode, WSTR, _Py_NULL),
2033+
#if 0x030B0000 <= PY_VERSION_HEX
2034+
PYTHONCAPI_COMPAT_SPEC(code_debug_ranges, BOOL, _Py_NULL),
2035+
#endif
2036+
PYTHONCAPI_COMPAT_SPEC(configure_c_stdio, BOOL, _Py_NULL),
2037+
#if 0x030D0000 <= PY_VERSION_HEX
2038+
PYTHONCAPI_COMPAT_SPEC(cpu_count, INT, _Py_NULL),
2039+
#endif
2040+
PYTHONCAPI_COMPAT_SPEC(dev_mode, BOOL, _Py_NULL),
2041+
PYTHONCAPI_COMPAT_SPEC(dump_refs, BOOL, _Py_NULL),
2042+
#if 0x030B0000 <= PY_VERSION_HEX
2043+
PYTHONCAPI_COMPAT_SPEC(dump_refs_file, WSTR_OPT, _Py_NULL),
2044+
#endif
2045+
#ifdef Py_GIL_DISABLED
2046+
PYTHONCAPI_COMPAT_SPEC(enable_gil, INT, _Py_NULL),
2047+
#endif
2048+
PYTHONCAPI_COMPAT_SPEC(faulthandler, BOOL, _Py_NULL),
2049+
PYTHONCAPI_COMPAT_SPEC(filesystem_encoding, WSTR, _Py_NULL),
2050+
PYTHONCAPI_COMPAT_SPEC(filesystem_errors, WSTR, _Py_NULL),
2051+
PYTHONCAPI_COMPAT_SPEC(hash_seed, ULONG, _Py_NULL),
2052+
PYTHONCAPI_COMPAT_SPEC(home, WSTR_OPT, _Py_NULL),
2053+
PYTHONCAPI_COMPAT_SPEC(import_time, BOOL, _Py_NULL),
2054+
PYTHONCAPI_COMPAT_SPEC(install_signal_handlers, BOOL, _Py_NULL),
2055+
PYTHONCAPI_COMPAT_SPEC(isolated, BOOL, _Py_NULL),
2056+
#ifdef MS_WINDOWS
2057+
PYTHONCAPI_COMPAT_SPEC(legacy_windows_stdio, BOOL, _Py_NULL),
2058+
#endif
2059+
PYTHONCAPI_COMPAT_SPEC(malloc_stats, BOOL, _Py_NULL),
2060+
#if 0x030A0000 <= PY_VERSION_HEX
2061+
PYTHONCAPI_COMPAT_SPEC(orig_argv, WSTR_LIST, "orig_argv"),
2062+
#endif
2063+
PYTHONCAPI_COMPAT_SPEC(parse_argv, BOOL, _Py_NULL),
2064+
PYTHONCAPI_COMPAT_SPEC(pathconfig_warnings, BOOL, _Py_NULL),
2065+
#if 0x030C0000 <= PY_VERSION_HEX
2066+
PYTHONCAPI_COMPAT_SPEC(perf_profiling, UINT, _Py_NULL),
2067+
#endif
2068+
PYTHONCAPI_COMPAT_SPEC(program_name, WSTR, _Py_NULL),
2069+
PYTHONCAPI_COMPAT_SPEC(run_command, WSTR_OPT, _Py_NULL),
2070+
PYTHONCAPI_COMPAT_SPEC(run_filename, WSTR_OPT, _Py_NULL),
2071+
PYTHONCAPI_COMPAT_SPEC(run_module, WSTR_OPT, _Py_NULL),
2072+
#if 0x030B0000 <= PY_VERSION_HEX
2073+
PYTHONCAPI_COMPAT_SPEC(safe_path, BOOL, _Py_NULL),
2074+
#endif
2075+
PYTHONCAPI_COMPAT_SPEC(show_ref_count, BOOL, _Py_NULL),
2076+
PYTHONCAPI_COMPAT_SPEC(site_import, BOOL, _Py_NULL),
2077+
PYTHONCAPI_COMPAT_SPEC(skip_source_first_line, BOOL, _Py_NULL),
2078+
PYTHONCAPI_COMPAT_SPEC(stdio_encoding, WSTR, _Py_NULL),
2079+
PYTHONCAPI_COMPAT_SPEC(stdio_errors, WSTR, _Py_NULL),
2080+
PYTHONCAPI_COMPAT_SPEC(tracemalloc, UINT, _Py_NULL),
2081+
#if 0x030B0000 <= PY_VERSION_HEX
2082+
PYTHONCAPI_COMPAT_SPEC(use_frozen_modules, BOOL, _Py_NULL),
2083+
#endif
2084+
PYTHONCAPI_COMPAT_SPEC(use_hash_seed, BOOL, _Py_NULL),
2085+
#if 0x030D0000 <= PY_VERSION_HEX && defined(__APPLE__)
2086+
PYTHONCAPI_COMPAT_SPEC(use_system_logger, BOOL, _Py_NULL),
2087+
#endif
2088+
PYTHONCAPI_COMPAT_SPEC(user_site_directory, BOOL, _Py_NULL),
2089+
#if 0x030A0000 <= PY_VERSION_HEX
2090+
PYTHONCAPI_COMPAT_SPEC(warn_default_encoding, BOOL, _Py_NULL),
2091+
#endif
2092+
};
2093+
2094+
#undef PYTHONCAPI_COMPAT_SPEC
2095+
2096+
const PyConfigSpec *spec;
2097+
int found = 0;
2098+
for (size_t i=0; i < Py_ARRAY_LENGTH(config_spec); i++) {
2099+
spec = &config_spec[i];
2100+
if (strcmp(spec->name, name) == 0) {
2101+
found = 1;
2102+
break;
2103+
}
2104+
}
2105+
if (found) {
2106+
if (spec->sys_attr != NULL) {
2107+
PyObject *value = PySys_GetObject(spec->sys_attr);
2108+
if (value == NULL) {
2109+
PyErr_Format(PyExc_RuntimeError, "lost sys.%s", spec->sys_attr);
2110+
return NULL;
2111+
}
2112+
return Py_NewRef(value);
2113+
}
2114+
2115+
extern const PyConfig* _Py_GetConfig(void);
2116+
const PyConfig *config = _Py_GetConfig();
2117+
void *member = (char *)config + spec->offset;
2118+
switch (spec->type) {
2119+
case _PyConfig_MEMBER_INT:
2120+
case _PyConfig_MEMBER_UINT:
2121+
{
2122+
int value = *(int *)member;
2123+
return PyLong_FromLong(value);
2124+
}
2125+
case _PyConfig_MEMBER_BOOL:
2126+
{
2127+
int value = *(int *)member;
2128+
return PyBool_FromLong(value != 0);
2129+
}
2130+
case _PyConfig_MEMBER_ULONG:
2131+
{
2132+
unsigned long value = *(unsigned long *)member;
2133+
return PyLong_FromUnsignedLong(value);
2134+
}
2135+
case _PyConfig_MEMBER_WSTR:
2136+
case _PyConfig_MEMBER_WSTR_OPT:
2137+
{
2138+
wchar_t *wstr = *(wchar_t **)member;
2139+
if (wstr != NULL) {
2140+
return PyUnicode_FromWideChar(wstr, -1);
2141+
}
2142+
else {
2143+
return Py_NewRef(Py_None);
2144+
}
2145+
}
2146+
case _PyConfig_MEMBER_WSTR_LIST:
2147+
{
2148+
const PyWideStringList *list = (const PyWideStringList *)member;
2149+
PyObject *tuple = PyTuple_New(list->length);
2150+
if (tuple == NULL) {
2151+
return NULL;
2152+
}
2153+
2154+
for (Py_ssize_t i = 0; i < list->length; i++) {
2155+
PyObject *item = PyUnicode_FromWideChar(list->items[i], -1);
2156+
if (item == NULL) {
2157+
Py_DECREF(tuple);
2158+
return NULL;
2159+
}
2160+
PyTuple_SET_ITEM(tuple, i, item);
2161+
}
2162+
return tuple;
2163+
}
2164+
default:
2165+
Py_UNREACHABLE();
2166+
}
2167+
}
2168+
2169+
PyErr_Format(PyExc_ValueError, "unknown config option name: %s", name);
2170+
return NULL;
2171+
}
2172+
2173+
static inline int
2174+
PyConfig_GetInt(const char *name, int *value)
2175+
{
2176+
PyObject *obj = PyConfig_Get(name);
2177+
if (obj == NULL) {
2178+
return -1;
2179+
}
2180+
2181+
if (!PyLong_Check(obj)) {
2182+
Py_DECREF(obj);
2183+
PyErr_Format(PyExc_TypeError, "config option %s is not an int", name);
2184+
return -1;
2185+
}
2186+
2187+
int as_int = PyLong_AsInt(obj);
2188+
Py_DECREF(obj);
2189+
if (as_int == -1 && PyErr_Occurred()) {
2190+
PyErr_Format(PyExc_OverflowError,
2191+
"config option %s value does not fit into a C int", name);
2192+
return -1;
2193+
}
2194+
2195+
*value = as_int;
2196+
return 0;
2197+
}
2198+
#endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION)
2199+
2200+
19772201
#ifdef __cplusplus
19782202
}
19792203
#endif

tests/test_pythoncapi_compat_cext.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2112,6 +2112,66 @@ test_file(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
21122112
}
21132113

21142114

2115+
#if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
2116+
static PyObject *
2117+
test_config(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
2118+
{
2119+
// Test PyConfig_Get()
2120+
PyObject *sys = PyImport_ImportModule("sys");
2121+
if (sys == _Py_NULL) {
2122+
return _Py_NULL;
2123+
}
2124+
2125+
PyObject *obj = PyConfig_Get("argv");
2126+
PyObject *sys_attr = PyObject_GetAttrString(sys, "argv");
2127+
assert(obj == sys_attr);
2128+
Py_DECREF(obj);
2129+
Py_DECREF(sys_attr);
2130+
2131+
obj = PyConfig_Get("module_search_paths");
2132+
sys_attr = PyObject_GetAttrString(sys, "path");
2133+
assert(obj == sys_attr);
2134+
Py_DECREF(obj);
2135+
Py_DECREF(sys_attr);
2136+
2137+
obj = PyConfig_Get("xoptions");
2138+
sys_attr = PyObject_GetAttrString(sys, "_xoptions");
2139+
assert(obj == sys_attr);
2140+
Py_DECREF(obj);
2141+
Py_DECREF(sys_attr);
2142+
2143+
obj = PyConfig_Get("use_environment");
2144+
assert(PyBool_Check(obj));
2145+
Py_DECREF(obj);
2146+
2147+
obj = PyConfig_Get("verbose");
2148+
assert(PyLong_Check(obj));
2149+
Py_DECREF(obj);
2150+
2151+
assert(PyConfig_Get("nonexistent") == NULL);
2152+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
2153+
PyErr_Clear();
2154+
2155+
// Test PyConfig_GetInt()
2156+
int value = -3;
2157+
2158+
assert(PyConfig_GetInt("verbose", &value) == 0);
2159+
assert(value >= 0);
2160+
2161+
assert(PyConfig_GetInt("argv", &value) == -1);
2162+
assert(PyErr_ExceptionMatches(PyExc_TypeError));
2163+
PyErr_Clear();
2164+
2165+
assert(PyConfig_GetInt("nonexistent", &value) == -1);
2166+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
2167+
PyErr_Clear();
2168+
2169+
Py_DECREF(sys);
2170+
Py_RETURN_NONE;
2171+
}
2172+
#endif
2173+
2174+
21152175
static struct PyMethodDef methods[] = {
21162176
{"test_object", test_object, METH_NOARGS, _Py_NULL},
21172177
{"test_py_is", test_py_is, METH_NOARGS, _Py_NULL},
@@ -2160,6 +2220,9 @@ static struct PyMethodDef methods[] = {
21602220
{"test_long_stdint", test_long_stdint, METH_NOARGS, _Py_NULL},
21612221
{"test_structmember", test_structmember, METH_NOARGS, _Py_NULL},
21622222
{"test_file", test_file, METH_NOARGS, _Py_NULL},
2223+
#if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
2224+
{"test_config", test_config, METH_NOARGS, _Py_NULL},
2225+
#endif
21632226
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
21642227
};
21652228

0 commit comments

Comments
 (0)