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]

Reply via email to