@@ -280,6 +280,55 @@ typedef struct {
280280 PyObject * exception_traceback ; /* Stored exception traceback */
281281} CallState ;
282282
283+ /**
284+ * @brief Compatibility layer for Python exception handling API changes.
285+ * PyErr_Fetch was deprecated in Python 3.12 in favor of PyErr_GetRaisedException.
286+ */
287+ #if PY_VERSION_HEX >= 0x030c0000
288+ /* Python 3.12+ - use new API */
289+ static inline void
290+ CallState_fetch_exception (CallState * cs ) {
291+ PyObject * exc = PyErr_GetRaisedException ();
292+ if (exc ) {
293+ cs -> exception_type = (PyObject * )Py_TYPE (exc );
294+ Py_INCREF (cs -> exception_type );
295+ cs -> exception_value = exc ;
296+ cs -> exception_traceback = PyException_GetTraceback (exc );
297+ } else {
298+ cs -> exception_type = NULL ;
299+ cs -> exception_value = NULL ;
300+ cs -> exception_traceback = NULL ;
301+ }
302+ }
303+
304+ static inline void
305+ CallState_restore_exception (CallState * cs ) {
306+ if (cs -> exception_value ) {
307+ PyErr_SetRaisedException (cs -> exception_value );
308+ /* PyErr_SetRaisedException steals the reference, so clear our pointer */
309+ cs -> exception_value = NULL ;
310+ Py_XDECREF (cs -> exception_type );
311+ cs -> exception_type = NULL ;
312+ Py_XDECREF (cs -> exception_traceback );
313+ cs -> exception_traceback = NULL ;
314+ }
315+ }
316+ #else
317+ /* Python < 3.12 - use legacy API */
318+ static inline void
319+ CallState_fetch_exception (CallState * cs ) {
320+ PyErr_Fetch (& cs -> exception_type , & cs -> exception_value , & cs -> exception_traceback );
321+ }
322+
323+ static inline void
324+ CallState_restore_exception (CallState * cs ) {
325+ PyErr_Restore (cs -> exception_type , cs -> exception_value , cs -> exception_traceback );
326+ cs -> exception_type = NULL ;
327+ cs -> exception_value = NULL ;
328+ cs -> exception_traceback = NULL ;
329+ }
330+ #endif
331+
283332/**
284333 * @brief Initialiase a CallState and unlock the GIL prior to a
285334 * possibly blocking external call.
0 commit comments