Skip to content

Commit 0486228

Browse files
authored
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.
1 parent f892b11 commit 0486228

5 files changed

Lines changed: 76 additions & 2 deletions

File tree

‎Doc/c-api/import.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,10 @@ Importing Modules
417417
(Custom importers should do this in their
418418
:py:meth:`~importlib.abc.Loader.exec_module` method.)
419419
420+
If the spec's name is dotted and *initfunc* creates the module using only
421+
the last component of the name, the module's :attr:`~module.__name__`
422+
is set to the full name from the spec.
423+
420424
On error, return NULL with an exception set.
421425
422426
.. versionadded:: 3.15

‎Lib/test/test_embed.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,10 @@ def test_create_module_from_initfunc(self):
278278
"my_test_extension.exec_slot_ran='yes'\n"
279279
"<module 'embedded_ext' (static-extension)>\n"
280280
"embedded_ext.executed='yes'\n"
281+
"<module 'sp_pkg.sp_submod' (static-extension)>\n"
282+
"sp_pkg.sp_submod.__name__='sp_pkg.sp_submod'\n"
283+
"sys.modules[\"sp_pkg.sp_submod\"] is sp_pkg.sp_submod=True\n"
284+
"\"sp_submod\" in sys.modules=False\n"
281285
)
282286

283287
def test_inittab_submodule_multiphase(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:c:func:`PyImport_CreateModuleFromInitfunc` now sets the package context
2+
while calling *initfunc*, so that a single-phase init submodule created with
3+
only the last component of its name gets the full name from the spec.

‎Programs/_testembed.c‎

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,6 +2357,40 @@ static int test_repeated_init_and_inittab(void)
23572357
return 0;
23582358
}
23592359

2360+
// A single-phase init submodule that only knows its short name,
2361+
// like pybind11's PYBIND11_MODULE(sp_submod, m) would.
2362+
static PyModuleDef cmfi_sp_submod_def = {
2363+
PyModuleDef_HEAD_INIT,
2364+
.m_name = "sp_submod",
2365+
.m_size = -1,
2366+
};
2367+
2368+
static PyObject*
2369+
PyInit_cmfi_sp_submod(void)
2370+
{
2371+
return PyModule_Create(&cmfi_sp_submod_def);
2372+
}
2373+
2374+
// A multi-phase init package for the submodule above.
2375+
static PyModuleDef_Slot cmfi_sp_pkg_slots[] = {
2376+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
2377+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
2378+
{0, NULL},
2379+
};
2380+
2381+
static PyModuleDef cmfi_sp_pkg_def = {
2382+
PyModuleDef_HEAD_INIT,
2383+
.m_name = "sp_pkg",
2384+
.m_size = 0,
2385+
.m_slots = cmfi_sp_pkg_slots,
2386+
};
2387+
2388+
static PyObject*
2389+
PyInit_cmfi_sp_pkg(void)
2390+
{
2391+
return PyModuleDef_Init(&cmfi_sp_pkg_def);
2392+
}
2393+
23602394
static PyObject*
23612395
create_module(PyObject* self, PyObject* spec)
23622396
{
@@ -2372,6 +2406,14 @@ create_module(PyObject* self, PyObject* spec)
23722406
Py_DECREF(name);
23732407
return PyImport_CreateModuleFromInitfunc(spec, PyInit_embedded_ext);
23742408
}
2409+
if (PyUnicode_EqualToUTF8(name, "sp_pkg")) {
2410+
Py_DECREF(name);
2411+
return PyImport_CreateModuleFromInitfunc(spec, PyInit_cmfi_sp_pkg);
2412+
}
2413+
if (PyUnicode_EqualToUTF8(name, "sp_pkg.sp_submod")) {
2414+
Py_DECREF(name);
2415+
return PyImport_CreateModuleFromInitfunc(spec, PyInit_cmfi_sp_submod);
2416+
}
23752417
PyErr_Format(PyExc_LookupError, "static module %R not found", name);
23762418
Py_DECREF(name);
23772419
return NULL;
@@ -2419,6 +2461,12 @@ test_create_module_from_initfunc(void)
24192461
L"import embedded_ext;"
24202462
L"print(embedded_ext);"
24212463
L"print(f'{embedded_ext.executed=}');"
2464+
// Single-phase submodule whose init only knows its short name
2465+
L"import sp_pkg.sp_submod;"
2466+
L"print(sp_pkg.sp_submod);"
2467+
L"print(f'{sp_pkg.sp_submod.__name__=}');"
2468+
L"print(f'{sys.modules[\"sp_pkg.sp_submod\"] is sp_pkg.sp_submod=}');"
2469+
L"print(f'{\"sp_submod\" in sys.modules=}');"
24222470
};
24232471
PyConfig config;
24242472
if (PyImport_AppendInittab("create_static_module",
@@ -2438,8 +2486,10 @@ test_create_module_from_initfunc(void)
24382486
" _ORIGIN = \"static-extension\"\n"
24392487
" @classmethod\n"
24402488
" def find_spec(cls, fullname, path, target=None):\n"
2441-
" if fullname in {'my_test_extension', 'embedded_ext'}:\n"
2442-
" return spec_from_loader(fullname, cls, origin=cls._ORIGIN)\n"
2489+
" if fullname in {'my_test_extension', 'embedded_ext',\n"
2490+
" 'sp_pkg', 'sp_pkg.sp_submod'}:\n"
2491+
" return spec_from_loader(fullname, cls, origin=cls._ORIGIN,\n"
2492+
" is_package=(fullname == 'sp_pkg'))\n"
24432493
" return None\n"
24442494
" @staticmethod\n"
24452495
" def create_module(spec):\n"

‎Python/import.c‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2515,6 +2515,19 @@ create_builtin(
25152515
if (_Py_ext_module_loader_info_init_for_builtin(&info, name) < 0) {
25162516
return NULL;
25172517
}
2518+
if (initfunc != NULL) {
2519+
/* An explicitly provided init function (see
2520+
* PyImport_CreateModuleFromInitfunc()) may belong to a submodule
2521+
* whose single-phase init creates the module using only the last
2522+
* component of the name (as e.g. pybind11 does). Set the package
2523+
* context so that PyModule_Create() resolves the full name, the
2524+
* same as for dynamically loaded extensions. */
2525+
info.newcontext = PyUnicode_AsUTF8(info.name);
2526+
if (info.newcontext == NULL) {
2527+
_Py_ext_module_loader_info_clear(&info);
2528+
return NULL;
2529+
}
2530+
}
25182531

25192532
struct extensions_cache_value *cached = NULL;
25202533
PyObject *mod = import_find_extension(tstate, &info, &cached);

0 commit comments

Comments
 (0)