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
thread state.
(Contributed by Victor Stinner in :gh:`139653`.)
+* 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
--------------
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;
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")
--- /dev/null
+Add a new :c:func:`PyImport_CreateModuleFromInitfunc` C-API for creating a
+module from a *spec* and *initfunc*. Patch by Itamar Oren.
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);
}
}
+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}
};
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);
#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}
};
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) {
_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
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 */
}
// 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) {
return NULL;
}
- PyObject *mod = create_builtin(tstate, name, spec);
+ PyObject *mod = create_builtin(tstate, name, spec, NULL);
Py_DECREF(name);
return mod;
}