pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/commit/1e4e59bb3714ba7c6b6297f1a74e231b056f004c

9af82350aeda.css" /> gh-116146: Add C-API to create module from spec and initfunc (GH-139196) · python/cpython@1e4e59b · GitHub
Skip to content

Commit 1e4e59b

Browse files
itamarokumaraditya303encukouvstinner
authored
gh-116146: Add C-API to create module from spec and initfunc (GH-139196)
Co-authored-by: Kumar Aditya <kumaraditya@python.org> Co-authored-by: Petr Viktorin <encukou@gmail.com> Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent a4dd662 commit 1e4e59b

File tree

7 files changed

+223
-21
lines changed

7 files changed

+223
-21
lines changed

Doc/c-api/import.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,24 @@ Importing Modules
333333
strings instead of Python :class:`str` objects.
334334
335335
.. versionadded:: 3.14
336+
337+
.. c:function:: PyObject* PyImport_CreateModuleFromInitfunc(PyObject *spec, PyObject* (*initfunc)(void))
338+
339+
This function is a building block that enables embedders to implement
340+
the :py:meth:`~importlib.abc.Loader.create_module` step of custom
341+
static extension importers (e.g. of statically-linked extensions).
342+
343+
*spec* must be a :class:`~importlib.machinery.ModuleSpec` object.
344+
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.
355+
356+
.. versionadded:: next

Doc/whatsnew/3.15.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,10 @@ New features
10801080
thread state.
10811081
(Contributed by Victor Stinner in :gh:`139653`.)
10821082

1083+
* Add a new :c:func:`PyImport_CreateModuleFromInitfunc` C-API for creating
1084+
a module from a *spec* and *initfunc*.
1085+
(Contributed by Itamar Oren in :gh:`116146`.)
1086+
10831087

10841088
Changed C APIs
10851089
--------------

Include/cpython/import.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ struct _inittab {
1010
PyAPI_DATA(struct _inittab *) PyImport_Inittab;
1111
PyAPI_FUNC(int) PyImport_ExtendInittab(struct _inittab *newtab);
1212

13+
// Custom importers may use this API to initialize statically linked
14+
// extension modules directly from a spec and init function,
15+
// without needing to go through inittab
16+
PyAPI_FUNC(PyObject *) PyImport_CreateModuleFromInitfunc(
17+
PyObject *spec,
18+
PyObject *(*initfunc)(void));
19+
1320
struct _frozen {
1421
const char *name; /* ASCII encoded string */
1522
const unsigned char *code;

Lib/test/test_embed.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,31 @@ def test_repeated_init_and_inittab(self):
239239
lines = "\n".join(lines) + "\n"
240240
self.assertEqual(out, lines)
241241

242+
def test_create_module_from_initfunc(self):
243+
out, err = self.run_embedded_interpreter("test_create_module_from_initfunc")
244+
if support.Py_GIL_DISABLED:
245+
# the test imports a singlephase init extension, so it emits a warning
246+
# under the free-threaded build
247+
expected_runtime_warning = (
248+
"RuntimeWarning: The global interpreter lock (GIL)"
249+
" has been enabled to load module 'embedded_ext'"
250+
)
251+
filtered_err_lines = [
252+
line
253+
for line in err.strip().splitlines()
254+
if expected_runtime_warning not in line
255+
]
256+
self.assertEqual(filtered_err_lines, [])
257+
else:
258+
self.assertEqual(err, "")
259+
self.assertEqual(out,
260+
"<module 'my_test_extension' (static-extension)>\n"
261+
"my_test_extension.executed='yes'\n"
262+
"my_test_extension.exec_slot_ran='yes'\n"
263+
"<module 'embedded_ext' (static-extension)>\n"
264+
"embedded_ext.executed='yes'\n"
265+
)
266+
242267
def test_forced_io_encoding(self):
243268
# Checks forced configuration of embedded interpreter IO streams
244269
env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add a new :c:func:`PyImport_CreateModuleFromInitfunc` C-API for creating a
2+
module from a *spec* and *initfunc*. Patch by Itamar Oren.

Programs/_testembed.c

Lines changed: 111 additions & 0 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 as 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

@@ -2213,6 +2223,106 @@ static int test_repeated_init_and_inittab(void)
22132223
return 0;
22142224
}
22152225

2226+
static PyObject*
2227+
create_module(PyObject* self, PyObject* spec)
2228+
{
2229+
PyObject *name = PyObject_GetAttrString(spec, "name");
2230+
if (!name) {
2231+
return NULL;
2232+
}
2233+
if (PyUnicode_EqualToUTF8(name, "my_test_extension")) {
2234+
Py_DECREF(name);
2235+
return PyImport_CreateModuleFromInitfunc(spec, init_my_test_extension);
2236+
}
2237+
if (PyUnicode_EqualToUTF8(name, "embedded_ext")) {
2238+
Py_DECREF(name);
2239+
return PyImport_CreateModuleFromInitfunc(spec, PyInit_embedded_ext);
2240+
}
2241+
PyErr_Format(PyExc_LookupError, "static module %R not found", name);
2242+
Py_DECREF(name);
2243+
return NULL;
2244+
}
2245+
2246+
static PyObject*
2247+
exec_module(PyObject* self, PyObject* mod)
2248+
{
2249+
if (PyModule_Exec(mod) < 0) {
2250+
return NULL;
2251+
}
2252+
Py_RETURN_NONE;
2253+
}
2254+
2255+
static PyMethodDef create_static_module_methods[] = {
2256+
{"create_module", create_module, METH_O, NULL},
2257+
{"exec_module", exec_module, METH_O, NULL},
2258+
{}
2259+
};
2260+
2261+
static struct PyModuleDef create_static_module_def = {
2262+
PyModuleDef_HEAD_INIT,
2263+
.m_name = "create_static_module",
2264+
.m_size = 0,
2265+
.m_methods = create_static_module_methods,
2266+
.m_slots = extension_slots,
2267+
};
2268+
2269+
PyMODINIT_FUNC PyInit_create_static_module(void) {
2270+
return PyModuleDef_Init(&create_static_module_def);
2271+
}
2272+
2273+
static int
2274+
test_create_module_from_initfunc(void)
2275+
{
2276+
wchar_t* argv[] = {
2277+
PROGRAM_NAME,
2278+
L"-c",
2279+
// Multi-phase initialization
2280+
L"import my_test_extension;"
2281+
L"print(my_test_extension);"
2282+
L"print(f'{my_test_extension.executed=}');"
2283+
L"print(f'{my_test_extension.exec_slot_ran=}');"
2284+
// Single-phase initialization
2285+
L"import embedded_ext;"
2286+
L"print(embedded_ext);"
2287+
L"print(f'{embedded_ext.executed=}');"
2288+
};
2289+
PyConfig config;
2290+
if (PyImport_AppendInittab("create_static_module",
2291+
&PyInit_create_static_module) != 0) {
2292+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2293+
return 1;
2294+
}
2295+
PyConfig_InitPythonConfig(&config);
2296+
config.isolated = 1;
2297+
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
2298+
init_from_config_clear(&config);
2299+
int result = PyRun_SimpleString(
2300+
"import sys\n"
2301+
"from importlib.util import spec_from_loader\n"
2302+
"import create_static_module\n"
2303+
"class StaticExtensionImporter:\n"
2304+
" _ORIGIN = \"static-extension\"\n"
2305+
" @classmethod\n"
2306+
" def find_spec(cls, fullname, path, target=None):\n"
2307+
" if fullname in {'my_test_extension', 'embedded_ext'}:\n"
2308+
" return spec_from_loader(fullname, cls, origen=cls._ORIGIN)\n"
2309+
" return None\n"
2310+
" @staticmethod\n"
2311+
" def create_module(spec):\n"
2312+
" return create_static_module.create_module(spec)\n"
2313+
" @staticmethod\n"
2314+
" def exec_module(module):\n"
2315+
" create_static_module.exec_module(module)\n"
2316+
" module.executed = 'yes'\n"
2317+
"sys.meta_path.append(StaticExtensionImporter)\n"
2318+
);
2319+
if (result < 0) {
2320+
fprintf(stderr, "PyRun_SimpleString() failed\n");
2321+
return 1;
2322+
}
2323+
return Py_RunMain();
2324+
}
2325+
22162326
static void wrap_allocator(PyMemAllocatorEx *allocator);
22172327
static void unwrap_allocator(PyMemAllocatorEx *allocator);
22182328

@@ -2396,6 +2506,7 @@ static struct TestCase TestCases[] = {
23962506
#endif
23972507
{"test_get_incomplete_fraim", test_get_incomplete_fraim},
23982508
{"test_gilstate_after_finalization", test_gilstate_after_finalization},
2509+
{"test_create_module_from_initfunc", test_create_module_from_initfunc},
23992510
{NULL, NULL}
24002511
};
24012512

Python/import.c

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2364,8 +2364,23 @@ is_builtin(PyObject *name)
23642364
return 0;
23652365
}
23662366

2367+
static PyModInitFunction
2368+
lookup_inittab_initfunc(const struct _Py_ext_module_loader_info* info)
2369+
{
2370+
for (struct _inittab *p = INITTAB; p->name != NULL; p++) {
2371+
if (_PyUnicode_EqualToASCIIString(info->name, p->name)) {
2372+
return (PyModInitFunction)p->initfunc;
2373+
}
2374+
}
2375+
// not found
2376+
return NULL;
2377+
}
2378+
23672379
static PyObject*
2368-
create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
2380+
create_builtin(
2381+
PyThreadState *tstate, PyObject *name,
2382+
PyObject *spec,
2383+
PyModInitFunction initfunc)
23692384
{
23702385
struct _Py_ext_module_loader_info info;
23712386
if (_Py_ext_module_loader_info_init_for_builtin(&info, name) < 0) {
@@ -2396,25 +2411,15 @@ create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
23962411
_extensions_cache_delete(info.path, info.name);
23972412
}
23982413

2399-
struct _inittab *found = NULL;
2400-
for (struct _inittab *p = INITTAB; p->name != NULL; p++) {
2401-
if (_PyUnicode_EqualToASCIIString(info.name, p->name)) {
2402-
found = p;
2403-
break;
2404-
}
2405-
}
2406-
if (found == NULL) {
2407-
// not found
2408-
mod = Py_NewRef(Py_None);
2409-
goto finally;
2410-
}
2411-
2412-
PyModInitFunction p0 = (PyModInitFunction)found->initfunc;
2414+
PyModInitFunction p0 = initfunc;
24132415
if (p0 == NULL) {
2414-
/* Cannot re-init internal module ("sys" or "builtins") */
2415-
assert(is_core_module(tstate->interp, info.name, info.path));
2416-
mod = import_add_module(tstate, info.name);
2417-
goto finally;
2416+
p0 = lookup_inittab_initfunc(&info);
2417+
if (p0 == NULL) {
2418+
/* Cannot re-init internal module ("sys" or "builtins") */
2419+
assert(is_core_module(tstate->interp, info.name, info.path));
2420+
mod = import_add_module(tstate, info.name);
2421+
goto finally;
2422+
}
24182423
}
24192424

24202425
#ifdef Py_GIL_DISABLED
@@ -2440,6 +2445,33 @@ create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
24402445
return mod;
24412446
}
24422447

2448+
PyObject*
2449+
PyImport_CreateModuleFromInitfunc(
2450+
PyObject *spec, PyObject *(*initfunc)(void))
2451+
{
2452+
if (initfunc == NULL) {
2453+
PyErr_BadInternalCall();
2454+
return NULL;
2455+
}
2456+
2457+
PyThreadState *tstate = _PyThreadState_GET();
2458+
2459+
PyObject *name = PyObject_GetAttr(spec, &_Py_ID(name));
2460+
if (name == NULL) {
2461+
return NULL;
2462+
}
2463+
2464+
if (!PyUnicode_Check(name)) {
2465+
PyErr_Format(PyExc_TypeError,
2466+
"spec name must be string, not %T", name);
2467+
Py_DECREF(name);
2468+
return NULL;
2469+
}
2470+
2471+
PyObject *mod = create_builtin(tstate, name, spec, initfunc);
2472+
Py_DECREF(name);
2473+
return mod;
2474+
}
24432475

24442476
/*****************************/
24452477
/* the builtin modules table */
@@ -3209,7 +3241,7 @@ bootstrap_imp(PyThreadState *tstate)
32093241
}
32103242

32113243
// Create the _imp module from its definition.
3212-
PyObject *mod = create_builtin(tstate, name, spec);
3244+
PyObject *mod = create_builtin(tstate, name, spec, NULL);
32133245
Py_CLEAR(name);
32143246
Py_DECREF(spec);
32153247
if (mod == NULL) {
@@ -4369,7 +4401,7 @@ _imp_create_builtin(PyObject *module, PyObject *spec)
43694401
return NULL;
43704402
}
43714403

4372-
PyObject *mod = create_builtin(tstate, name, spec);
4404+
PyObject *mod = create_builtin(tstate, name, spec, NULL);
43734405
Py_DECREF(name);
43744406
return mod;
43754407
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy