Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-142417: Add PyInitConfig_SetInitCallback() function#142420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
vstinner wants to merge4 commits intopython:mainfromvstinner:init_callback
Closed
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletionsDoc/c-api/init_config.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -238,6 +238,43 @@ Module
Similar to the :c:func:`PyImport_AppendInittab` function.


Initialization Callback
-----------------------

.. c:function:: int PyInitConfig_SetInitCallback(PyInitConfig *config, PyStatus (*callback)(void *arg), void *arg)

Set an initialization callback. It allows executing code during the
Python interpreter, before the first import. For example, it
can be used to add a meta path importer into :data:`sys.meta_path`.

When the callback is called, Python is only partially initialized. What's
available at this point:

* Builtin types;
* Builtin exceptions;
* Builtin and frozen modules (can be imported);
* The :mod:`sys` module is only partially initialized
(ex: :data:`sys.path` and :data:`sys.stdout` don't exist yet).

After the callback, the Python initialization is completed:

* Install and configure :mod:`importlib`;
* Apply the :ref:`Path Configuration <init-path-config>`;
* Install signal handlers;
* Finish :mod:`sys` module initialization (ex: create :data:`sys.stdout`
and :data:`sys.path`);
* Enable optional features like :mod:`faulthandler` and :mod:`tracemalloc`;
* Import the :mod:`site` module;
* etc.

A single callback can be registered. If this function is called more than
once, it fails with an error.

* Return ``0`` on success.
* Set an error in *config* and return ``-1`` on error.

.. versionadded:: next

Initialize Python
-----------------

Expand Down
5 changes: 5 additions & 0 deletionsDoc/whatsnew/3.15.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1228,6 +1228,11 @@ New features
a module from a *spec* and *initfunc*.
(Contributed by Itamar Oren in :gh:`116146`.)

* Add :c:func:`PyInitConfig_SetInitCallback` to execute code as soon as the
Python interpreter is initialized, before the first import. For example, it
can be used to add a meta path importer into :data:`sys.meta_path`.
(Contributed by Victor Stinner in :gh:`142417`.)

* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
(Contributed by Victor Stinner in :gh:`111489`.)

Expand Down
10 changes: 10 additions & 0 deletionsInclude/cpython/initconfig.h
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -215,6 +215,12 @@ typedef struct PyConfig {
wchar_t *run_module;
wchar_t *run_filename;

/* --- Initialization callback ------------------- */

// See PyInitConfig_SetInitCallback() function.
PyStatus (*init_callback)(void *arg);
void *init_callback_arg;

/* --- Set by Py_Main() -------------------------- */
wchar_t *sys_path_0;

Expand DownExpand Up@@ -323,6 +329,10 @@ PyAPI_FUNC(int) PyInitConfig_AddModule(PyInitConfig *config,
const char *name,
PyObject* (*initfunc)(void));

PyAPI_FUNC(int) PyInitConfig_SetInitCallback(PyInitConfig *config,
PyStatus (*callback)(void *arg),
void *arg);

PyAPI_FUNC(int) Py_InitializeFromInitConfig(PyInitConfig *config);


Expand Down
14 changes: 14 additions & 0 deletionsLib/test/test_embed.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1885,6 +1885,20 @@ def test_init_in_background_thread(self):
out, err = self.run_embedded_interpreter("test_init_in_background_thread")
self.assertEqual(err, "")

def test_init_callback(self):
out, err = self.run_embedded_interpreter("test_init_callback")
modules = [
'_frozen_importlib', '_imp', '_thread', '_warnings', '_weakref',
'builtins', 'sys']
meta_path = (
"[<class '_frozen_importlib.BuiltinImporter'>, "
"<class '_frozen_importlib.FrozenImporter'>]")
self.assertEqual(err.splitlines(),
["Hello Callback!",
f"sys.modules: {modules}",
f"sys.meta_path: {meta_path}"])
self.assertEqual(out, "")


class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
def test_open_code_hook(self):
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
Add :c:func:`PyInitConfig_SetInitCallback` to execute code as soon as the
Python interpreter is initialized, before the first import. For example, it can
be used to add a meta path importer into :data:`sys.meta_path`. Patch by Victor
Stinner.
100 changes: 88 additions & 12 deletionsPrograms/_testembed.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1741,6 +1741,16 @@ static int initconfig_getint(PyInitConfig *config, const char *name)
return (int)value;
}

static void initconfig_error(PyInitConfig *config)
{
const char *err_msg;
int res = PyInitConfig_GetError(config, &err_msg);
assert(res == 1);

printf("Python init failed: %s\n", err_msg);
PyInitConfig_Free(config);
}


static int test_initconfig_api(void)
{
Expand DownExpand Up@@ -1795,12 +1805,8 @@ static int test_initconfig_api(void)
return 0;

error:
{
const char *err_msg;
(void)PyInitConfig_GetError(config, &err_msg);
printf("Python init failed: %s\n", err_msg);
exit(1);
}
initconfig_error(config);
return 1;
}


Expand DownExpand Up@@ -1953,12 +1959,8 @@ static int test_initconfig_module(void)
return 0;

error:
{
const char *err_msg;
(void)PyInitConfig_GetError(config, &err_msg);
printf("Python init failed: %s\n", err_msg);
exit(1);
}
initconfig_error(config);
return 1;
}


Expand DownExpand Up@@ -2175,6 +2177,79 @@ static int test_init_in_background_thread(void)
}


static PyStatus init_callback(void *arg)
{
const char *msg = (const char*)arg;
fprintf(stderr, "%s\n", msg);

// Write sorted(sys.modules) to sys.stderr
PyObject *modules = PySys_GetAttrString("modules");
if (modules == NULL) {
return PyStatus_Error("failed to get sys.modules");
}

PyObject *builtins = PyEval_GetBuiltins(); // borrowed ref
if (builtins == NULL) {
Py_DECREF(modules);
return PyStatus_Error("failed to get builtins");
}

PyObject *sorted;
if (PyDict_GetItemStringRef(builtins, "sorted", &sorted) <= 0) {
Py_DECREF(modules);
return PyStatus_Error("failed to get sorted");
}

PyObject *names = PyObject_CallOneArg(sorted, modules);
Py_DECREF(modules);
if (names == NULL) {
return PyStatus_Error("sorted failed");
}

PySys_FormatStderr("sys.modules: %R\n", names);
Py_DECREF(names);

// Write sys.meta_path to sys.stderr
const char *code = (
"import sys; "
"print(f\"sys.meta_path: {sys.meta_path}\", file=sys.stderr)");
if (PyRun_SimpleString(code) < 0) {
return PyStatus_Error("PyRun_SimpleString failed");
}

return PyStatus_Ok();
}


static int test_init_callback(void)
{
PyInitConfig *config = PyInitConfig_Create();
if (config == NULL) {
printf("Init allocation error\n");
return 1;
}

if (PyInitConfig_SetStr(config, "program_name", PROGRAM_NAME_UTF8) < 0) {
goto error;
}

const char *msg = "Hello Callback!";
if (PyInitConfig_SetInitCallback(config, init_callback, (void*)msg) < 0) {
goto error;
}

if (Py_InitializeFromInitConfig(config) < 0) {
goto error;
}
PyInitConfig_Free(config);
return 0;

error:
initconfig_error(config);
return 1;
}


#ifndef MS_WINDOWS
#include "test_frozenmain.h" // M_test_frozenmain

Expand DownExpand Up@@ -2663,6 +2738,7 @@ static struct TestCase TestCases[] = {
{"test_init_use_frozen_modules", test_init_use_frozen_modules},
{"test_init_main_interpreter_settings", test_init_main_interpreter_settings},
{"test_init_in_background_thread", test_init_in_background_thread},
{"test_init_callback", test_init_callback},

// Audit
{"test_open_code_hook", test_open_code_hook},
Expand Down
19 changes: 19 additions & 0 deletionsPython/initconfig.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1274,6 +1274,10 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
Py_UNREACHABLE();
}
}

config->init_callback = config2->init_callback;
config->init_callback_arg = config2->init_callback_arg;

return _PyStatus_OK();
}

Expand DownExpand Up@@ -4242,6 +4246,21 @@ Py_InitializeFromInitConfig(PyInitConfig *config)
}


int
PyInitConfig_SetInitCallback(PyInitConfig *config,
PyStatus (*callback)(void *arg), void *arg)
{
if (config->config.init_callback != NULL) {
initconfig_set_error(config, "cannot set two init callbacks");
return -1;
}

config->config.init_callback = callback;
config->config.init_callback_arg = arg;
return 0;
}


// --- PyConfig_Get() -------------------------------------------------------

static const PyConfigSpec*
Expand Down
7 changes: 7 additions & 0 deletionsPython/pylifecycle.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1462,6 +1462,13 @@ Py_InitializeFromConfig(const PyConfig *config)
}
config = _PyInterpreterState_GetConfig(tstate->interp);

if (config->init_callback != NULL) {
status = config->init_callback(config->init_callback_arg);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
}

if (config->_init_main) {
status = pyinit_main(tstate);
if (_PyStatus_EXCEPTION(status)) {
Expand Down
Loading

[8]ページ先頭

©2009-2026 Movatter.jp