Skip to content

Commit f652399

Browse files
encukouitamaro
andauthored
PyImport_CreateModuleFromInitfunc: Doc & test update (#27)
* Test single-phase init as well; don't use private APIs in test * Doc update --------- Co-authored-by: Itamar Oren <[email protected]>
1 parent 6aa2fb2 commit f652399

File tree

3 files changed

+71
-12
lines changed

3 files changed

+71
-12
lines changed

Doc/c-api/import.rst

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -336,12 +336,21 @@ Importing Modules
336336
337337
.. c:function:: PyObject* PyImport_CreateModuleFromInitfunc(PyObject *spec, PyObject* (*initfunc)(void))
338338
339-
This function is a building block that enables embedders to implement custom
339+
This function is a building block that enables embedders to implement
340+
the :py:meth:`~importlib.abc.Loader.create_module` step of custom
340341
static extension importers (e.g. of statically-linked extensions).
341-
The function creates and returns a module object given a *spec* and an *initfunc*.
342342
343-
*spec* must be a :class:`~importlib.machinery.ModuleSpec` object
343+
*spec* must be a :class:`~importlib.machinery.ModuleSpec` object.
344344
345-
*initfunc* is the same as in :c:func:`PyImport_ExtendInittab`
345+
*initfunc* must be an :ref:`initialization function <extension-export-hook>`,
346+
the same as for :c:func:`PyImport_AppendInittab`.
347+
348+
On success, create and return a module object.
349+
This module will not be initialized; call :c:func:`PyModule_Exec`
350+
to initialize it.
351+
(Custom importers should do this in their
352+
:py:meth:`~importlib.abc.Loader.exec_module` method.)
353+
354+
On error, return NULL with an exception set.
346355
347356
.. versionadded:: 3.15

Lib/test/test_embed.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,13 @@ def test_repeated_init_and_inittab(self):
242242
def test_create_module_from_initfunc(self):
243243
out, err = self.run_embedded_interpreter("test_create_module_from_initfunc")
244244
self.assertEqual(err, "")
245-
self.assertEqual(out, "<module 'my_test_extension' (static-extension)>\n")
245+
self.assertEqual(out,
246+
"<module 'my_test_extension' (static-extension)>\n"
247+
"my_test_extension.executed='yes'\n"
248+
"my_test_extension.exec_slot_ran='yes'\n"
249+
"<module 'embedded_ext' (static-extension)>\n"
250+
"embedded_ext.executed='yes'\n"
251+
)
246252

247253
def test_forced_io_encoding(self):
248254
# Checks forced configuration of embedded interpreter IO streams

Programs/_testembed.c

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ static PyModuleDef embedded_ext = {
166166
static PyObject*
167167
PyInit_embedded_ext(void)
168168
{
169+
// keep this a single-phase initialization module;
170+
// see test_create_module_from_initfunc
169171
return PyModule_Create(&embedded_ext);
170172
}
171173

@@ -1894,8 +1896,16 @@ static int test_initconfig_exit(void)
18941896
}
18951897

18961898

1899+
int
1900+
extension_module_exec(PyObject *mod)
1901+
{
1902+
return PyModule_AddStringConstant(mod, "exec_slot_ran", "yes");
1903+
}
1904+
1905+
18971906
static PyModuleDef_Slot extension_slots[] = {
18981907
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
1908+
{Py_mod_exec, extension_module_exec},
18991909
{0, NULL}
19001910
};
19011911

@@ -2214,11 +2224,33 @@ static int test_repeated_init_and_inittab(void)
22142224
}
22152225

22162226
static PyObject* create_module(PyObject* self, PyObject* spec) {
2217-
return PyImport_CreateModuleFromInitfunc(spec, init_my_test_extension);
2227+
PyObject *name = PyObject_GetAttrString(spec, "name");
2228+
if (!name) {
2229+
return NULL;
2230+
}
2231+
if (PyUnicode_EqualToUTF8(name, "my_test_extension")) {
2232+
Py_DECREF(name);
2233+
return PyImport_CreateModuleFromInitfunc(spec, init_my_test_extension);
2234+
}
2235+
if (PyUnicode_EqualToUTF8(name, "embedded_ext")) {
2236+
Py_DECREF(name);
2237+
return PyImport_CreateModuleFromInitfunc(spec, PyInit_embedded_ext);
2238+
}
2239+
PyErr_Format(PyExc_LookupError, "static module %R not found", name);
2240+
Py_DECREF(name);
2241+
return NULL;
2242+
}
2243+
2244+
static PyObject* exec_module(PyObject* self, PyObject* mod) {
2245+
if (PyModule_Exec(mod) < 0) {
2246+
return NULL;
2247+
}
2248+
Py_RETURN_NONE;
22182249
}
22192250

22202251
static PyMethodDef create_static_module_methods[] = {
22212252
{"create_module", create_module, METH_O, NULL},
2253+
{"exec_module", exec_module, METH_O, NULL},
22222254
{}
22232255
};
22242256

@@ -2236,7 +2268,19 @@ PyMODINIT_FUNC PyInit_create_static_module(void) {
22362268

22372269
static int test_create_module_from_initfunc(void)
22382270
{
2239-
wchar_t* argv[] = {PROGRAM_NAME, L"-c", L"import my_test_extension; print(my_test_extension)"};
2271+
wchar_t* argv[] = {
2272+
PROGRAM_NAME,
2273+
L"-c",
2274+
// Multi-phase initialization
2275+
L"import my_test_extension;"
2276+
L"print(my_test_extension);"
2277+
L"print(f'{my_test_extension.executed=}');"
2278+
L"print(f'{my_test_extension.exec_slot_ran=}');"
2279+
// Single-phase initialization
2280+
L"import embedded_ext;"
2281+
L"print(embedded_ext);"
2282+
L"print(f'{embedded_ext.executed=}');"
2283+
};
22402284
PyConfig config;
22412285
if (PyImport_AppendInittab("create_static_module",
22422286
&PyInit_create_static_module) != 0) {
@@ -2249,22 +2293,22 @@ static int test_create_module_from_initfunc(void)
22492293
init_from_config_clear(&config);
22502294
int result = PyRun_SimpleString(
22512295
"import sys\n"
2252-
"from importlib._bootstrap import spec_from_loader, _call_with_frames_removed\n"
2253-
"import _imp\n"
2296+
"from importlib.util import spec_from_loader\n"
22542297
"import create_static_module\n"
22552298
"class StaticExtensionImporter:\n"
22562299
" _ORIGIN = \"static-extension\"\n"
22572300
" @classmethod\n"
22582301
" def find_spec(cls, fullname, path, target=None):\n"
2259-
" if fullname == \"my_test_extension\":\n"
2302+
" if fullname in {'my_test_extension', 'embedded_ext'}:\n"
22602303
" return spec_from_loader(fullname, cls, origin=cls._ORIGIN)\n"
22612304
" return None\n"
22622305
" @staticmethod\n"
22632306
" def create_module(spec):\n"
2264-
" return _call_with_frames_removed(create_static_module.create_module, spec)\n"
2307+
" return create_static_module.create_module(spec)\n"
22652308
" @staticmethod\n"
22662309
" def exec_module(module):\n"
2267-
" _call_with_frames_removed(_imp.exec_builtin, module)\n"
2310+
" create_static_module.exec_module(module)\n"
2311+
" module.executed = 'yes'\n"
22682312
"sys.meta_path.append(StaticExtensionImporter)\n"
22692313
);
22702314
if (result < 0) {

0 commit comments

Comments
 (0)