Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
12 changes: 12 additions & 0 deletions Doc/c-api/import.rst
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,15 @@ 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 custom
static extension importers (e.g. of statically-linked extensions).
The function creates and returns a module object given a *spec* and an *initfunc*.

*spec* must be a :class:`~importlib.machinery.ModuleSpec` object

*initfunc* is the same as in :c:func:`PyImport_ExtendInittab`

.. versionadded:: 3.15
9 changes: 9 additions & 0 deletions Include/cpython/import.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ 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
5 changes: 5 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,11 @@ 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")
self.assertEqual(err, "")
self.assertEqual(out, "<module 'my_test_extension' (static-extension)>\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.
62 changes: 62 additions & 0 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -2213,6 +2213,67 @@ static int test_repeated_init_and_inittab(void)
return 0;
}

static PyObject* create_module(PyObject* self, PyObject* spec) {
return PyImport_CreateModuleFromInitfunc(spec, init_my_test_extension);
}

static PyMethodDef create_static_module_methods[] = {
{"create_module", create_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", L"import my_test_extension; print(my_test_extension)"};
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._bootstrap import spec_from_loader, _call_with_frames_removed\n"
"import _imp\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 == \"my_test_extension\":\n"
" return spec_from_loader(fullname, cls, origin=cls._ORIGIN)\n"
" return None\n"
" @staticmethod\n"
" def create_module(spec):\n"
" return _call_with_frames_removed(create_static_module.create_module, spec)\n"
" @staticmethod\n"
" def exec_module(module):\n"
" _call_with_frames_removed(_imp.exec_builtin, module)\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 +2457,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
69 changes: 48 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,28 @@ create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
return mod;
}

PyObject*
PyImport_CreateModuleFromInitfunc(
PyObject *spec, PyObject* (*initfunc)(void))
{
PyThreadState *tstate = _PyThreadState_GET();

PyObject *name = PyObject_GetAttrString(spec, "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 +3234,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 +4394,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