Replace use of tp_name in builtin wrappers for Py_LIMITED_API support

Add SWIG_PyType_GetFullyQualifiedName which is just a wrapper around
PyType_GetFullyQualifiedName, but is only available in python-3.13.
Code up the equivalent for earlier versions - loosely based on the
python-3.13 implementation.

PyType_GetFullyQualifiedName is recommended in PEP-737 for getting the
fully qualified type name of a type.
This commit is contained in:
William S Fulton 2025-07-02 23:30:42 +01:00
parent 5ea4449c3e
commit af6120329c
6 changed files with 132 additions and 9 deletions

View File

@ -81,6 +81,7 @@ CPP_TEST_CASES += \
python_strict_unicode \
python_threads \
python_typemap_macro \
python_various \
simutry \
std_containers \
swigobject \

View File

@ -0,0 +1,26 @@
from python_various import *
import datetime
import sys
class MyClass:
pass
# Only implemented for python 3
if sys.version_info[0:2] < (3, 0):
exit(0)
type = GetFullyQualifiedName(1234) # Test builtins type
if type != "int":
raise RuntimeError("wrong type {}".format(type))
type = GetFullyQualifiedName(MyClass()) # Test __main__ type
if type != "MyClass":
raise RuntimeError("wrong type {}".format(type))
type = GetFullyQualifiedName(WrappedClass()) # Test a SWIG wrapped class
if type != "python_various.WrappedClass":
raise RuntimeError("wrong type {}".format(type))
type = GetFullyQualifiedName(datetime.date(2020, 1, 20)) # Test a Python standard library class
if type != "datetime.date":
raise RuntimeError("wrong type {}".format(type))

View File

@ -0,0 +1,14 @@
%module python_various
%inline %{
struct WrappedClass {};
// A unit test for SWIG_PyType_GetFullyQualifiedName
PyObject *GetFullyQualifiedName(PyObject *type) {
#if PY_VERSION_HEX >= 0x03000000
return SWIG_PyType_GetFullyQualifiedName(Py_TYPE(type));
#else
return type;
#endif
}
%}

View File

@ -32,7 +32,13 @@ SWIG_PyNumber_AsPyHash(PyObject *obj) {
SWIGINTERN int
SwigPyBuiltin_BadInit(PyObject *self, PyObject *SWIGUNUSEDPARM(args), PyObject *SWIGUNUSEDPARM(kwds)) {
#if PY_VERSION_HEX >= 0x03000000
PyObject *tpname = SWIG_PyType_GetFullyQualifiedName(Py_TYPE(self));
PyErr_Format(PyExc_TypeError, "Cannot create new instances of type '%S'", tpname);
SWIG_Py_DECREF(tpname);
#else
PyErr_Format(PyExc_TypeError, "Cannot create new instances of type '%s'", Py_TYPE(self)->tp_name);
#endif
return -1;
}
@ -40,7 +46,13 @@ SWIGINTERN void
SwigPyBuiltin_BadDealloc(PyObject *obj) {
SwigPyObject *sobj = (SwigPyObject *)obj;
if (sobj->own) {
#if PY_VERSION_HEX >= 0x03000000
PyObject *tpname = SWIG_PyType_GetFullyQualifiedName(Py_TYPE(obj));
PyErr_Format(PyExc_TypeError, "Swig detected a memory leak in type '%S': no callable destructor found.", tpname);
SWIG_Py_DECREF(tpname);
#else
PyErr_Format(PyExc_TypeError, "Swig detected a memory leak in type '%s': no callable destructor found.", Py_TYPE(obj)->tp_name);
#endif
}
}
@ -87,12 +99,24 @@ SwigPyBuiltin_SetterClosure (PyObject *obj, PyObject *val, void *closure) {
return -1;
}
if (!val) {
#if PY_VERSION_HEX >= 0x03000000
PyObject *tpname = SWIG_PyType_GetFullyQualifiedName(Py_TYPE(obj));
PyErr_Format(PyExc_AttributeError, "Illegal member variable deletion in type '%S'", tpname);
SWIG_Py_DECREF(tpname);
#else
PyErr_Format(PyExc_AttributeError, "Illegal member variable deletion in type '%s'", Py_TYPE(obj)->tp_name);
#endif
return -1;
}
getset = (SwigPyGetSet *)closure;
if (!getset->set) {
#if PY_VERSION_HEX >= 0x03000000
PyObject *tpname = SWIG_PyType_GetFullyQualifiedName(Py_TYPE(obj));
PyErr_Format(PyExc_AttributeError, "Illegal member variable assignment in type '%S'", tpname);
SWIG_Py_DECREF(tpname);
#else
PyErr_Format(PyExc_AttributeError, "Illegal member variable assignment in type '%s'", Py_TYPE(obj)->tp_name);
#endif
return -1;
}
tuple = PyTuple_New(1);
@ -114,12 +138,24 @@ SwigPyBuiltin_FunpackSetterClosure (PyObject *obj, PyObject *val, void *closure)
return -1;
}
if (!val) {
#if PY_VERSION_HEX >= 0x03000000
PyObject *tpname = SWIG_PyType_GetFullyQualifiedName(Py_TYPE(obj));
PyErr_Format(PyExc_AttributeError, "Illegal member variable deletion in type '%S'", tpname);
SWIG_Py_DECREF(tpname);
#else
PyErr_Format(PyExc_AttributeError, "Illegal member variable deletion in type '%s'", Py_TYPE(obj)->tp_name);
#endif
return -1;
}
getset = (SwigPyGetSet *)closure;
if (!getset->set) {
#if PY_VERSION_HEX >= 0x03000000
PyObject *tpname = SWIG_PyType_GetFullyQualifiedName(Py_TYPE(obj));
PyErr_Format(PyExc_AttributeError, "Illegal member variable assignment in type '%S'", tpname);
SWIG_Py_DECREF(tpname);
#else
PyErr_Format(PyExc_AttributeError, "Illegal member variable assignment in type '%s'", Py_TYPE(obj)->tp_name);
#endif
return -1;
}
result = (*getset->set)(obj, val);
@ -138,7 +174,6 @@ SwigPyStaticVar_dealloc(PyDescrObject *descr) {
SWIGINTERN PyObject *
SwigPyStaticVar_repr(PyGetSetDescrObject *descr) {
#if PY_VERSION_HEX >= 0x03000000
return PyUnicode_FromFormat("<class attribute '%S' of type '%s'>", PyDescr_NAME(descr), PyDescr_TYPE(descr)->tp_name);
#else
return PyString_FromFormat("<class attribute '%s' of type '%s'>", PyString_AsString(PyDescr_NAME(descr)), PyDescr_TYPE(descr)->tp_name);
@ -181,24 +216,29 @@ SWIGINTERN int
SwigPyObjectType_setattro(PyObject *typeobject, PyObject *name, PyObject *value) {
PyObject *attribute;
PyTypeObject *type;
descrsetfunc local_set;
assert(PyType_Check(typeobject));
type = (PyTypeObject *)typeobject;
attribute = _PyType_Lookup(type, name);
if (attribute != NULL) {
/* Implement descriptor functionality, if any */
local_set = Py_TYPE(attribute)->tp_descr_set;
if (local_set != NULL)
return local_set(attribute, (PyObject *)type, value);
descrsetfunc local_set = Py_TYPE(attribute)->tp_descr_set;
if (local_set == NULL) {
#if PY_VERSION_HEX >= 0x03000000
PyErr_Format(PyExc_AttributeError, "cannot modify read-only attribute '%s.%S'", type->tp_name, name);
PyObject *tpname = SWIG_PyType_GetFullyQualifiedName(type);
PyErr_Format(PyExc_AttributeError, "cannot modify read-only attribute '%S.%S'", tpname, name);
SWIG_Py_DECREF(tpname);
#else
PyErr_Format(PyExc_AttributeError, "cannot modify read-only attribute '%s.%s'", type->tp_name, PyString_AS_STRING(name));
PyErr_Format(PyExc_AttributeError, "cannot modify read-only attribute '%s.%s'", type->tp_name, PyString_AS_STRING(name));
#endif
} else {
return local_set(attribute, (PyObject *)type, value);
}
} else {
#if PY_VERSION_HEX >= 0x03000000
PyErr_Format(PyExc_AttributeError, "type '%s' has no attribute '%S'", type->tp_name, name);
PyObject *tpname = SWIG_PyType_GetFullyQualifiedName(type);
PyErr_Format(PyExc_AttributeError, "type '%S' has no attribute '%S'", tpname, name);
SWIG_Py_DECREF(tpname);
#else
PyErr_Format(PyExc_AttributeError, "type '%s' has no attribute '%s'", type->tp_name, PyString_AS_STRING(name));
#endif

View File

@ -66,7 +66,7 @@ SWIG_PyUnicode_AsUTF8AndSize(PyObject *str, Py_ssize_t *psize, PyObject **pbytes
#endif
}
SWIGINTERN PyObject*
SWIGINTERN PyObject *
SWIG_Python_str_FromChar(const char *c)
{
#if PY_VERSION_HEX >= 0x03000000
@ -132,3 +132,31 @@ SWIG_Python_str_FromChar(const char *c)
# define SWIG_Py_DECREF Py_DECREF
# define SWIG_Py_XDECREF Py_XDECREF
#endif
#if PY_VERSION_HEX >= 0x03000000
SWIGINTERN PyObject *
SWIG_PyType_GetFullyQualifiedName(PyTypeObject *type) {
#if PY_VERSION_HEX >= 0x030d0000
return PyType_GetFullyQualifiedName(type);
#else
PyObject *result = NULL;
PyObject *qualname = PyObject_GetAttrString((PyObject *)type, "__qualname__");
if (qualname) {
PyObject *mod = PyObject_GetAttrString((PyObject *)type, "__module__");
if (mod) {
if (PyUnicode_Check(mod) && PyUnicode_CompareWithASCIIString(mod, "builtins") && PyUnicode_CompareWithASCIIString(mod, "__main__")) {
result = PyUnicode_FromFormat("%U%c%U", mod, '.', qualname);
SWIG_Py_DECREF(qualname);
} else {
result = qualname;
}
SWIG_Py_DECREF(mod);
} else {
result = qualname;
}
}
return result;
#endif
}
#endif

View File

@ -2082,7 +2082,13 @@ SWIG_Python_NonDynamicSetAttr(PyObject *obj, PyObject *name, PyObject *value) {
if (!PyString_Check(name))
# endif
{
#if PY_VERSION_HEX >= 0x03000000
PyObject *tpname = SWIG_PyType_GetFullyQualifiedName(Py_TYPE(name));
PyErr_Format(PyExc_TypeError, "attribute name must be string, not '%S'", tpname);
SWIG_Py_DECREF(tpname);
#else
PyErr_Format(PyExc_TypeError, "attribute name must be string, not '%s'", Py_TYPE(name)->tp_name);
#endif
return -1;
} else {
SWIG_Py_INCREF(name);
@ -2106,7 +2112,15 @@ SWIG_Python_NonDynamicSetAttr(PyObject *obj, PyObject *name, PyObject *value) {
if (!encoded_name)
goto done;
}
#if PY_VERSION_HEX >= 0x03000000
{
PyObject *tpname = SWIG_PyType_GetFullyQualifiedName(tp);
PyErr_Format(PyExc_AttributeError, "'%S' object has no attribute '%s'", tpname, PyString_AsString(encoded_name));
SWIG_Py_DECREF(tpname);
}
#else
PyErr_Format(PyExc_AttributeError, "'%s' object has no attribute '%s'", tp->tp_name, PyString_AsString(encoded_name));
#endif
SWIG_Py_DECREF(encoded_name);
} else {
res = f(descr, obj, value);