Description: plugins/python: work around Numpy 2.3 "feature"
 Since Numpy 2.3, 'np.bool' scalars can no longer be interpreted as 
 an index. As a consequence, PyLong_AsLong fails on them.
 Modify Metric::Python::isStopCondition to behave seamlessly when the
 underlying Python code returns a np.bool nevertheless.
Author: Thibaut Paumard <thibaut@debian.org>
Origin: upstream
Bug-Debian: https://bugs.debian.org/1116436
Applied-Upstream: 05bdd0c428ddb4cb78f23ae3d5178ce959b90b34
Last-Update: 2025-10-21
---
This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
diff --git a/plugins/python/lib/Metric.C b/plugins/python/lib/Metric.C
index ccc8b30..3be3c74 100644
--- a/plugins/python/lib/Metric.C
+++ b/plugins/python/lib/Metric.C
@@ -366,19 +366,49 @@ double Metric::Python::getPotential
 int Metric::Python::isStopCondition
 (double const coord[8])
   const {
+  /* This method, part of the Gyoto::Metric::Generic API, wraps around the
+     Python method of same same in a Python class.
+   */
+
+  // If this method was not found is the Python class, then the
+  // pIsStopCondition_ pointer is null.
   if (!pIsStopCondition_)
     return Metric::Generic::isStopCondition(coord);
 
+  // All functions need to claim the GIL state.
   PyGILState_STATE gstate = PyGILState_Ensure();
 
+  // Wrap the coord input argument as a Python array.
   npy_intp dims_coord[] = {8};
-
   PyObject * pPo = PyArray_SimpleNewFromData(1, dims_coord, NPY_DOUBLE, const_cast<double*>(coord));
+
+  // Execute the Python isStopCondition method. This creates a new
+  // Python object *pR to hold tge result.
   PyObject * pR =
     PyObject_CallFunctionObjArgs(pIsStopCondition_, pPo, NULL);
 
+  // Release the Python array holding the coord input argument.
   Py_XDECREF(pPo);
 
+  // Check for errors.
+  if (PyErr_Occurred()) {
+    Py_XDECREF(pR);
+    PyErr_Print();
+    PyGILState_Release(gstate);
+    GYOTO_ERROR("Error occurred in Metric::isStopCondition()");
+  }
+
+  // Interpret the result depending on its Python type.
+  int res = 0;
+  if (PyLong_Check(pR)) {
+    GYOTO_DEBUG << "Python code returned a Long, return it unchanged.\n";
+    res =  PyLong_AsLong(pR);
+  } else {
+    GYOTO_DEBUG << "Python code did not return a Long, return truth value.\n";
+    res = PyObject_IsTrue(pR);
+  }
+
+  // Check for errors.
   if (PyErr_Occurred()) {
     Py_XDECREF(pR);
     PyErr_Print();
@@ -386,10 +416,11 @@ int Metric::Python::isStopCondition
     GYOTO_ERROR("Error occurred in Metric::isStopCondition()");
   }
 
-  int res = PyLong_AsLong(pR);
+  // Release all temporary Python objects as well as the GIL.
   Py_XDECREF(pR);
   PyGILState_Release(gstate);
 
+  // Return the result.
   return res;
 }
 
