Skip to content

Commit 56d89df

Browse files
committed
Add a backwards-compatibility shim for PyInit
1 parent ca88c42 commit 56d89df

File tree

3 files changed

+108
-1
lines changed

3 files changed

+108
-1
lines changed

peps/pep-0793.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@ Here is a guide to convert an existing module to the new API, including
412412
some tricky edge cases.
413413
It should be moved to a HOWTO in the documentation.
414414

415+
This guide is meant for hand-written modules. For code generators and language
416+
wrappers, the :ref:`pep793-shim` below may be more useful.
417+
415418
#. Scan your code for uses of ``PyModule_GetDef``. This function will
416419
return ``NULL`` for modules that use the new mechanism. Instead:
417420

@@ -493,6 +496,40 @@ Once your module no longer supports lower versions, delete the ``PyInit_``
493496
function and any unused data.
494497

495498

499+
Backwards compatibility shim
500+
----------------------------
501+
502+
It is possible to write generic function that implements the existing export
503+
hook (``PyInit_``) in terms of the API proposed here.
504+
505+
The following implemntation can be copy-pasted; only the names
506+
``PyInit_examplemodule`` (twice) and ``PyModExport_examplemodule`` need adjusting.
507+
508+
When added to the :ref:`pep793-example` below and compiled with a
509+
non-free-threaded build of this PEP's reference implementation, the resulting
510+
extension is compatible with regular builds 3.9+ in addition to a
511+
free-threading build of the reference implementation.
512+
(The module must be named without a version tag, e.g. ``examplemodule.so``,
513+
and be placed on ``sys.path``.)
514+
515+
Full support for creating such modules will require backports of some new
516+
API, and support in build/install tools. This is out of scope of this PEP.
517+
(In particular, the demo “cheats” uses a subset Limited API 3.15 that
518+
*happens to work* on 3.9; it *should* use Limited API 3.9 with backport
519+
shims for new API like ``Py_mod_name``.)
520+
521+
This implementation places a few additional requirements on the slots array:
522+
523+
- Slots that correspond to ``PyModuleDef`` members must come first.
524+
- A ``Py_mod_name`` slot is required.
525+
- Any ``Py_mod_token`` must be set to ``&module_def_and_token``, defined here.
526+
527+
It also passes ``NULL`` as *spec* to the ``PyModExport`` export hook.
528+
A proper implementation would pass ``None`` instead, .
529+
530+
.. literalinclude:: pep-0793/shim.c
531+
:language: c
532+
496533

497534
Security Implications
498535
=====================
@@ -507,6 +544,8 @@ In addition to regular reference docs, the :ref:`pep793-porting-notes` should
507544
be added as a new HOWTO.
508545

509546

547+
.. _pep793-example:
548+
510549
Example
511550
=======
512551

peps/pep-0793/examplemodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ PyDoc_STRVAR(examplemodule_doc, "Example extension.");
4949
static PyModuleDef_Slot examplemodule_slots[] = {
5050
{Py_mod_name, "examplemodule"},
5151
{Py_mod_doc, (char*)examplemodule_doc},
52-
{Py_mod_exec, (void*)examplemodule_exec},
5352
{Py_mod_methods, examplemodule_methods},
5453
{Py_mod_state_size, (void*)sizeof(examplemodule_state)},
54+
{Py_mod_exec, (void*)examplemodule_exec},
5555
{0}
5656
};
5757

peps/pep-0793/shim.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#include <string.h> // memset
2+
3+
PyMODINIT_FUNC PyInit_examplemodule(void);
4+
5+
static PyModuleDef module_def_and_token;
6+
7+
PyMODINIT_FUNC
8+
PyInit_examplemodule(void)
9+
{
10+
PyModuleDef_Slot *slot = PyModExport_examplemodule(NULL);
11+
12+
if (module_def_and_token.m_name) {
13+
// Take care to only set up the static PyModuleDef once.
14+
// (PyModExport might theoretically return different data each time.)
15+
return PyModuleDef_Init(&module_def_and_token);
16+
}
17+
int copying_slots = 1;
18+
for (/* slot set above */; slot->slot; slot++) {
19+
switch (slot->slot) {
20+
// Set PyModuleDef members from slots. These slots must come first.
21+
# define COPYSLOT_CASE(SLOT, MEMBER, TYPE) \
22+
case SLOT: \
23+
if (!copying_slots) { \
24+
PyErr_SetString(PyExc_SystemError, \
25+
#SLOT " must be specified earlier"); \
26+
goto error; \
27+
} \
28+
module_def_and_token.MEMBER = (TYPE)(slot->value); \
29+
break; \
30+
/////////////////////////////////////////////////////////////////
31+
COPYSLOT_CASE(Py_mod_name, m_name, char*)
32+
COPYSLOT_CASE(Py_mod_doc, m_doc, char*)
33+
COPYSLOT_CASE(Py_mod_state_size, m_size, Py_ssize_t)
34+
COPYSLOT_CASE(Py_mod_methods, m_methods, PyMethodDef*)
35+
COPYSLOT_CASE(Py_mod_state_traverse, m_traverse, traverseproc)
36+
COPYSLOT_CASE(Py_mod_state_clear, m_clear, inquiry)
37+
COPYSLOT_CASE(Py_mod_state_free, m_free, freefunc)
38+
case Py_mod_token:
39+
// With PyInit_, the PyModuleDef is used as the token.
40+
if (slot->value != &module_def_and_token) {
41+
PyErr_SetString(PyExc_SystemError,
42+
"Py_mod_token must be set to "
43+
"&module_def_and_token");
44+
goto error;
45+
}
46+
break;
47+
default:
48+
// The remaining slots become m_slots in the def.
49+
// (`slot` now points to the "rest" of the original
50+
// zero-terminated array.)
51+
if (copying_slots) {
52+
module_def_and_token.m_slots = slot;
53+
}
54+
copying_slots = 0;
55+
break;
56+
}
57+
}
58+
if (!module_def_and_token.m_name) {
59+
// This function needs m_name as the "is initialized" marker.
60+
PyErr_SetString(PyExc_SystemError, "Py_mod_name slot is required");
61+
goto error;
62+
}
63+
return PyModuleDef_Init(&module_def_and_token);
64+
65+
error:
66+
memset(&module_def_and_token, 0, sizeof(module_def_and_token));
67+
return NULL;
68+
}

0 commit comments

Comments
 (0)