Skip to content

Commit 238cdb7

Browse files
itamaroclaude
andcommitted
gh-157384: Set package context in PyImport_CreateModuleFromInitfunc()
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. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HoZE3WdRWQshvinfBZ137N
1 parent 2cd6d4b commit 238cdb7

5 files changed

Lines changed: 77 additions & 2 deletions

File tree

Doc/c-api/import.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,11 @@ 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 (as some binding generators do for
422+
submodules), the module's :attr:`~module.__name__` is set to the full
423+
name from the spec.
424+
420425
On error, return NULL with an exception set.
421426
422427
.. 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
@@ -2410,6 +2410,40 @@ static int test_repeated_init_and_inittab(void)
24102410
return 0;
24112411
}
24122412

2413+
// A single-phase init submodule that only knows its short name,
2414+
// like pybind11's PYBIND11_MODULE(sp_submod, m) would.
2415+
static PyModuleDef cmfi_sp_submod_def = {
2416+
PyModuleDef_HEAD_INIT,
2417+
.m_name = "sp_submod",
2418+
.m_size = -1,
2419+
};
2420+
2421+
static PyObject*
2422+
PyInit_cmfi_sp_submod(void)
2423+
{
2424+
return PyModule_Create(&cmfi_sp_submod_def);
2425+
}
2426+
2427+
// A multi-phase init package for the submodule above.
2428+
static PyModuleDef_Slot cmfi_sp_pkg_slots[] = {
2429+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
2430+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
2431+
{0, NULL},
2432+
};
2433+
2434+
static PyModuleDef cmfi_sp_pkg_def = {
2435+
PyModuleDef_HEAD_INIT,
2436+
.m_name = "sp_pkg",
2437+
.m_size = 0,
2438+
.m_slots = cmfi_sp_pkg_slots,
2439+
};
2440+
2441+
static PyObject*
2442+
PyInit_cmfi_sp_pkg(void)
2443+
{
2444+
return PyModuleDef_Init(&cmfi_sp_pkg_def);
2445+
}
2446+
24132447
static PyObject*
24142448
create_module(PyObject* self, PyObject* spec)
24152449
{
@@ -2425,6 +2459,14 @@ create_module(PyObject* self, PyObject* spec)
24252459
Py_DECREF(name);
24262460
return PyImport_CreateModuleFromInitfunc(spec, PyInit_embedded_ext);
24272461
}
2462+
if (PyUnicode_EqualToUTF8(name, "sp_pkg")) {
2463+
Py_DECREF(name);
2464+
return PyImport_CreateModuleFromInitfunc(spec, PyInit_cmfi_sp_pkg);
2465+
}
2466+
if (PyUnicode_EqualToUTF8(name, "sp_pkg.sp_submod")) {
2467+
Py_DECREF(name);
2468+
return PyImport_CreateModuleFromInitfunc(spec, PyInit_cmfi_sp_submod);
2469+
}
24282470
PyErr_Format(PyExc_LookupError, "static module %R not found", name);
24292471
Py_DECREF(name);
24302472
return NULL;
@@ -2472,6 +2514,12 @@ test_create_module_from_initfunc(void)
24722514
L"import embedded_ext;"
24732515
L"print(embedded_ext);"
24742516
L"print(f'{embedded_ext.executed=}');"
2517+
// Single-phase submodule whose init only knows its short name
2518+
L"import sp_pkg.sp_submod;"
2519+
L"print(sp_pkg.sp_submod);"
2520+
L"print(f'{sp_pkg.sp_submod.__name__=}');"
2521+
L"print(f'{sys.modules[\"sp_pkg.sp_submod\"] is sp_pkg.sp_submod=}');"
2522+
L"print(f'{\"sp_submod\" in sys.modules=}');"
24752523
};
24762524
PyConfig config;
24772525
if (PyImport_AppendInittab("create_static_module",
@@ -2491,8 +2539,10 @@ test_create_module_from_initfunc(void)
24912539
" _ORIGIN = \"static-extension\"\n"
24922540
" @classmethod\n"
24932541
" def find_spec(cls, fullname, path, target=None):\n"
2494-
" if fullname in {'my_test_extension', 'embedded_ext'}:\n"
2495-
" return spec_from_loader(fullname, cls, origin=cls._ORIGIN)\n"
2542+
" if fullname in {'my_test_extension', 'embedded_ext',\n"
2543+
" 'sp_pkg', 'sp_pkg.sp_submod'}:\n"
2544+
" return spec_from_loader(fullname, cls, origin=cls._ORIGIN,\n"
2545+
" is_package=(fullname == 'sp_pkg'))\n"
24962546
" return None\n"
24972547
" @staticmethod\n"
24982548
" def create_module(spec):\n"

Python/import.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2507,6 +2507,19 @@ create_builtin(
25072507
if (_Py_ext_module_loader_info_init_for_builtin(&info, name) < 0) {
25082508
return NULL;
25092509
}
2510+
if (initfunc != NULL) {
2511+
/* An explicitly provided init function (see
2512+
* PyImport_CreateModuleFromInitfunc()) may belong to a submodule
2513+
* whose single-phase init creates the module using only the last
2514+
* component of the name (as e.g. pybind11 does). Set the package
2515+
* context so that PyModule_Create() resolves the full name, the
2516+
* same as for dynamically loaded extensions. */
2517+
info.newcontext = PyUnicode_AsUTF8(info.name);
2518+
if (info.newcontext == NULL) {
2519+
_Py_ext_module_loader_info_clear(&info);
2520+
return NULL;
2521+
}
2522+
}
25102523

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

0 commit comments

Comments
 (0)