Skip to content

Commit

Permalink
Work around missing features in Python < 3.9
Browse files Browse the repository at this point in the history
  • Loading branch information
tiran committed Sep 29, 2021
1 parent 74c5a38 commit 0c37cd2
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 9 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ jobs:
env:
WRAPT_INSTALL_EXTENSIONS: true # Require extensions.
CIBW_SKIP: pp* # Skip wheels for PyPy.
CIBW_BUILD: cp36-* # only build on 3.6 for stable cp36-abi3
CIBW_BUILD: cp3{6789}-* # only build on 3.6 - 3.9
# can only build stable abi for cp39-abi3
- uses: actions/upload-artifact@v2
with:
name: dist
Expand Down
1 change: 0 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ where=src

[bdist_wheel]
universal = false
py_limited_api=cp36

# --- Test and coverage configuration ------------------------------------------

Expand Down
34 changes: 32 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import os
import platform

import sys
import setuptools

try:
from wheel.bdist_wheel import bdist_wheel
except ImportError:
bdist_wheel = None


# # --- Detect if extensions should be disabled ------------------------------

Expand All @@ -17,20 +24,43 @@
if platform.python_implementation() != "CPython":
disable_extensions = True

# # --- stable ABI / limited API hacks ---------------------------------------
# Python < 3.9 is missing some features to build wheels with stable ABI

define_macros = []
cmdclass = {}

if sys.version_info >= (3, 9, 0) and platform.python_implementation() == "CPython":
py_limited_api = True
define_macros.append(("Py_LIMITED_API", "0x03090000"))

if bdist_wheel is not None:
class LimitedAPIWheel(bdist_wheel):
def finalize_options(self):
self.py_limited_api = "cp39"
bdist_wheel.finalize_options(self)

cmdclass["bdist_wheel"] = LimitedAPIWheel
else:
py_limited_api = False


# --- C extension ------------------------------------------------------------

extensions = [
setuptools.Extension(
"wrapt._wrappers",
sources=["src/wrapt/_wrappers.c"],
optional=not force_extensions,
py_limited_api=True,
py_limited_api=py_limited_api,
define_macros=define_macros,
)
]


# --- Setup ------------------------------------------------------------------

setuptools.setup(
ext_modules=[] if disable_extensions else extensions
ext_modules=[] if disable_extensions else extensions,
cmdclass=cmdclass
)
37 changes: 32 additions & 5 deletions src/wrapt/_wrappers.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
/* ------------------------------------------------------------------------- */

#ifndef Py_LIMITED_API
/* stable ABI Python >= 3.6, keep in sync with setup.cfg py_limited_api */
#define Py_LIMITED_API 0x03060000
#endif

#include "Python.h"

#include "structmember.h"
Expand All @@ -13,6 +8,20 @@
#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size,
#endif

/* Heap types in Python < 3.9 don't support __weaklistoffset__ and
* __dictoffset__ in PyMemberDef.
*/
#if PY_VERSION_HEX >= 0x03090000
#define WRAPT_HEAPTYPE_SUPPORT_OFFSET 1
#endif

/* Instanes of heap types in Python < 3.8 don't hold strong ref to their
* type object.
*/
#if PY_VERSION_HEX >= 0x03080000
#define WRAPT_HEAPTYPE_STRONG_TYPEREF 1
#endif

/* ------------------------------------------------------------------------- */

typedef struct {
Expand Down Expand Up @@ -147,6 +156,7 @@ static int WraptObjectProxy_init(WraptObjectProxyObject *self,
static int WraptObjectProxy_traverse(WraptObjectProxyObject *self,
visitproc visit, void *arg)
{
Py_VISIT(Py_TYPE(self));
Py_VISIT(self->dict);
Py_VISIT(self->wrapped);

Expand All @@ -167,7 +177,10 @@ static int WraptObjectProxy_clear(WraptObjectProxyObject *self)

static void WraptObjectProxy_dealloc(WraptObjectProxyObject *self)
{
#ifdef WRAPT_HEAPTYPE_STRONG_TYPEREF
/* Type decref causes segfaults in <= 3.7 */
PyTypeObject *tp = Py_TYPE(self);
#endif

PyObject_GC_UnTrack(self);

Expand All @@ -179,7 +192,9 @@ static void WraptObjectProxy_dealloc(WraptObjectProxyObject *self)
freefunc free_func = PyType_GetSlot(Py_TYPE(self), Py_tp_free);
free_func(self);

#ifdef WRAPT_HEAPTYPE_STRONG_TYPEREF
Py_DECREF(tp);
#endif
}

/* ------------------------------------------------------------------------- */
Expand Down Expand Up @@ -1589,8 +1604,10 @@ static PyGetSetDef WraptObjectProxy_getset[] = {
};

static struct PyMemberDef WraptObjectProxy_Type_members[] = {
#ifdef WRAPT_HEAPTYPE_SUPPORT_OFFSET
{"__weaklistoffset__", T_PYSSIZET, offsetof(WraptObjectProxyObject, weakreflist), READONLY},
{"__dictoffset__", T_PYSSIZET, offsetof(WraptObjectProxyObject, dict), READONLY},
#endif
{NULL},
};

Expand Down Expand Up @@ -1697,6 +1714,7 @@ static PyType_Spec WraptCallableObjectProxy_Type_spec = {
"CallableObjectProxy",
sizeof(WraptObjectProxyObject),
0,
/* Only define HAVE_GC if the object defines custom traverse and clear */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
WraptCallableObjectProxy_Type_slots
};
Expand Down Expand Up @@ -2772,12 +2790,21 @@ init_type(PyObject *module, PyObject **newtype, PyType_Spec *spec,
return -1;
}
}
#if PY_VERSION_HEX >= 0x03090000
*newtype = PyType_FromModuleAndSpec(module, spec, bases);
#else
*newtype = PyType_FromSpecWithBases(spec, bases);
#endif
Py_XDECREF(bases);
if (*newtype == NULL) {
return -1;
}
assert(((PyTypeObject *)*newtype)->tp_traverse != NULL);
#ifndef WRAPT_HEAPTYPE_SUPPORT_OFFSET
/* hack for Python <= 3.8 */
((PyTypeObject *)*newtype)->tp_weaklistoffset = offsetof(WraptObjectProxyObject, weakreflist);
((PyTypeObject *)*newtype)->tp_dictoffset = offsetof(WraptObjectProxyObject, dict);
#endif
}

if (PyModule_AddObject(module, attrname, *newtype) < 0) {
Expand Down

0 comments on commit 0c37cd2

Please sign in to comment.