https://github.com/python/cpython/commit/04862280a2ccbf9be9883eb4bce886f6b1bc4ca0
commit: 04862280a2ccbf9be9883eb4bce886f6b1bc4ca0
branch: main
author: Itamar Oren <[email protected]>
committer: encukou <[email protected]>
date: 2026-10-13T13:35:19Z
summary:
gh-157384: Set package context in PyImport_CreateModuleFromInitfunc()
(GH-157387)
A single-phase init function that creates its module using only the
last component of a dotted name (as pybind11 does for submodules) got
a module whose __name__ was the short name. Set the package context
while calling the init function, as the dynamic loader does, so the
module gets the full name from the spec.
files:
A Misc/NEWS.d/next/C_API/2026-09-12-18-37-22.gh-issue-157384.LGsy8K.rst
M Doc/c-api/import.rst
M Lib/test/test_embed.py
M Programs/_testembed.c
M Python/import.c
diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst
index 7a3e5357e0c2ca..e9911a3efcbeb4 100644
--- a/Doc/c-api/import.rst
+++ b/Doc/c-api/import.rst
@@ -417,6 +417,10 @@ Importing Modules
(Custom importers should do this in their
:py:meth:`~importlib.abc.Loader.exec_module` method.)
+ If the spec's name is dotted and *initfunc* creates the module using only
+ the last component of the name, the module's :attr:`~module.__name__`
+ is set to the full name from the spec.
+
On error, return NULL with an exception set.
.. versionadded:: 3.15
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index 39485afcdb0f57..02fb352502b54d 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -278,6 +278,10 @@ def test_create_module_from_initfunc(self):
"my_test_extension.exec_slot_ran='yes'\n"
"<module 'embedded_ext' (static-extension)>\n"
"embedded_ext.executed='yes'\n"
+ "<module 'sp_pkg.sp_submod' (static-extension)>\n"
+ "sp_pkg.sp_submod.__name__='sp_pkg.sp_submod'\n"
+ "sys.modules[\"sp_pkg.sp_submod\"] is
sp_pkg.sp_submod=True\n"
+ "\"sp_submod\" in sys.modules=False\n"
)
def test_inittab_submodule_multiphase(self):
diff --git
a/Misc/NEWS.d/next/C_API/2026-09-12-18-37-22.gh-issue-157384.LGsy8K.rst
b/Misc/NEWS.d/next/C_API/2026-09-12-18-37-22.gh-issue-157384.LGsy8K.rst
new file mode 100644
index 00000000000000..9f79975e25123a
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2026-09-12-18-37-22.gh-issue-157384.LGsy8K.rst
@@ -0,0 +1,3 @@
+:c:func:`PyImport_CreateModuleFromInitfunc` now sets the package context
+while calling *initfunc*, so that a single-phase init submodule created with
+only the last component of its name gets the full name from the spec.
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 592c23756201f0..71bc37194e4b41 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -2357,6 +2357,40 @@ static int test_repeated_init_and_inittab(void)
return 0;
}
+// A single-phase init submodule that only knows its short name,
+// like pybind11's PYBIND11_MODULE(sp_submod, m) would.
+static PyModuleDef cmfi_sp_submod_def = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "sp_submod",
+ .m_size = -1,
+};
+
+static PyObject*
+PyInit_cmfi_sp_submod(void)
+{
+ return PyModule_Create(&cmfi_sp_submod_def);
+}
+
+// A multi-phase init package for the submodule above.
+static PyModuleDef_Slot cmfi_sp_pkg_slots[] = {
+ {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+ {0, NULL},
+};
+
+static PyModuleDef cmfi_sp_pkg_def = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "sp_pkg",
+ .m_size = 0,
+ .m_slots = cmfi_sp_pkg_slots,
+};
+
+static PyObject*
+PyInit_cmfi_sp_pkg(void)
+{
+ return PyModuleDef_Init(&cmfi_sp_pkg_def);
+}
+
static PyObject*
create_module(PyObject* self, PyObject* spec)
{
@@ -2372,6 +2406,14 @@ create_module(PyObject* self, PyObject* spec)
Py_DECREF(name);
return PyImport_CreateModuleFromInitfunc(spec, PyInit_embedded_ext);
}
+ if (PyUnicode_EqualToUTF8(name, "sp_pkg")) {
+ Py_DECREF(name);
+ return PyImport_CreateModuleFromInitfunc(spec, PyInit_cmfi_sp_pkg);
+ }
+ if (PyUnicode_EqualToUTF8(name, "sp_pkg.sp_submod")) {
+ Py_DECREF(name);
+ return PyImport_CreateModuleFromInitfunc(spec, PyInit_cmfi_sp_submod);
+ }
PyErr_Format(PyExc_LookupError, "static module %R not found", name);
Py_DECREF(name);
return NULL;
@@ -2419,6 +2461,12 @@ test_create_module_from_initfunc(void)
L"import embedded_ext;"
L"print(embedded_ext);"
L"print(f'{embedded_ext.executed=}');"
+ // Single-phase submodule whose init only knows its short name
+ L"import sp_pkg.sp_submod;"
+ L"print(sp_pkg.sp_submod);"
+ L"print(f'{sp_pkg.sp_submod.__name__=}');"
+ L"print(f'{sys.modules[\"sp_pkg.sp_submod\"] is sp_pkg.sp_submod=}');"
+ L"print(f'{\"sp_submod\" in sys.modules=}');"
};
PyConfig config;
if (PyImport_AppendInittab("create_static_module",
@@ -2438,8 +2486,10 @@ test_create_module_from_initfunc(void)
" _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"
+ " if fullname in {'my_test_extension', 'embedded_ext',\n"
+ " 'sp_pkg', 'sp_pkg.sp_submod'}:\n"
+ " return spec_from_loader(fullname, cls,
origin=cls._ORIGIN,\n"
+ " is_package=(fullname ==
'sp_pkg'))\n"
" return None\n"
" @staticmethod\n"
" def create_module(spec):\n"
diff --git a/Python/import.c b/Python/import.c
index 7538575fbf7520..88d4980991f16a 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -2515,6 +2515,19 @@ create_builtin(
if (_Py_ext_module_loader_info_init_for_builtin(&info, name) < 0) {
return NULL;
}
+ if (initfunc != NULL) {
+ /* An explicitly provided init function (see
+ * PyImport_CreateModuleFromInitfunc()) may belong to a submodule
+ * whose single-phase init creates the module using only the last
+ * component of the name (as e.g. pybind11 does). Set the package
+ * context so that PyModule_Create() resolves the full name, the
+ * same as for dynamically loaded extensions. */
+ info.newcontext = PyUnicode_AsUTF8(info.name);
+ if (info.newcontext == NULL) {
+ _Py_ext_module_loader_info_clear(&info);
+ return NULL;
+ }
+ }
struct extensions_cache_value *cached = NULL;
PyObject *mod = import_find_extension(tstate, &info, &cached);
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]