From: Claude Code <noreply@anthropic.com>
Subject: [PATCH] Fix PyQt4 compatibility with Python 3.11+

This patch fixes PyQt4 (v4.12.x) for compatibility with Python 3.11, 3.12, and 3.13.

Changes:

1. Fix PyFrameObject access (frame->f_back) in qpycore_classinfo.cpp - CRITICAL for Python 3.11+
   - Use PyFrame_GetBack() API which properly handles strong references

2. Replace deprecated PyWeakref_GetObject() in qpycore_qtlib.cpp for Python 3.13+
   - Use PyWeakref_GetRef() with proper signature (returns int, takes PyObject** out param)

3. Replace removed PyCFunction_Call() in qpycore_pyqtsignal.cpp for Python 3.9+
   - Use PyObject_Call() which works for all callable objects

4. Replace removed PyEval_CallObject() in qpycore_qtlib.cpp for Python 3.13+
   - Use PyObject_Call() with NULL for kwargs

5. Replace removed PyObject_AsCharBuffer() in qpycore_qstring.cpp for Python 3.13+
   - Use new buffer protocol: PyObject_GetBuffer() with PyBuffer_Release()
   - Convert buffer to bytes to ensure data lifetime

6. Add missing struct field initializers for Python 3.13 in all PyTypeObject declarations:
   - tp_vectorcall (Python 3.8+)
   - tp_watched (Python 3.13+)
   - tp_versions_used (Python 3.13+)

diff -Naur PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_classinfo.cpp PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_classinfo.cpp
--- PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_classinfo.cpp	2018-08-31 07:40:13.000000000 +0000
+++ PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_classinfo.cpp	2026-03-10 10:01:21.995513387 +0000
@@ -36,7 +36,17 @@
 
     // We need the frame we were called from, not the current one.
     if (frame)
+    {
+#if PY_VERSION_HEX >= 0x030b0000
+        /* Python 3.11+: use PyFrame_GetBack() which returns a strong reference */
+        PyFrameObject *back = PyFrame_GetBack(frame);
+        Py_XDECREF(frame);
+        frame = back;
+#else
+        /* Python 3.10 and earlier: direct structure access */
         frame = frame->f_back;
+#endif
+    }
 
     if (!frame)
     {
diff -Naur PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_pyqtboundsignal.cpp PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_pyqtboundsignal.cpp
--- PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_pyqtboundsignal.cpp	2018-08-31 07:40:13.000000000 +0000
+++ PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_pyqtboundsignal.cpp	2026-03-10 10:01:47.287166208 +0000
@@ -184,6 +184,15 @@
 #if PY_VERSION_HEX >= 0x03040000
     0,                      /* tp_finalize */
 #endif
+#if PY_VERSION_HEX >= 0x03080000
+    0,                      /* tp_vectorcall */
+#endif
+#if PY_VERSION_HEX >= 0x030d0000
+    0,                      /* tp_watched */
+#endif
+#if PY_VERSION_HEX >= 0x030d0000
+    0,                      /* tp_versions_used */
+#endif
 };
 
 
diff -Naur PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_pyqtmethodproxy.cpp PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_pyqtmethodproxy.cpp
--- PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_pyqtmethodproxy.cpp	2018-08-31 07:40:13.000000000 +0000
+++ PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_pyqtmethodproxy.cpp	2026-03-10 10:01:47.763178496 +0000
@@ -99,6 +99,15 @@
 #if PY_VERSION_HEX >= 0x03040000
     0,                      /* tp_finalize */
 #endif
+#if PY_VERSION_HEX >= 0x03080000
+    0,                      /* tp_vectorcall */
+#endif
+#if PY_VERSION_HEX >= 0x030d0000
+    0,                      /* tp_watched */
+#endif
+#if PY_VERSION_HEX >= 0x030d0000
+    0,                      /* tp_versions_used */
+#endif
 };
 
 
diff -Naur PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_pyqtsignal.cpp PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_pyqtsignal.cpp
--- PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_pyqtsignal.cpp	2018-08-31 07:40:13.000000000 +0000
+++ PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_pyqtsignal.cpp	2026-03-10 10:10:46.401099609 +0000
@@ -132,6 +132,15 @@
 #if PY_VERSION_HEX >= 0x03040000
     0,                      /* tp_finalize */
 #endif
+#if PY_VERSION_HEX >= 0x03080000
+    0,                      /* tp_vectorcall */
+#endif
+#if PY_VERSION_HEX >= 0x030d0000
+    0,                      /* tp_watched */
+#endif
+#if PY_VERSION_HEX >= 0x030d0000
+    0,                      /* tp_versions_used */
+#endif
 };
 
 
@@ -658,7 +667,12 @@
     if (!func)
         return 0;
 
+#if PY_VERSION_HEX >= 0x03090000
+    /* Python 3.9+: PyCFunction_Call deprecated, use PyObject_Call */
+    PyObject *result = PyObject_Call(func, args, kw);
+#else
     PyObject *result = PyCFunction_Call(func, args, kw);
+#endif
 
     Py_DECREF(func);
 
diff -Naur PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_qstring.cpp PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_qstring.cpp
--- PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_qstring.cpp	2018-08-31 07:40:13.000000000 +0000
+++ PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_qstring.cpp	2026-03-10 10:15:12.857993744 +0000
@@ -298,10 +298,28 @@
         Py_INCREF(obj);
     }
 #endif
+#if PY_VERSION_HEX >= 0x030d0000
+    /* Python 3.13+: PyObject_AsCharBuffer removed, use new buffer protocol */
+    else if (PyObject_CheckBuffer(obj))
+    {
+        Py_buffer view;
+        if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) >= 0)
+        {
+            /* Convert to bytes to ensure data lifetime */
+            obj = PyBytes_FromStringAndSize((const char *)view.buf, view.len);
+            PyBuffer_Release(&view);
+            if (obj)
+            {
+                es = PyBytes_AS_STRING(obj);
+            }
+        }
+    }
+#else
     else if (PyObject_AsCharBuffer(obj, &es, &sz) >= 0)
     {
         Py_INCREF(obj);
     }
+#endif
 
     if (es)
     {
diff -Naur PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_qtlib.cpp PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_qtlib.cpp
--- PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_qtlib.cpp	2018-08-31 07:40:13.000000000 +0000
+++ PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_qtlib.cpp	2026-03-10 10:15:18.261133570 +0000
@@ -45,12 +45,27 @@
     }
     else
     {
+#if PY_VERSION_HEX >= 0x030d0000
+        /* Python 3.13+: PyWeakref_GetRef returns 1 if alive, 0 if dead, -1 on error */
+        int rc = PyWeakref_GetRef(slot->weakSlot, &sref);
+        if (rc < 0)
+            return 0;  /* Error */
+        if (rc == 0)
+        {
+            /* Referent is dead, return Py_None */
+            Py_INCREF(Py_None);
+            return Py_None;
+        }
+        /* rc == 1: sref now holds a strong reference, no need to INCREF */
+#else
         sref = PyWeakref_GetObject(slot->weakSlot);
 
         if (!sref)
             return 0;
+#endif
     }
 
+#if PY_VERSION_HEX < 0x030d0000
     if (sref == Py_None)
     {
         // If the real object has gone then we pretend everything is Ok.  This
@@ -61,6 +76,7 @@
     }
 
     Py_XINCREF(sref);
+#endif
 
     if (!slot->pyobj)
     {
@@ -127,7 +143,12 @@
 
     for (;;)
     {
+#if PY_VERSION_HEX >= 0x030d0000
+        /* Python 3.13+: PyEval_CallObject removed, use PyObject_Call */
+        PyObject *resobj = PyObject_Call(sfunc, sa, NULL);
+#else
         PyObject *resobj = PyEval_CallObject(sfunc, sa);
+#endif
 
         if (resobj)
         {
diff -Naur PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_types.cpp PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_types.cpp
--- PyQt4_gpl_mac-4.12.3.orig/qpy/QtCore/qpycore_types.cpp	2018-08-31 07:40:13.000000000 +0000
+++ PyQt4_gpl_mac-4.12.3/qpy/QtCore/qpycore_types.cpp	2026-03-10 10:01:48.209190009 +0000
@@ -146,6 +146,15 @@
 #if PY_VERSION_HEX >= 0x03040000
     0,                      /* tp_finalize */
 #endif
+#if PY_VERSION_HEX >= 0x03080000
+    0,                      /* tp_vectorcall */
+#endif
+#if PY_VERSION_HEX >= 0x030d0000
+    0,                      /* tp_watched */
+#endif
+#if PY_VERSION_HEX >= 0x030d0000
+    0,                      /* tp_versions_used */
+#endif
 };
 
 
