Skip to content

Commit 4bee604

Browse files
committed
gh-107954: Add PyInitConfig C API
Add PyInitConfig functions: * PyInitConfig_Python_New() * PyInitConfig_Isolated_New() * PyInitConfig_Free(config) * PyInitConfig_SetInt(config, key, value) * PyInitConfig_SetStr(config, key, value) * PyInitConfig_SetWStr(config, key, value) * PyInitConfig_SetStrList(config, key, length, items) * PyInitConfig_SetWStrList(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 038c356 commit 4bee604

File tree

8 files changed

+572
-56
lines changed

8 files changed

+572
-56
lines changed

Include/Python.h

Lines changed: 1 addition & 1 deletion
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

Lines changed: 2 additions & 11 deletions
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

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
//
13+
// PyInitConfig_Python_New() has a Python configuration by default.
14+
// PyInitConfig_Isolated_New() has an Isolated configuration by default.
15+
PyAPI_FUNC(PyInitConfig*) PyInitConfig_Python_New(void);
16+
PyAPI_FUNC(PyInitConfig*) PyInitConfig_Isolated_New(void);
17+
18+
// Free memory of a initialization configuration.
19+
PyAPI_FUNC(void) PyInitConfig_Free(PyInitConfig *config);
20+
21+
// Set an integer configuration option.
22+
// Return 0 on success, or return -1 on error.
23+
PyAPI_FUNC(int) PyInitConfig_SetInt(
24+
PyInitConfig *config,
25+
const char *key,
26+
int64_t value);
27+
28+
// Set a string configuration option from a bytes string.
29+
//
30+
// The bytes string is decoded by Py_DecodeLocale(). Preinitialize Python if
31+
// needed to ensure that encodings are properly configured.
32+
//
33+
// Return 0 on success, or return -1 on error.
34+
PyAPI_FUNC(int) PyInitConfig_SetStr(
35+
PyInitConfig *config,
36+
const char *key,
37+
const char *value);
38+
39+
// Set a string configuration option from a wide string.
40+
// Preinitialize Python if needed.
41+
// Return 0 on success, or return -1 on error.
42+
PyAPI_FUNC(int) PyInitConfig_SetWStr(
43+
PyInitConfig *config,
44+
const char *key,
45+
const wchar_t *value);
46+
47+
// Set a string list configuration option from bytes strings.
48+
//
49+
// The bytes strings are decoded by Py_DecodeLocale(). Preinitialize Python if
50+
// needed to ensure that encodings are properly configured.
51+
//
52+
// Return 0 on success, or return -1 on error.
53+
PyAPI_FUNC(int) PyInitConfig_SetStrList(
54+
PyInitConfig *config,
55+
const char *key,
56+
size_t length,
57+
char * const *items);
58+
59+
// Set a string list configuration option from a wide strings.
60+
// Preinitialize Python if needed.
61+
// Return 0 on success, or return -1 on error.
62+
PyAPI_FUNC(int) PyInitConfig_SetWStrList(
63+
PyInitConfig *config,
64+
const char *key,
65+
size_t length,
66+
wchar_t * const *items);
67+
68+
// Initialize Python from the initialization configuration.
69+
// Return 0 on success.
70+
// Return -1 if Python wants to exit and on error
71+
PyAPI_FUNC(int) Py_InitializeFromInitConfig(PyInitConfig *config);
72+
73+
// Get the current error message.
74+
// Return a UTF-8 string allocated by malloc(). It must be released by free().
75+
// Return NULL on memory allocation failure.
76+
PyAPI_FUNC(char*) PyInitConfig_GetErrorMsg(PyInitConfig* config);
77+
78+
// Exit Python and free memory of a initialization configuration.
79+
// The function does not return.
80+
PyAPI_FUNC(void) _Py_NO_RETURN Py_ExitWithInitConfig(PyInitConfig *config);
81+
82+
83+
#ifndef Py_LIMITED_API
84+
# define Py_CPYTHON_INITCONFIG_H
85+
# include "cpython/initconfig.h"
86+
# undef Py_CPYTHON_INITCONFIG_H
87+
#endif
88+
89+
#ifdef __cplusplus
90+
}
91+
#endif
92+
#endif // !Py_INITCONFIG_H

Include/internal/pycore_initconfig.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ extern int _PyWideStringList_Copy(PyWideStringList *list,
6262
extern PyStatus _PyWideStringList_Extend(PyWideStringList *list,
6363
const PyWideStringList *list2);
6464
extern PyObject* _PyWideStringList_AsList(const PyWideStringList *list);
65+
extern PyStatus _PyWideStringList_FromBytes(
66+
PyWideStringList *list,
67+
Py_ssize_t length,
68+
char * const *items);
6569

6670

6771
/* --- _PyArgv ---------------------------------------------------- */

Lib/test/test_embed.py

Lines changed: 31 additions & 16 deletions
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,21 @@ 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+
'xoptions': ['faulthandler'],
1649+
'faulthandler': 1,
1650+
}
1651+
config_dev_mode(preconfig, config)
1652+
self.check_all_configs("test_initconfig_api", config, preconfig,
1653+
api=API_PYTHON)
1654+
16401655
def test_get_argc_argv(self):
16411656
self.run_embedded_interpreter("test_get_argc_argv")
16421657
# ignore output

Programs/_testembed.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,6 +1729,63 @@ 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+
// Set a list of wide strings (argv)
1745+
wchar_t *argv[] = {PROGRAM_NAME, L"-c", L"pass"};
1746+
if (PyInitConfig_SetWStrList(config, "argv",
1747+
Py_ARRAY_LENGTH(argv), argv) < 0) {
1748+
goto error;
1749+
}
1750+
1751+
if (PyInitConfig_SetInt(config, "hash_seed", 10) < 0) {
1752+
goto error;
1753+
}
1754+
1755+
// Set a wide string (program_name)
1756+
if (PyInitConfig_SetWStr(config, "program_name", PROGRAM_NAME) < 0) {
1757+
goto error;
1758+
}
1759+
1760+
// Set a bytes string (pycache_prefix)
1761+
if (PyInitConfig_SetStr(config, "pycache_prefix",
1762+
"conf_pycache_prefix") < 0) {
1763+
goto error;
1764+
}
1765+
1766+
// Set a list of bytes strings (xoptions)
1767+
char* xoptions[] = {"faulthandler"};
1768+
if (PyInitConfig_SetStrList(config, "xoptions",
1769+
Py_ARRAY_LENGTH(xoptions), xoptions) < 0) {
1770+
goto error;
1771+
}
1772+
1773+
1774+
if (Py_InitializeFromInitConfig(config) < 0) {
1775+
Py_ExitWithInitConfig(config);
1776+
}
1777+
PyInitConfig_Free(config);
1778+
1779+
dump_config();
1780+
Py_Finalize();
1781+
return 0;
1782+
1783+
error:
1784+
printf("Init failed:\n");
1785+
Py_ExitWithInitConfig(config);
1786+
}
1787+
1788+
17321789
static void configure_init_main(PyConfig *config)
17331790
{
17341791
wchar_t* argv[] = {
@@ -2131,6 +2188,7 @@ static struct TestCase TestCases[] = {
21312188
{"test_init_is_python_build", test_init_is_python_build},
21322189
{"test_init_warnoptions", test_init_warnoptions},
21332190
{"test_init_set_config", test_init_set_config},
2191+
{"test_initconfig_api", test_initconfig_api},
21342192
{"test_run_main", test_run_main},
21352193
{"test_run_main_loop", test_run_main_loop},
21362194
{"test_get_argc_argv", test_get_argc_argv},

0 commit comments

Comments
 (0)