Hi everyone!
The current PyCapsule API is a bit lacking, especially in terms of versioning an ABI. I’ve worked with @encukou on developing a draft PEP on this topic. It mainly addresses these three problems:
- Capsules, in general, have no proper way to version themselves.
PyCapsule_Import
has a bug when it comes to submodules – but technically, fixing it would be a breaking change.- Nowadays, many modules are not immortal, so if a capsule needs to reference it’s own module, it needs a destructor. This isn’t necesarilly a problem, just an inconvenience – this PEP makes it automatic.
Here’s what we came up with (apologies for any formatting errors between the conversion of reST to Markdown):
Abstract
CPython capsules are typically only known to those that are familiar with the C API – in short, they smuggle a void *
between Python code and C code. This makes them the preferred way for a library to expose an ABI, since the Python packaging ecosystem does not allow you to link extension modules against each other (see why this is the case here).
However, the current capsule API doesn’t have any standard way to manage ABI versions. This PEP takes some inspiration from SemVer, and solves that problem by modifying the internal PyCapsule
structure to hold an ABI major version, as well as a reference to its module, and the size of the ABI structure.
Motivation
Generally, a capsule contains a struct or array containing function pointers, which are then unpacked by a header file (which is installed by pip
or something similar – this is conventionally stored in $DATAROOT/include
) that defines ABI functions. For example, an ABI could be exported by defining a structure that looked like the following:
typedef struct {
PyObject *(foo *)(int); // PyObject *foo(int);
} FooABI;
Then, a capsule would hold a pointer to this structure, and the header file could define foo
as such:
static PyObject *
foo(int whatever) {
FooABI *foo_abi = PyCapsule_Import("foo.foo_abi", 0);
// Throughout this PEP, caching is omitted for simplicity
if (foo_abi == NULL) {
return NULL;
}
return foo_abi->foo(whatever);
}
However, the header file and the capsule at runtime may be written for different versions, causing some ambiguous segfaults if not handled properly. Capsules have no “proper” way to specify a version, so it’s up to the developer to decide how to do it. For example, that could be a version
field containing an integer (but again, this is totally unspecified). In the FooABI
example from above, this could look like:
#define MAJOR_VERSION 2
// In the real world, this would be in a header file - ignore that for simplicity purposes
typedef struct {
int32_t major_version;
PyObject *(foo *)(int); // PyObject *foo(int);
} FooABI;
static PyObject *
foo(int whatever) {
FooABI *foo_abi = PyCapsule_Import("foo.foo_abi", 0);
if (foo_abi == NULL) {
return NULL;
}
if (foo_abi->major_version != MAJOR_VERSION)
{
PyErr_SetString(PyExc_RuntimeError, "major version does not match");
return NULL;
}
return foo_abi->foo(whatever);
}
This is quite a bit of boilerplate just for adding a very basic versioning system. Note that in any approach, this does not cover using the ABI from an FFI, such as ctypes
, which has to know how to handle the version. Many ABIs choose to be completely frozen because of this lack of support, which isn’t exactly an ideal standard. It would be much easier – and much less error-prone – if a capsule did all of this automatically.
Incidentally, this PEP can also solve two problems regarding capsules:
- More and more modules are not immortal singletons – assuming data is stored on the module and not on the capsule, capsules need to hold a reference to their module if they use it in any way. So, nearly all modern capsules need a
PyCapsule_Destructor
just for that. Ideally, this could be done automatically. - If the C extension is a submodule of the package, and importing the package doesn’t also import the submodule, then
PyCapsule_Import
will fail.
For example, if the ABI function foo()
from earlier wanted to access foo.bar
in the package, it would need to add an additional PyCapsule_Destructor
to its capsule, just for Py_DECREF
-ing the module.
static void
decref_module(PyObject *capsule)
{
FooABI *abi = PyCapsule_GetPointer(/* ... */)
Py_DECREF(abi->module);
}
Regarding version problems, some changes to the capsule API are needed for backwards compatibility reasons. By adding a version API into capsules directly, frozen ABIs could safely “unlock” themselves – otherwise, they’re going to remain frozen, since they cannot add a version
field. This could have happened simply because the developer forgot to, such as with the FooABI
structure from earlier – it would be impossible for FooABI
users to check if a function is supported on their version safely.
A dedicated function for importing ABI versions will also fix the problems with FFIs. For example, using an ABI from ctypes
would be much safer and easier – the developer would not have to remember to check the version, nor reverse engineer the ABI’s version management to do that.
Rationale
This PEP addresses these problems by adding fields for each of the following to the PyCapsule
structure directly:
Py_ssize_t size
, representing the size of the ABI structure (e.g., in the example above, this would besizeof(FooABI)
).int32_t major_version
, containing the major version number of the ABI (this is unspecified inFooABI
from above). Note that this is in relation to SemVer, but more on that in a moment.PyObject *module
, holding a strong reference to the module where the ABI was created.
This PEP has some ideas inspired by SemVer, namely:
- A major version bump is a breaking change
- Minor versions are for additions (note that in this specification,
size
is used in place of a minor version number).
Note that these are the only rules that ABI developers need to follow from SemVer – they don’t need to follow to entire specification. Micro/patch versions from SemVer are disregarded in this context, as they don’t affect ABI compatibility (e.g., FooABI V1.0.0
is more or less equivalent to FooABI V1.0.1
here).
Sizes are used instead of minor versions as a clever way to make things simpler – since ABI developers are expected to follow SemVer (or really, this PEP’s version of SemVer), a size does all of the following:
- It prevents any odd errors related to minor version checking – users don’t have to check against arbitrary numbers for version checking (e.g.,
if (foo_abi->minor_version >= 14) /* Do something with foo */
). Instead, the user can doif (PyCapsule_GetSize(capsule) >= offsetof(foo_abi, foo))
, which is much less error-prone (and easier, considering the user doesn’t have to check whatever documentation to figure out whenfoo
was added). - Technically speaking, it automatically bumps the “minor version” every time something is added. Since any change will automatically update the
size
, the ABI developer doesn’t have to remember to change it or document when something was added (although, they probably should, they just don’t have to). - Makes it clearer to an ABI developer that following SemVer for minor versions is a must, since changing the structure could risk memory errors if the header definition doesn’t match the runtime version. For example, if
foo()
were to be removed fromFooABI
from earlier, the existing code that thinks it has afoo
field would access out-of-bounds memory at runtime. By adding an explicitsizeof()
, it may be more intuitive to an ABI developer to not mess with it between versions. (Although, this is C we’re talking about – it’s difficult to foolproof things).
Technically, instead of a major version number being kept as an integer, it could probably be sufficient to just keep an ABI version in a capsule name (e.g., foo.foo_abi_v1
would always return the ABI at version one). In fact, this is the recommended option for backporting this PEP. However, a “stringly typed” API is generally less convenient (in the sense that, if you have a structure to your data, you should keep it structured), so this PEP adds a new module slot instead of something like overriding the module-level __getattr__
.
With that approach, an entirely new ABI would be needed to make breaking changes – this PEP allows you to update the ABI itself depending on the major version number. For example:
static PyObject *
capsule_getter(PyObject *self, const char *qualname, int32_t major_version) {
if (strcmp(qualname, "foo.foo_abi") {
PyErr_SetString(PyExc_RuntimeError, "unknown abi");
return NULL;
}
FooABI *foo_abi = malloc(sizeof(FooABI));
/* Initialize ABI */
if (major_version < 2) foo_abi->foo = old_foo;
return PyCapsule_NewVersioned(foo_abi, /* Other arguments */);
}
This wouldn’t be possible with the string approach – you would have to make a brand new ABI just to change a field.
Specification
Additions to the API
Changes to PyCapsule
This PEP proposes the following new fields to the internal PyCapsule
structure:
typedef struct {
/* ... */
PyObject *module; // NULL by default
Py_ssize_t size; // 0 by default
int32_t major_version; // 0 by default
} PyCapsule;
Addition to ctypes
This PEP adds a PyABI
class to ctypes
:
class PyABI(ctypes.Structure):
_capsule_: types.CapsuleType
_capsule_size_: ctypes.c_ssize_t
@classmethod
def from_capsule(
cls,
capsule_or_module: str | types.ModuleType,
capsule_name: str | None = None,
major_version: int = 0,
min_size: int = 0,
) -> typing.Self: ...
def __init_subclass__(
cls,
size_field: str | None = None,
default_size: int | ctypes.c_ssize_t = 0
) -> None: ...
However, it’s difficult to understand PyABI
without understanding the C API additions to this PEP, so more on that later.
Additions to the C API
This PEP introduces the following additions to the C API:
PyObject *PyCapsule_NewVersioned(void *pointer, const char *capsule_name, PyCapsule_Destructor destructor, PyObject *module, int32_t major_version, Py_ssize_t size);
Py_ssize_t PyCapsule_GetSize(PyObject *capsule);
int PyCapsule_GetModule(PyObject *capsule, PyObject **module);
int32_t PyCapsule_GetMajorVersion(PyObject *capsule);
PyObject *PyCapsule_GetFromModule(PyObject *module, const char *capsule_name, int32_t version, Py_ssize_t min_size);
PyObject *PyCapsule_ImportVersioned(const char *capsule_name, int32_t version, Py_ssize_t min_size);
int PyCapsule_IsValidWithVersion(PyObject *op, const char *name, PyObject *module, int32_t major_version, Py_ssize_t min_size);
// New module slot
#define Py_mod_capsule 5
This PEP also deprecates PyCapsule_Import
(as a soft deprecation) – more on that later. Regardless, there’s a lot to take in here – let’s go through each section.
New ABIs
On 3.14+, capsules intended to expose an ABI should use PyCapsule_NewVersioned
instead of PyCapsule_New
. NewVersioned
will set the new module
, size
, and major_version
, and New
will initialize each of them to empty values (NULL
and 0
). For example, defining an ABI would look like:
FooABI abi = {
/* ABI Fields */
};
PyMODINIT_FUNC PyInit_foo()
{
PyObject *m = PyModule_Create(/* ... */);
PyObject *capsule = PyCapsule_NewVersioned(
abi, // ABI Pointer
"foo_abi", // Capsule name
NULL, // Capsule destructor
m, // Reference to module
1, // Major version number
sizeof(FooABI) // Size of the structure
);
/* Initialize attributes */
return m;
}
For backwards compatibility purposes, a size and major version of 0
denote that the capsule is the first version. PyCapsule_NewVersioned
will allow explicitly setting both of these values to 0
to let CPython know that the ABI is compatible with the legacy version. Note that PyCapsule_NewVersioned
will raise a ValueError
if the size or major version number is negative, to reserve negative values for error indicators in the getter functions.
ABI Sizes
GetSize
is the getter for the size of the underlying ABI structure. So, in the previous example, the size
field in PyCapsule
would be sizeof(FooABI)
, so GetSize
would return that. Since PyCapsule_New
implicitly sets the size
field to zero, GetSize
returns 0
on capsules created with it.
For example:
void
downstream_user_function()
{
PyObject *foo_capsule = PyCapsule_ImportVersioned(/* ... */);
FooABI *foo_abi = PyCapsule_GetPointer(foo_capsule, "foo_abi");
// Skip error checking for simplicity
if (PyCapsule_GetSize(foo_capsule) >= offsetof(foo_abi, foo))
{
// foo() is available on this version
foo_abi->foo(42);
} else {
// foo() is not available! Use a fallback or throw an error!
/* ... */
}
}
Version and Module Getters
PyCapsule_GetModule
is the interface for getting a module object out of the capsule. Since this might be NULL
, this function returns an integer denoting the result of the call (-1
, 0
, or 1
) and takes a pointer to an output parameter to store the module (PyObject **
).
If this function fails, the pointer is set to NULL
, and returns -1
. If it succeeds, but the capsule does not have a module set, then this function returns 0
with the output pointer set to NULL
. Otherwise, this returns 1
and sets the pointer to a strong reference to the module.
PyCapsule_GetMajorVersion
, on the other hand, returns the major version number passed to NewVersioned
, 0
if it was initialized the old way, and -1
if an error occurred. For both of these functions, if the object passed was not a capsule, a TypeError
is raised.
It’s up to the developer to decide how to deal with backwards compatibility with these functions – ideally, they will just do it the old way, and drop support for it as they decide to support new versions. For example, if foo()
from earlier wanted to access bar
on the module:
PyObject *
foo_impl(int whatever)
{
PyObject *capsule = PyCapsule_ImportVersioned("foo.foo_abi", MAJOR_VERSION, sizeof(FooABI)); // See specification below
PyObject *module;
if (PyCapsule_GetModule(capsule, &module) < 0)
return NULL;
if (module == NULL)
{
/* ABI was initialized with the legacy PyCapsule_New, we'll just throw an error */
PyErr_SetString(PyExc_RuntimeError, "your python version is too low!");
return NULL;
// Note that in a real-world scenario, the ABI would have some fallback module field
}
PyObject *attr = PyObject_GetAttrString(module, "bar");
if (attr == NULL)
return NULL;
/* Rest of foo() */
}
Import Utilities
PyCapsule_GetFromModule
takes the following:
- Any module object passed to
PyCapsule_NewVersioned
, or a module that contains aPy_mod_capsule
module slot. - Qualified capsule name. This may be
NULL
, per the current capsule API. - Major version number.
- Minimum size of the ABI structure.
GetFromModule
will raise a RuntimeError
if the major version or module on the returned capsule does not match what was passed.
Now, with PyCapsule_GetFromModule
, the ABI function for foo
from earlier would look like:
#define MAJOR_VERSION 2
static PyObject *
foo(int whatever) {
PyObject *foo_mod = PyImport_ImportModule("foo", 0);
if (foo_mod == NULL)
return NULL;
// This will either call the Py_mod_capsule slot, if it's defined, or
// try and access the foo_abi attribute on the module.
PyObject *capsule = PyCapsule_GetFromModule(
foo_mod,
"foo.foo_abi"
MAJOR_VERSION,
sizeof(FooABI)
)
Py_DECREF(foo_mod);
if (capsule == NULL)
return NULL;
FooABI *foo_abi = PyCapsule_GetPointer(capsule, "foo.foo_abi");
// We can't DECREF the capsule until after foo() has been called, since
// technically, we don't have a real reference to it, meaning
// that it can be deallocated upon the call of Py_DECREF
if (foo_abi == NULL) {
Py_DECREF(capsule);
return NULL;
}
PyObject *result = foo_abi->foo(whatever);
Py_DECREF(capsule);
return result;
}
The main upside of this function is that now, if you were to use this ABI from outside the header file – such as through an FFI – then it would be trivial to implement. Example with ctypes
:
from ctypes import pythonapi, Structure, POINTER
import foo
# Initialize argtypes and restype for GetFromModule and GetPointer
# ...
class FooABI(Structure):
_fields_ = [
("foo", ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_int))
]
capsule = pythonapi.PyCapsule_GetFromModule(
foo,
"foo_abi",
2,
0
)
abi = ctypes.cast(
pythonapi.PyCapsule_GetPointer(capsule, "foo.foo_abi"),
POINTER(FooABI)
)
PyCapsule_ImportVersioned
is a utility for making this process shorter. Unlike PyCapsule_Import
, ImportVersioned
returns the capsule object, not the underlying ABI, since the user needs to hold a reference to the capsule. It takes all the same parameters as GetFromModule
(with the exception of no module
parameter, and the capsule_name
should be a qualified name including the module), and should be preferred over GetFromModule
where possible.
Now, the foo
function from earlier looks like:
#define MAJOR_VERSION 2
static PyObject *
foo(int whatever) {
PyObject *capsule = PyCapsule_ImportVersioned(
"foo.foo_abi",
MAJOR_VERSION,
sizeof(FooABI)
)
if (capsule == NULL)
return NULL;
FooABI *foo_abi = PyCapsule_GetPointer(capsule, "foo.foo_abi");
if (foo_abi == NULL) {
Py_DECREF(capsule);
return NULL;
}
PyObject *result = foo_abi->foo(whatever);
Py_DECREF(capsule);
return result;
}
Changes to the current capsule import implementation
Note that as stated earlier, an edge case exists in the current implementation of PyCapsule_Import
. Since fixing PyCapsule_Import
would technically be a breaking change, ImportVersioned
is the fixed alternative.
Due to this problem, this PEP will deprecate PyCapsule_Import("foo.foo_abi", 0)
in favor of PyCapsule_ImportVersioned("foo.foo_abi", 0, 0)
. Although, this is a soft deprecation, as stated earlier – there will be no actual plan for removal in the future, just new use of PyCapsule_Import
will be discouraged.
With that being said, it’s important to make some changes to PyCapsule_Import
to allow a proper migration path for existing ABIs. If a module imported by PyCapsule_Import
has a Py_mod_capsule
slot, as shown below, then Import
will call it with a major version of 0
and the qualified name instead of attempting to access the attribute of the module. Note that, unlike the new capsule import API, if the major version was 0
and a different major version is on the returned capsule, this will not raise an error. This may seem a bit counterintuitive and error-prone – but it’s important for backwards compatibility.
This means that if foo.foo_abi_v1
were to be passed to PyCapsule_Import
, then the call to the module slot would have:
- The imported module object for
foo
. foo.foo_abi_v1
as the qualified name.- Major version of
0
.
We’re getting a bit ahead of ourselves, though. More on this in the backwards compatibility section.
Capsule Validation
Similar to the existing PyCapsule_IsValid
function, this PEP introduces a PyCapsule_IsValidWithVersion
function. Once again similar to the other additions to the C API in this PEP, this function takes a capsule object, the capsule name, a reference to the module (which may be NULL
, if it was initialized the old way), a major version, and a minimum size. IsValidWithVersion
acts as an extension of PIsValid
(i.e., it makes the same checks, with some additional ones), so IsValidWithVersion
does everything that IsValid
does, with all of the following:
- Ensuring the module passed and the module on the capsule are equivalent (including when they are both
NULL
). - Checking if the
major_version
matches that on the capsule. - Making sure the size on the capsule is greater than or equal to the minimum size passed.
Once again, this function can be used to check compatibility with the legacy ABI by passing NULL
, 0
, and 0
for the module, major version, and size arguments. Note that the existing PyCapsule_IsValid
will not check these values by default, instead it will just ignore them. To stay similar to IsValid
, IsValidWithVersion
returns 1
indicating true, and 0
indicating false.
This function is useful if you got a capsule through some other means, and want to make sure it’s the one you want. For example:
int
some_user_function(PyObject *capsule)
{
// Ensure we have the legacy ABI
if (!PyCapsule_IsValidWithVersion(capsule, "foo.foo_abi", NULL, 0, 0)) {
PyErr_SetString(PyExc_RuntimeError, "expected legacy ABI");
return -1;
}
/* ... */
return 0;
}
New module slot
This PEP adds a Py_mod_capsule
slot, which will be paired with a PyObject *(capsule_getter *)(PyObject *module, const char *qualified_name, int32_t version)
– this will be the preferred alternative over adding the capsule to an attribute.
PyCapsule_*
functions (including the existing ones) will choose to use the new slot if the module defines it, otherwise, it will do it the old way. For example, with foo
previously, it would now expose an ABI as such:
const FooABI foo_abi_v1 = {
/* ... */
};
static PyObject *
capsule_getter(PyObject *self, const char *qualname, int32_t major_version) {
return PyCapsule_NewABI(
foo_abi_v1,
"foo.foo_abi",
NULL,
self,
1,
sizeof(FooABI)
);
}
static PyModuleDef_Slot ModuleSlots[] = {
{Py_mod_capsule, capsule_getter},
{0, NULL}
};
static struct PyModuleDef FooModule = {
PyModuleDef_HEAD_INIT,
"foo",
/* Other fields */
.m_slots = ModuleSlots
};
A module may not define multiple Py_mod_capsule
slots.
ctypes
ABI Support
As mentioned earlier, this PEP introduces a new ctypes.PyABI
class. Also mentioned earlier, this class behaves like, and inherits from, ctypes.Structure
. Users should set the _fields_
class attribute to define an ABI structure, just like a Structure
. PyABI
also comes with two instance attributes:
_capsule_
, which is a reference to the capsule object itself. This is of typetypes.CapsuleType
._capsule_size_
, which is actypes.c_ssize_t
containing the size of the ABI structure, if it’s known.
This class also takes two subclass arguments:
size_field
, which is a string containing the name of the field that provides the size of the ABI structure. For example, if this wassize
, thenPyABI
would use the value of thesize
field at runtime to set the_capsule_size_
attribute. By default, this isNone
, in which case the value of_capsule_size_
is deferred to thedefault_size
parameter below.default_size
is pretty self-explanatory – it’s the default value of_capsule_size_
if the size on the capsule object is zero (due to it being explicitly set that way or created with a legacy API).
If both of these arguments are passed with non-default values (e.g., class FooABI(ctypes.PyABI, size_field="size", default_size=42)
), a ValueError
is raised, since these parameters counteract each other.
Now, since PyABI
inherits from Structure
, it can be instantiated the same way – but the user probably doesn’t want to do that. Instead, they want to use from_capsule
, which will take a capsule object and map it to the structure automatically, as well as doing the proper version checking. from_capsule
has three parameters:
- A module object, or a string containing a fully qualified location of the capsule. Depending on the value of this parameter,
ctypes
will choose which capsule import function to use. - The capsule name itself (this has been
foo.foo_abi
in the examples throughout this PEP). This isNone
by default, in which caseNULL
is passed as the name. - The major version number of the ABI. This is
0
by default. - The minimum size of the ABI. This is also
0
by default, meaning that the check is technically skipped (since an ABI size cannot be less than zero).
As mentioned, the mechanism to import the capsule object is dependent on the first parameter. If the argument is a module, then PyCapsule_GetFromModule
is used to get the capsule object – otherwise PyCapsule_ImportVersioned
is used.
Finally, the last quirk of PyABI
, is that if _capsule_size_
is set, it checks that the size is greater than the offset of a value upon attribute lookup. If not, a RuntimeError
is raised.
For example, to use FooABI
from ctypes
would look like:
import ctypes
import foo
class FooABI(ctypes.PyABI):
_fields_ = [
("foo", ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_int)
]
abi = FooABI.from_capsule(foo, "foo.foo_abi", major_version=2)
abi.foo(42) # Will ensure that size > offsetof(foo)
Backwards Compatibility
Capsule Imports
PyCapsule_New
initializes both the size
and major_version
to zero – this means that it’s the first version of the ABI. As mentioned earlier, PyCapsule_NewVersioned
can also explicitly set these values to zero to denote that it’s compatible with the first version.
Likewise, PyCapsule_Import
will call the Py_mod_capsule
slot with a major version of 0
. This retains C API compatibility – if an ABI were to use PyCapsule_New
and its users were to use PyCapsule_Import
forever, nothing would change. However, if this ABI wanted to make changes someday, it could then bump the major version for new changes, and users that explicitly ask for the new version will receive it.
Sizes
In the rejected ideas section later, a standard Py_ssize_t size
at the beginning of an actual ABI structure was part of this specification. This was decided against, but it can still be used as a replacement for the capsule-level size
in the meantime. If an ABI does not provide a way to access the size, then they lack that backport mechanism for this PEP – users will just have to ignore size/minor version checking.
For example, if FooABI
from the beginning of this PEP wanted to provide a size
on their ABI, it could add the field like so:
typedef struct {
PyObject *(foo *)(int); // PyObject *foo(int);
Py_ssize_t size;
} FooABI;
const FooABI foo_abi = {
foo,
sizeof(FooABI)
}
Now, a downstream user could use it like such:
#if Py_MINOR_VERSION < 14
#define SIZE(abi) abi->size
#else
// Technically, PyCapsule_GetSize could return
// an error, but we're treating it like a
// field - ignore that.
#define SIZE(abi) PyCapsule_GetSize(abi)
#endif
static int
user_function() {
// We should use PyCapsule_Import here, since
// it's backwards compatible and the major version
// for foo_abi is zero
FooABI *foo_abi = PyCapsule_Import("foo.foo_abi", 0);
if (foo_abi == NULL) {
return -1;
}
if (offsetof(FooABI, foo) <= SIZE(foo_abi)) {
foo_abi->foo(42);
} else {
// Use some fallback
}
}
Major Versions
On Python versions <3.14, an ABI can store the major version in the capsule name itself (e.g., foo_abi_v2
), and then use PyCapsule_Import
to import it. Going with the previous example, that would be PyCapsule_Import("foo.foo_abi_v2", 0)
. Then, on newer versions, a Py_mod_capsule
slot could return the v2
ABI when foo_abi_v2
is requested. This is the purpose of the special case when returning different major versions in PyCapsule_Import
– existing code that imports the ABI the old way will still work, assuming the library handles it in their Py_mod_capsule
slot. For example, the capsule getter for the previous example could look like:
static PyObject *
capsule_getter(PyObject *self, const char *qualname, int32_t major_version)
{
FooABI *foo_abi = malloc(sizeof(FooABI));
/* Initialize fields... */
if (major_version == 2 || !strcmp(qualname, "foo.foo_abi_v2"))
{
foo_abi->foo = new_foo;
}
return PyCapsule_NewVersioned(/* ... */);
}