Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
21 changes: 21 additions & 0 deletions Doc/c-api/import.rst
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,24 @@ Importing Modules
strings instead of Python :class:`str` objects.
.. versionadded:: 3.14
.. c:function:: PyObject* PyImport_CreateModuleFromInitfunc(PyObject *spec, PyObject* (*initfunc)(void))
This function is a building block that enables embedders to implement
the :py:meth:`~importlib.abc.Loader.create_module` step of custom
static extension importers (e.g. of statically-linked extensions).
*spec* must be a :class:`~importlib.machinery.ModuleSpec` object.
*initfunc* must be an :ref:`initialization function <extension-export-hook>`,
the same as for :c:func:`PyImport_AppendInittab`.
On success, create and return a module object.
This module will not be initialized; call :c:func:`!PyModule_Exec`
to initialize it.
(Custom importers should do this in their
:py:meth:`~importlib.abc.Loader.exec_module` method.)
On error, return NULL with an exception set.
.. versionadded:: next
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,10 @@ New features
* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
(Contributed by Victor Stinner in :gh:`111489`.)

* Add a new :c:func:`PyImport_CreateModuleFromInitfunc` C-API for creating
a module from a *spec* and *initfunc*.
(Contributed by Itamar Oren in :gh:`116146`.)


Changed C APIs
--------------
Expand Down
7 changes: 7 additions & 0 deletions Include/cpython/import.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ struct _inittab {
PyAPI_DATA(struct _inittab *) PyImport_Inittab;
PyAPI_FUNC(int) PyImport_ExtendInittab(struct _inittab *newtab);

// Custom importers may use this API to initialize statically linked
// extension modules directly from a spec and init function,
// without needing to go through inittab
PyAPI_FUNC(PyObject *) PyImport_CreateModuleFromInitfunc(
PyObject *spec,
PyObject *(*initfunc)(void));

struct _frozen {
const char *name; /* ASCII encoded string */
const unsigned char *code;
Expand Down
25 changes: 25 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,31 @@ def test_repeated_init_and_inittab(self):
lines = "\n".join(lines) + "\n"
self.assertEqual(out, lines)

def test_create_module_from_initfunc(self):
out, err = self.run_embedded_interpreter("test_create_module_from_initfunc")
if support.Py_GIL_DISABLED:
# the test imports a singlephase init extension, so it emits a warning
# under the free-threaded build
expected_runtime_warning = (
"RuntimeWarning: The global interpreter lock (GIL)"
" has been enabled to load module 'embedded_ext'"
)
filtered_err_lines = [
line
for line in err.strip().splitlines()
if expected_runtime_warning not in line
]
self.assertEqual(filtered_err_lines, [])
else:
self.assertEqual(err, "")
self.assertEqual(out,
"<module 'my_test_extension' (static-extension)>\n"
"my_test_extension.executed='yes'\n"
"my_test_extension.exec_slot_ran='yes'\n"
"<module 'embedded_ext' (static-extension)>\n"
"embedded_ext.executed='yes'\n"
)

def test_forced_io_encoding(self):
# Checks forced configuration of embedded interpreter IO streams
env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add a new :c:func:`PyImport_CreateModuleFromInitfunc` C-API for creating a
module from a *spec* and *initfunc*. Patch by Itamar Oren.
111 changes: 111 additions & 0 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ static PyModuleDef embedded_ext = {
static PyObject*
PyInit_embedded_ext(void)
{
// keep this as a single-phase initialization module;
// see test_create_module_from_initfunc
return PyModule_Create(&embedded_ext);
}

Expand Down Expand Up @@ -1894,8 +1896,16 @@ static int test_initconfig_exit(void)
}


int
extension_module_exec(PyObject *mod)
{
return PyModule_AddStringConstant(mod, "exec_slot_ran", "yes");
}


static PyModuleDef_Slot extension_slots[] = {
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{Py_mod_exec, extension_module_exec},
{0, NULL}
};

Expand Down Expand Up @@ -2213,6 +2223,106 @@ static int test_repeated_init_and_inittab(void)
return 0;
}

static PyObject*
create_module(PyObject* self, PyObject* spec)
{
PyObject *name = PyObject_GetAttrString(spec, "name");
if (!name) {
return NULL;
}
if (PyUnicode_EqualToUTF8(name, "my_test_extension")) {
Py_DECREF(name);
return PyImport_CreateModuleFromInitfunc(spec, init_my_test_extension);
}
if (PyUnicode_EqualToUTF8(name, "embedded_ext")) {
Py_DECREF(name);
return PyImport_CreateModuleFromInitfunc(spec, PyInit_embedded_ext);
}
PyErr_Format(PyExc_LookupError, "static module %R not found", name);
Py_DECREF(name);
return NULL;
}

static PyObject*
exec_module(PyObject* self, PyObject* mod)
{
if (PyModule_Exec(mod) < 0) {
return NULL;
}
Py_RETURN_NONE;
}

static PyMethodDef create_static_module_methods[] = {
{"create_module", create_module, METH_O, NULL},
{"exec_module", exec_module, METH_O, NULL},
{}
};

static struct PyModuleDef create_static_module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "create_static_module",
.m_size = 0,
.m_methods = create_static_module_methods,
.m_slots = extension_slots,
};

PyMODINIT_FUNC PyInit_create_static_module(void) {
return PyModuleDef_Init(&create_static_module_def);
}

static int
test_create_module_from_initfunc(void)
{
wchar_t* argv[] = {
PROGRAM_NAME,
L"-c",
// Multi-phase initialization
L"import my_test_extension;"
L"print(my_test_extension);"
L"print(f'{my_test_extension.executed=}');"
L"print(f'{my_test_extension.exec_slot_ran=}');"
// Single-phase initialization
L"import embedded_ext;"
L"print(embedded_ext);"
L"print(f'{embedded_ext.executed=}');"
};
PyConfig config;
if (PyImport_AppendInittab("create_static_module",
&PyInit_create_static_module) != 0) {
fprintf(stderr, "PyImport_AppendInittab() failed\n");
return 1;
}
PyConfig_InitPythonConfig(&config);
config.isolated = 1;
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
init_from_config_clear(&config);
int result = PyRun_SimpleString(
"import sys\n"
"from importlib.util import spec_from_loader\n"
"import create_static_module\n"
"class StaticExtensionImporter:\n"
" _ORIGIN = \"static-extension\"\n"
" @classmethod\n"
" def find_spec(cls, fullname, path, target=None):\n"
" if fullname in {'my_test_extension', 'embedded_ext'}:\n"
" return spec_from_loader(fullname, cls, origin=cls._ORIGIN)\n"
" return None\n"
" @staticmethod\n"
" def create_module(spec):\n"
" return create_static_module.create_module(spec)\n"
" @staticmethod\n"
" def exec_module(module):\n"
" create_static_module.exec_module(module)\n"
" module.executed = 'yes'\n"
"sys.meta_path.append(StaticExtensionImporter)\n"
);
if (result < 0) {
fprintf(stderr, "PyRun_SimpleString() failed\n");
return 1;
}
return Py_RunMain();
}

static void wrap_allocator(PyMemAllocatorEx *allocator);
static void unwrap_allocator(PyMemAllocatorEx *allocator);

Expand Down Expand Up @@ -2396,6 +2506,7 @@ static struct TestCase TestCases[] = {
#endif
{"test_get_incomplete_frame", test_get_incomplete_frame},
{"test_gilstate_after_finalization", test_gilstate_after_finalization},
{"test_create_module_from_initfunc", test_create_module_from_initfunc},
{NULL, NULL}
};

Expand Down
74 changes: 53 additions & 21 deletions Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -2362,8 +2362,23 @@ is_builtin(PyObject *name)
return 0;
}

static PyModInitFunction
lookup_inittab_initfunc(const struct _Py_ext_module_loader_info* info)
{
for (struct _inittab *p = INITTAB; p->name != NULL; p++) {
if (_PyUnicode_EqualToASCIIString(info->name, p->name)) {
return (PyModInitFunction)p->initfunc;
}
}
// not found
return NULL;
}

static PyObject*
create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
create_builtin(
PyThreadState *tstate, PyObject *name,
PyObject *spec,
PyModInitFunction initfunc)
{
struct _Py_ext_module_loader_info info;
if (_Py_ext_module_loader_info_init_for_builtin(&info, name) < 0) {
Expand Down Expand Up @@ -2394,25 +2409,15 @@ create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
_extensions_cache_delete(info.path, info.name);
}

struct _inittab *found = NULL;
for (struct _inittab *p = INITTAB; p->name != NULL; p++) {
if (_PyUnicode_EqualToASCIIString(info.name, p->name)) {
found = p;
break;
}
}
if (found == NULL) {
// not found
mod = Py_NewRef(Py_None);
goto finally;
}

PyModInitFunction p0 = (PyModInitFunction)found->initfunc;
PyModInitFunction p0 = initfunc;
if (p0 == NULL) {
/* Cannot re-init internal module ("sys" or "builtins") */
assert(is_core_module(tstate->interp, info.name, info.path));
mod = import_add_module(tstate, info.name);
goto finally;
p0 = lookup_inittab_initfunc(&info);
if (p0 == NULL) {
/* Cannot re-init internal module ("sys" or "builtins") */
assert(is_core_module(tstate->interp, info.name, info.path));
mod = import_add_module(tstate, info.name);
goto finally;
}
}

#ifdef Py_GIL_DISABLED
Expand All @@ -2438,6 +2443,33 @@ create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
return mod;
}

PyObject*
PyImport_CreateModuleFromInitfunc(
PyObject *spec, PyObject *(*initfunc)(void))
{
if (initfunc == NULL) {
PyErr_BadInternalCall();
return NULL;
}

PyThreadState *tstate = _PyThreadState_GET();

PyObject *name = PyObject_GetAttr(spec, &_Py_ID(name));
if (name == NULL) {
return NULL;
}

if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
"spec name must be string, not %T", name);
Py_DECREF(name);
return NULL;
}

PyObject *mod = create_builtin(tstate, name, spec, initfunc);
Py_DECREF(name);
return mod;
}

/*****************************/
/* the builtin modules table */
Expand Down Expand Up @@ -3207,7 +3239,7 @@ bootstrap_imp(PyThreadState *tstate)
}

// Create the _imp module from its definition.
PyObject *mod = create_builtin(tstate, name, spec);
PyObject *mod = create_builtin(tstate, name, spec, NULL);
Py_CLEAR(name);
Py_DECREF(spec);
if (mod == NULL) {
Expand Down Expand Up @@ -4367,7 +4399,7 @@ _imp_create_builtin(PyObject *module, PyObject *spec)
return NULL;
}

PyObject *mod = create_builtin(tstate, name, spec);
PyObject *mod = create_builtin(tstate, name, spec, NULL);
Py_DECREF(name);
return mod;
}
Expand Down
Loading