Skip to content

Commit 8fe6515

Browse files
committed
pythongh-142417: Add PyInitConfig_SetInitCallback() function
Add init_callback and init_callback_arg members to PyConfig.
1 parent 0b8c348 commit 8fe6515

6 files changed

Lines changed: 141 additions & 12 deletions

File tree

Doc/c-api/init_config.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,22 @@ Module
238238
Similar to the :c:func:`PyImport_AppendInittab` function.
239239
240240
241+
Initialization Callback
242+
-----------------------
243+
244+
.. c:function:: int PyInitConfig_SetInitCallback(PyInitConfig *config, PyStatus (*callback)(void *arg), void *arg)
245+
246+
Set an initialization callback. It is called once the Python interpreter is
247+
initialized and before the first import.
248+
249+
A single callback can be registered. If this function is called more than
250+
once, the previous callback is overridden.
251+
252+
* Return ``0`` on success.
253+
* Set an error in *config* and return ``-1`` on error.
254+
255+
.. versionadded:: next
256+
241257
Initialize Python
242258
-----------------
243259

Include/cpython/initconfig.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ typedef struct PyConfig {
215215
wchar_t *run_module;
216216
wchar_t *run_filename;
217217

218+
/* --- Initialization callback ------------------- */
219+
PyStatus (*init_callback)(void *arg);
220+
void *init_callback_arg;
221+
218222
/* --- Set by Py_Main() -------------------------- */
219223
wchar_t *sys_path_0;
220224

@@ -323,6 +327,10 @@ PyAPI_FUNC(int) PyInitConfig_AddModule(PyInitConfig *config,
323327
const char *name,
324328
PyObject* (*initfunc)(void));
325329

330+
PyAPI_FUNC(int) PyInitConfig_SetInitCallback(PyInitConfig *config,
331+
PyStatus (*callback)(void *arg),
332+
void *arg);
333+
326334
PyAPI_FUNC(int) Py_InitializeFromInitConfig(PyInitConfig *config);
327335

328336

Lib/test/test_embed.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,6 +1885,16 @@ def test_init_in_background_thread(self):
18851885
out, err = self.run_embedded_interpreter("test_init_in_background_thread")
18861886
self.assertEqual(err, "")
18871887

1888+
def test_init_callback(self):
1889+
out, err = self.run_embedded_interpreter("test_init_callback")
1890+
modules = [
1891+
'_frozen_importlib', '_imp', '_thread', '_warnings', '_weakref',
1892+
'builtins', 'sys']
1893+
self.assertEqual(out.splitlines(),
1894+
["Hello Callback!",
1895+
f"sys.modules: {modules}"])
1896+
self.assertEqual(err, "")
1897+
18881898

18891899
class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
18901900
def test_open_code_hook(self):

Programs/_testembed.c

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1741,6 +1741,16 @@ static int initconfig_getint(PyInitConfig *config, const char *name)
17411741
return (int)value;
17421742
}
17431743

1744+
static void initconfig_error(PyInitConfig *config)
1745+
{
1746+
const char *err_msg;
1747+
int res = PyInitConfig_GetError(config, &err_msg);
1748+
assert(res == 1);
1749+
1750+
printf("Python init failed: %s\n", err_msg);
1751+
PyInitConfig_Free(config);
1752+
}
1753+
17441754

17451755
static int test_initconfig_api(void)
17461756
{
@@ -1795,12 +1805,8 @@ static int test_initconfig_api(void)
17951805
return 0;
17961806

17971807
error:
1798-
{
1799-
const char *err_msg;
1800-
(void)PyInitConfig_GetError(config, &err_msg);
1801-
printf("Python init failed: %s\n", err_msg);
1802-
exit(1);
1803-
}
1808+
initconfig_error(config);
1809+
return 1;
18041810
}
18051811

18061812

@@ -1953,12 +1959,8 @@ static int test_initconfig_module(void)
19531959
return 0;
19541960

19551961
error:
1956-
{
1957-
const char *err_msg;
1958-
(void)PyInitConfig_GetError(config, &err_msg);
1959-
printf("Python init failed: %s\n", err_msg);
1960-
exit(1);
1961-
}
1962+
initconfig_error(config);
1963+
return 1;
19621964
}
19631965

19641966

@@ -2170,6 +2172,77 @@ static int test_init_in_background_thread(void)
21702172
}
21712173

21722174

2175+
static PyStatus init_callback(void *arg)
2176+
{
2177+
const char *msg = (const char*)arg;
2178+
printf("%s\n", msg);
2179+
2180+
PyObject *modules = PySys_GetAttrString("modules");
2181+
if (modules == NULL) {
2182+
return PyStatus_Error("failed to get sys.modules");
2183+
}
2184+
2185+
PyObject *builtins = PyEval_GetBuiltins(); // borrowed ref
2186+
if (builtins == NULL) {
2187+
Py_DECREF(modules);
2188+
return PyStatus_Error("failed to get builtins");
2189+
}
2190+
2191+
PyObject *sorted;
2192+
if (PyDict_GetItemStringRef(builtins, "sorted", &sorted) <= 0) {
2193+
Py_DECREF(modules);
2194+
return PyStatus_Error("failed to get sorted");
2195+
}
2196+
2197+
PyObject *names = PyObject_CallOneArg(sorted, modules);
2198+
Py_DECREF(modules);
2199+
if (names == NULL) {
2200+
return PyStatus_Error("sorted failed");
2201+
}
2202+
2203+
PySys_FormatStdout("sys.modules: %R\n", names);
2204+
Py_DECREF(names);
2205+
2206+
return PyStatus_Ok();
2207+
}
2208+
2209+
2210+
static int test_init_callback(void)
2211+
{
2212+
PyInitConfig *config = PyInitConfig_Create();
2213+
if (config == NULL) {
2214+
printf("Init allocation error\n");
2215+
return 1;
2216+
}
2217+
2218+
if (PyInitConfig_SetStr(config, "program_name", PROGRAM_NAME_UTF8) < 0) {
2219+
goto error;
2220+
}
2221+
2222+
const char *ignored_msg = "ignored_msg";
2223+
if (PyInitConfig_SetInitCallback(config, init_callback, (void*)ignored_msg) < 0) {
2224+
goto error;
2225+
}
2226+
2227+
// PyInitConfig_SetInitCallback() can be called more than once, but the
2228+
// previous callback and callback argument are overridden.
2229+
const char *msg = "Hello Callback!";
2230+
if (PyInitConfig_SetInitCallback(config, init_callback, (void*)msg) < 0) {
2231+
goto error;
2232+
}
2233+
2234+
if (Py_InitializeFromInitConfig(config) < 0) {
2235+
goto error;
2236+
}
2237+
PyInitConfig_Free(config);
2238+
return 0;
2239+
2240+
error:
2241+
initconfig_error(config);
2242+
return 1;
2243+
}
2244+
2245+
21732246
#ifndef MS_WINDOWS
21742247
#include "test_frozenmain.h" // M_test_frozenmain
21752248

@@ -2658,6 +2731,7 @@ static struct TestCase TestCases[] = {
26582731
{"test_init_use_frozen_modules", test_init_use_frozen_modules},
26592732
{"test_init_main_interpreter_settings", test_init_main_interpreter_settings},
26602733
{"test_init_in_background_thread", test_init_in_background_thread},
2734+
{"test_init_callback", test_init_callback},
26612735

26622736
// Audit
26632737
{"test_open_code_hook", test_open_code_hook},

Python/initconfig.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,10 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
12741274
Py_UNREACHABLE();
12751275
}
12761276
}
1277+
1278+
config->init_callback = config2->init_callback;
1279+
config->init_callback_arg = config2->init_callback_arg;
1280+
12771281
return _PyStatus_OK();
12781282
}
12791283

@@ -4240,6 +4244,16 @@ Py_InitializeFromInitConfig(PyInitConfig *config)
42404244
}
42414245

42424246

4247+
int
4248+
PyInitConfig_SetInitCallback(PyInitConfig *config,
4249+
PyStatus (*callback)(void *arg), void *arg)
4250+
{
4251+
config->config.init_callback = callback;
4252+
config->config.init_callback_arg = arg;
4253+
return 0;
4254+
}
4255+
4256+
42434257
// --- PyConfig_Get() -------------------------------------------------------
42444258

42454259
static const PyConfigSpec*

Python/pylifecycle.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,13 @@ Py_InitializeFromConfig(const PyConfig *config)
14571457
}
14581458
config = _PyInterpreterState_GetConfig(tstate->interp);
14591459

1460+
if (config->init_callback != NULL) {
1461+
status = config->init_callback(config->init_callback_arg);
1462+
if (_PyStatus_EXCEPTION(status)) {
1463+
return status;
1464+
}
1465+
}
1466+
14601467
if (config->_init_main) {
14611468
status = pyinit_main(tstate);
14621469
if (_PyStatus_EXCEPTION(status)) {

0 commit comments

Comments
 (0)