Skip to content

Commit 7ea38bf

Browse files
committed
pythongh-107954: Add PyInitConfig C API
Add PyInitConfig functions: * PyInitConfig_Python_New() * PyInitConfig_Isolated_New() * PyInitConfig_Free(config) * PyInitConfig_SetInt(config, key, value) * PyInitConfig_SetString(config, key, value) * PyInitConfig_SetList(config, key, length, items) * PyInitConfig_GetErrorMsg(config) Add also functions using it: * Py_InitializeFromInitConfig(config) * Py_ExitWithInitConfig(config) Changes: * Add Include/initconfig.h header.
1 parent 62405c7 commit 7ea38bf

File tree

6 files changed

+432
-36
lines changed

6 files changed

+432
-36
lines changed

Include/Python.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
#include "sliceobject.h"
8383
#include "cpython/cellobject.h"
8484
#include "iterobject.h"
85-
#include "cpython/initconfig.h"
85+
#include "initconfig.h"
8686
#include "pystate.h"
8787
#include "cpython/genobject.h"
8888
#include "descrobject.h"

Include/cpython/initconfig.h

+2-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
#ifndef Py_PYCORECONFIG_H
2-
#define Py_PYCORECONFIG_H
3-
#ifndef Py_LIMITED_API
4-
#ifdef __cplusplus
5-
extern "C" {
1+
#ifndef Py_CPYTHON_INITCONFIG_H
2+
# error "this header file must not be included directly"
63
#endif
74

85
/* --- PyStatus ----------------------------------------------- */
@@ -252,9 +249,3 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config,
252249
253250
See also PyConfig.orig_argv. */
254251
PyAPI_FUNC(void) Py_GetArgcArgv(int *argc, wchar_t ***argv);
255-
256-
#ifdef __cplusplus
257-
}
258-
#endif
259-
#endif /* !Py_LIMITED_API */
260-
#endif /* !Py_PYCORECONFIG_H */

Include/initconfig.h

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#ifndef Py_INITCONFIG_H
2+
#define Py_INITCONFIG_H
3+
#ifdef __cplusplus
4+
extern "C" {
5+
#endif
6+
7+
typedef struct PyInitConfig PyInitConfig;
8+
9+
// Create a new initialization configuration.
10+
// It must be freed by PyInitConfig_Free().
11+
// Return NULL on memory allocation failure.
12+
PyAPI_FUNC(PyInitConfig*) PyInitConfig_Python_New(void);
13+
PyAPI_FUNC(PyInitConfig*) PyInitConfig_Isolated_New(void);
14+
15+
// Free memory of a initialization configuration.
16+
PyAPI_FUNC(void) PyInitConfig_Free(PyInitConfig *config);
17+
18+
// Set an integer configuration option.
19+
// Return 0 on success, or return -1 on error.
20+
PyAPI_FUNC(int) PyInitConfig_SetInt(
21+
PyInitConfig *config,
22+
const char *key,
23+
int64_t value);
24+
25+
// Set a string configuration option.
26+
// Return 0 on success, or return -1 on error.
27+
PyAPI_FUNC(int) PyInitConfig_SetString(
28+
PyInitConfig *config,
29+
const char *key,
30+
const wchar_t *value);
31+
32+
// Set a string configuration option.
33+
// Return 0 on success, or return -1 on error.
34+
PyAPI_FUNC(int) PyInitConfig_SetList(
35+
PyInitConfig *config,
36+
const char *key,
37+
size_t length,
38+
wchar_t **items);
39+
40+
// Initialize Python from the initialization configuration.
41+
// Return 0 on success.
42+
// Return -1 if Python wants to exit and on error
43+
PyAPI_FUNC(int) Py_InitializeFromInitConfig(PyInitConfig *config);
44+
45+
// Get the current error message.
46+
// Return a UTF-8 string allocated by malloc(). It must be released by free().
47+
// Return NULL on memory allocation failure.
48+
PyAPI_FUNC(char*) PyInitConfig_GetErrorMsg(PyInitConfig* config);
49+
50+
// Exit Python and free memory of a initialization configuration.
51+
// The function does not return.
52+
PyAPI_FUNC(void) _Py_NO_RETURN Py_ExitWithInitConfig(PyInitConfig *config);
53+
54+
55+
#ifndef Py_LIMITED_API
56+
# define Py_CPYTHON_INITCONFIG_H
57+
# include "cpython/initconfig.h"
58+
# undef Py_CPYTHON_INITCONFIG_H
59+
#endif
60+
61+
#ifdef __cplusplus
62+
}
63+
#endif
64+
#endif // !Py_INITCONFIG_H

Lib/test/test_embed.py

+29-16
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,14 @@ def test_ucnhash_capi_reset(self):
393393
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
394394
self.assertEqual(out, '9\n' * INIT_LOOPS)
395395

396+
397+
def config_dev_mode(preconfig, config):
398+
preconfig['allocator'] = PYMEM_ALLOCATOR_DEBUG
399+
config['dev_mode'] = 1
400+
config['warnoptions'] = ['default']
401+
config['faulthandler'] = 1
402+
403+
396404
class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
397405
maxDiff = 4096
398406
UTF8_MODE_ERRORS = ('surrogatepass' if MS_WINDOWS else 'surrogateescape')
@@ -510,7 +518,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
510518
'check_hash_pycs_mode': 'default',
511519
'pathconfig_warnings': 1,
512520
'_init_main': 1,
513-
'use_frozen_modules': not support.Py_DEBUG,
521+
'use_frozen_modules': int(not support.Py_DEBUG),
514522
'safe_path': 0,
515523
'_is_python_build': IGNORE_CONFIG,
516524
}
@@ -990,33 +998,26 @@ def test_init_env_dev_mode_alloc(self):
990998
api=API_COMPAT)
991999

9921000
def test_init_dev_mode(self):
993-
preconfig = {
994-
'allocator': PYMEM_ALLOCATOR_DEBUG,
995-
}
1001+
preconfig = {}
9961002
config = {
997-
'faulthandler': 1,
9981003
'dev_mode': 1,
999-
'warnoptions': ['default'],
10001004
}
1005+
config_dev_mode(preconfig, config)
10011006
self.check_all_configs("test_init_dev_mode", config, preconfig,
10021007
api=API_PYTHON)
10031008

10041009
def test_preinit_parse_argv(self):
10051010
# Pre-initialize implicitly using argv: make sure that -X dev
10061011
# is used to configure the allocation in preinitialization
1007-
preconfig = {
1008-
'allocator': PYMEM_ALLOCATOR_DEBUG,
1009-
}
1012+
preconfig = {}
10101013
config = {
10111014
'argv': ['script.py'],
10121015
'orig_argv': ['python3', '-X', 'dev', '-P', 'script.py'],
10131016
'run_filename': os.path.abspath('script.py'),
1014-
'dev_mode': 1,
1015-
'faulthandler': 1,
1016-
'warnoptions': ['default'],
10171017
'xoptions': ['dev'],
10181018
'safe_path': 1,
10191019
}
1020+
config_dev_mode(preconfig, config)
10201021
self.check_all_configs("test_preinit_parse_argv", config, preconfig,
10211022
api=API_PYTHON)
10221023

@@ -1615,16 +1616,15 @@ def test_init_warnoptions(self):
16151616
'ignore:::PySys_AddWarnOption2', # PySys_AddWarnOption()
16161617
'ignore:::PyConfig_BeforeRead', # PyConfig.warnoptions
16171618
'ignore:::PyConfig_AfterRead'] # PyWideStringList_Append()
1618-
preconfig = dict(allocator=PYMEM_ALLOCATOR_DEBUG)
1619+
preconfig = {}
16191620
config = {
1620-
'dev_mode': 1,
1621-
'faulthandler': 1,
16221621
'bytes_warning': 1,
1623-
'warnoptions': warnoptions,
16241622
'orig_argv': ['python3',
16251623
'-Wignore:::cmdline1',
16261624
'-Wignore:::cmdline2'],
16271625
}
1626+
config_dev_mode(preconfig, config)
1627+
config['warnoptions'] = warnoptions
16281628
self.check_all_configs("test_init_warnoptions", config, preconfig,
16291629
api=API_PYTHON)
16301630

@@ -1637,6 +1637,19 @@ def test_init_set_config(self):
16371637
self.check_all_configs("test_init_set_config", config,
16381638
api=API_ISOLATED)
16391639

1640+
def test_initconfig_api(self):
1641+
preconfig = {}
1642+
config = {
1643+
'dev_mode': 1,
1644+
'pycache_prefix': 'conf_pycache_prefix',
1645+
'argv': ['-c'],
1646+
'orig_argv': ['./_testembed', '-c', 'pass'],
1647+
'run_command': 'pass\n',
1648+
}
1649+
config_dev_mode(preconfig, config)
1650+
self.check_all_configs("test_initconfig_api", config, preconfig,
1651+
api=API_PYTHON)
1652+
16401653
def test_get_argc_argv(self):
16411654
self.run_embedded_interpreter("test_get_argc_argv")
16421655
# ignore output

Programs/_testembed.c

+43
Original file line numberDiff line numberDiff line change
@@ -1729,6 +1729,48 @@ static int test_init_set_config(void)
17291729
}
17301730

17311731

1732+
static int test_initconfig_api(void)
1733+
{
1734+
PyInitConfig *config = PyInitConfig_Python_New();
1735+
if (config == NULL) {
1736+
printf("Init allocation error\n");
1737+
return 1;
1738+
}
1739+
1740+
if (PyInitConfig_SetInt(config, "dev_mode", 1) < 0) {
1741+
goto error;
1742+
}
1743+
1744+
wchar_t *argv[] = {PROGRAM_NAME, L"-c", L"pass"};
1745+
if (PyInitConfig_SetList(config, "argv", Py_ARRAY_LENGTH(argv), argv) < 0) {
1746+
goto error;
1747+
}
1748+
1749+
if (PyInitConfig_SetInt(config, "hash_seed", 10) < 0) {
1750+
goto error;
1751+
}
1752+
if (PyInitConfig_SetString(config, "program_name", PROGRAM_NAME) < 0) {
1753+
goto error;
1754+
}
1755+
if (PyInitConfig_SetString(config, "pycache_prefix", L"conf_pycache_prefix") < 0) {
1756+
goto error;
1757+
}
1758+
1759+
if (Py_InitializeFromInitConfig(config) < 0) {
1760+
Py_ExitWithInitConfig(config);
1761+
}
1762+
PyInitConfig_Free(config);
1763+
1764+
dump_config();
1765+
Py_Finalize();
1766+
return 0;
1767+
1768+
error:
1769+
printf("Init failed:\n");
1770+
Py_ExitWithInitConfig(config);
1771+
}
1772+
1773+
17321774
static void configure_init_main(PyConfig *config)
17331775
{
17341776
wchar_t* argv[] = {
@@ -2131,6 +2173,7 @@ static struct TestCase TestCases[] = {
21312173
{"test_init_is_python_build", test_init_is_python_build},
21322174
{"test_init_warnoptions", test_init_warnoptions},
21332175
{"test_init_set_config", test_init_set_config},
2176+
{"test_initconfig_api", test_initconfig_api},
21342177
{"test_run_main", test_run_main},
21352178
{"test_run_main_loop", test_run_main_loop},
21362179
{"test_get_argc_argv", test_get_argc_argv},

0 commit comments

Comments
 (0)