11#include " vtkMRMLLayerDMPythonUtil.h"
2+
23#include < vtkObjectFactory.h>
34
45vtkStandardNewMacro (vtkMRMLLayerDMPythonUtil);
@@ -62,7 +63,7 @@ vtkSmartPyObject vtkMRMLLayerDMPythonUtil::ToPyArgs(vtkObject* obj, unsigned lon
6263PyObject* vtkMRMLLayerDMPythonUtil::CastCallData (PyObject* object, int vtkType)
6364{
6465 vtkPythonScopeGilEnsurer gilEnsurer;
65- if (!Py_IsInitialized ())
66+ if (!IsValidPythonContext ())
6667 {
6768 return nullptr ;
6869 }
@@ -126,16 +127,24 @@ PyObject* vtkMRMLLayerDMPythonUtil::CastCallData(PyObject* object, int vtkType)
126127
127128PyObject* vtkMRMLLayerDMPythonUtil::CallPythonMethod (PyObject* object, const vtkSmartPyObject& pyArgs, const std::string& fName )
128129{
129- if (!Py_IsInitialized () || !object)
130+ if (!IsValidPythonContext () || !object)
130131 {
131132 return nullptr ;
132133 }
133134
135+ vtkPythonScopeGilEnsurer gilEnsurer;
134136 PyObject* method = PyObject_GetAttrString (object, fName .c_str ());
135- if (!method || !PyCallable_Check (method))
137+ if (!method)
138+ {
139+ // Don't modify Python's error reporting
140+ return nullptr ;
141+ }
142+
143+ if (!PyCallable_Check (method))
136144 {
137- const std::string errorString = " Method is not callable : " + fName ;
138- PyErr_SetString (PyExc_ValueError, errorString.c_str ());
145+ // PyCallable_Check doesn't raise any errors. Raise called attribute isn't callable.
146+ const auto errorString = std::string (" vtkMRMLLayerDMPythonUtil::" ) + __func__ + " : Attribute is not callable : '" + fName + " ' of object : " + GetObjectStr (object);
147+ PyErr_SetString (PyExc_TypeError, errorString.c_str ());
139148 return nullptr ;
140149 }
141150
@@ -144,24 +153,26 @@ PyObject* vtkMRMLLayerDMPythonUtil::CallPythonMethod(PyObject* object, const vtk
144153
145154PyObject* vtkMRMLLayerDMPythonUtil::CallPythonObject (PyObject* object, const vtkSmartPyObject& pyArgs)
146155{
147- if (!Py_IsInitialized () || !object || ! PyCallable_Check (object) )
156+ if (!IsValidPythonContext () || !object)
148157 {
149158 return nullptr ;
150159 }
151160
152161 vtkPythonScopeGilEnsurer gilEnsurer;
153- PyObject* result = PyObject_CallObject (object, pyArgs);
154- if (!result)
162+ if (!PyCallable_Check (object))
155163 {
156- PyErr_Print ();
164+ // PyCallable_Check doesn't raise any errors. Raise called attribute isn't callable.
165+ const auto errorString = std::string (" vtkMRMLLayerDMPythonUtil::" ) + __func__ + " : Object is not callable : " + GetObjectStr (object);
166+ PyErr_SetString (PyExc_TypeError, errorString.c_str ());
157167 return nullptr ;
158168 }
159- return result;
169+
170+ return PyObject_CallObject (object, pyArgs);
160171}
161172
162173void vtkMRMLLayerDMPythonUtil::SetPythonObject (PyObject** destObject, PyObject* object)
163174{
164- if (!Py_IsInitialized ())
175+ if (!IsValidPythonContext ())
165176 {
166177 return ;
167178 }
@@ -188,3 +199,99 @@ void vtkMRMLLayerDMPythonUtil::DeletePythonObject(PyObject** destObject)
188199 Py_XDECREF (*destObject);
189200 *destObject = nullptr ;
190201}
202+
203+ std::string vtkMRMLLayerDMPythonUtil::GetObjectStr (PyObject* object)
204+ {
205+ if (!Py_IsInitialized ())
206+ {
207+ return {};
208+ }
209+
210+ if (!object)
211+ {
212+ return " None" ;
213+ }
214+
215+ // Save current errors to avoid changing the current python error stack if any
216+ PyObject *type, *value, *traceback;
217+ PyErr_Fetch (&type, &value, &traceback);
218+
219+ std::string objectString{ " INVALID_OBJECT_STR" };
220+ if (auto strObj = PyObject_Str (object))
221+ {
222+ objectString = PyUnicode_AsUTF8 (strObj);
223+ Py_DECREF (strObj);
224+ }
225+
226+ // Restore the python error stack
227+ PyErr_Restore (type, value, traceback);
228+ return objectString;
229+ }
230+
231+ bool vtkMRMLLayerDMPythonUtil::IsValidPythonContext ()
232+ {
233+ if (!Py_IsInitialized ())
234+ {
235+ return false ;
236+ }
237+
238+ vtkPythonScopeGilEnsurer gilEnsurer;
239+ return !PyErr_Occurred ();
240+ }
241+
242+ std::string vtkMRMLLayerDMPythonUtil::FormatExceptionTraceback ()
243+ {
244+ // Don't use IsValidPythonContext here as it checks if no error has occurred
245+ if (!Py_IsInitialized ())
246+ {
247+ return {};
248+ }
249+
250+ vtkPythonScopeGilEnsurer gilEnsurer;
251+ if (!PyErr_Occurred ())
252+ {
253+ // No error has occurred
254+ return {};
255+ }
256+
257+ // Get error string from the traceback module
258+ PyObject *type, *value, *traceback;
259+ PyErr_Fetch (&type, &value, &traceback);
260+ PyErr_NormalizeException (&type, &value, &traceback);
261+
262+ PyObject* tracebackModule = PyImport_ImportModule (" traceback" );
263+ PyObject* formatExceptionFunc = PyObject_GetAttrString (tracebackModule, " format_exception" );
264+ PyObject* args = PyTuple_Pack (3 , type, value, traceback);
265+ PyObject* formattedList = PyObject_CallObject (formatExceptionFunc, args);
266+ PyObject* emptyString = PyUnicode_FromString (" " );
267+ PyObject* formatted = PyUnicode_Join (emptyString, formattedList);
268+ std::string exceptionTraceback = PyUnicode_AsUTF8 (formatted);
269+
270+ // Cleanup
271+ PyErr_Restore (type, value, traceback);
272+ Py_XDECREF (formatted);
273+ Py_XDECREF (emptyString);
274+ Py_XDECREF (formattedList);
275+ Py_XDECREF (args);
276+ Py_XDECREF (formatExceptionFunc);
277+ Py_XDECREF (tracebackModule);
278+ return exceptionTraceback;
279+ }
280+
281+ void vtkMRMLLayerDMPythonUtil::PrintErrorTraceback (const vtkObject* object, const std::string& errorMsg)
282+ {
283+ // If the traceback is not empty, print the traceback using vtkErrorMacro
284+ const auto traceback = FormatExceptionTraceback ();
285+ if (traceback.empty ())
286+ {
287+ return ;
288+ }
289+
290+ std::string errorString{ errorMsg };
291+ if (!errorString.empty ())
292+ {
293+ errorString += " \n " ;
294+ }
295+ errorString += traceback;
296+ vtkErrorWithObjectMacro (object, " " << traceback.c_str ());
297+ }
0 commit comments