@@ -44,8 +44,10 @@ void QgsPythonUtils::initPython(QgisInterface* interface)
4444 mMainModule = PyImport_AddModule (" __main__" ); // borrowed reference
4545 mMainDict = PyModule_GetDict (mMainModule ); // borrowed reference
4646
47- // import sys module
48- runString (" import sys" );
47+ runString (" import sys" ); // import sys module (for display / exception hooks)
48+ runString (" import traceback" ); // for formatting stack traces
49+ runString (" import __main__" ); // to access explicitly global variables
50+
4951
5052 // expect that bindings are installed locally, so add the path to modules
5153 // also add path to plugins
@@ -75,23 +77,22 @@ void QgsPythonUtils::initPython(QgisInterface* interface)
7577 return ;
7678 }
7779
78- runString (" import __main__" );
79- runString (" import traceback" ); // for formatting stack traces
80-
8180 // hook that will show information and traceback in message box
8281 runString (
8382 " def qgis_except_hook_msg(type, value, tb, msg):\n "
8483 " lst = traceback.format_exception(type, value, tb)\n "
85- " if msg == None: msg = 'An error has occured while executing Python code:'\n "
86- " str = '<font color=\" red\" >'+msg+'</font><br><br>'\n "
84+ " if msg == None: msg = '" + QObject::tr ( " An error has occured while executing Python code:" ) + " '\n "
85+ " txt = '<font color=\" red\" >'+msg+'</font><br><br>'\n "
8786 " for s in lst:\n "
88- " str += s\n "
89- " str = str.replace('\\ n', '<br>')\n "
90- " str = str.replace(' ', ' ')\n " // preserve whitespaces for nicer output
87+ " txt += s\n "
88+ " txt += '<br>" + QObject::tr (" Python version:" ) + " <br>' + sys.version + '<br><br>'\n "
89+ " txt += '" +QObject::tr (" Python path:" ) + " ' + str(sys.path)\n "
90+ " txt = txt.replace('\\ n', '<br>')\n "
91+ " txt = txt.replace(' ', ' ')\n " // preserve whitespaces for nicer output
9192 " \n "
9293 " msg = QgsMessageOutput.createMessageOutput()\n "
93- " msg.setTitle('Error ')\n "
94- " msg.setMessage(str , QgsMessageOutput.MessageHtml)\n "
94+ " msg.setTitle('" + QObject::tr ( " Python error " ) + " ')\n "
95+ " msg.setMessage(txt , QgsMessageOutput.MessageHtml)\n "
9596 " msg.showMessage()\n " );
9697 runString (
9798 " def qgis_except_hook(type, value, tb):\n "
@@ -168,22 +169,21 @@ bool QgsPythonUtils::runString(const QString& command, QString msgOnError)
168169 if (res)
169170 return true ;
170171
171- // an error occured
172- // fetch error details and show it
173- QString err_type, err_value;
174- getError (err_type, err_value);
175-
176172 if (msgOnError.isEmpty ())
177173 {
178174 // use some default message if custom hasn't been specified
179175 msgOnError = QObject::tr (" An error occured during execution of following code:" ) + " \n <tt>" + command + " </tt>" ;
180176 }
181- msgOnError.replace (" \n " , " <br>" );
182- QString str = msgOnError + " <br><br>" + QObject::tr (" Error details:" ) + " <br>"
183- + QObject::tr (" Type:" ) + " <b>" + err_type + " </b><br>"
184- + QObject::tr (" Value:" ) + " <b>" + err_value + " </b>" ;
185177
186- // TODO: show sys.path, local variables, traceback
178+ QString traceback = getTraceback ();
179+ QString path, version;
180+ evalString (" str(sys.path)" , path);
181+ evalString (" sys.version" , version);
182+
183+ QString str = " <font color=\" red\" >" + msgOnError + " </font><br><br>" + traceback + " <br>" +
184+ QObject::tr (" Python version:" ) + " <br>" + version + " <br><br>" +
185+ QObject::tr (" Python path:" ) + " <br>" + path;
186+ str.replace (" \n " ," <br>" ).replace (" " , " " );
187187
188188 QgsMessageOutput* msg = QgsMessageOutput::createMessageOutput ();
189189 msg->setTitle (QObject::tr (" Python error" ));
@@ -194,6 +194,78 @@ bool QgsPythonUtils::runString(const QString& command, QString msgOnError)
194194}
195195
196196
197+ QString QgsPythonUtils::getTraceback ()
198+ {
199+ #define TRACEBACK_FETCH_ERROR (what ) {errMsg = what; goto done;}
200+
201+
202+ QString errMsg;
203+ QString result;
204+
205+ PyObject *modStringIO = NULL ;
206+ PyObject *modTB = NULL ;
207+ PyObject *obStringIO = NULL ;
208+ PyObject *obResult = NULL ;
209+
210+ PyObject *type, *value, *traceback;
211+
212+ PyErr_Fetch (&type, &value, &traceback);
213+ PyErr_NormalizeException (&type, &value, &traceback);
214+
215+ modStringIO = PyImport_ImportModule (" cStringIO" );
216+ if (modStringIO==NULL )
217+ TRACEBACK_FETCH_ERROR (" can't import cStringIO" );
218+
219+ obStringIO = PyObject_CallMethod (modStringIO, (char *) " StringIO" , NULL );
220+
221+ /* Construct a cStringIO object */
222+ if (obStringIO==NULL )
223+ TRACEBACK_FETCH_ERROR (" cStringIO.StringIO() failed" );
224+
225+ modTB = PyImport_ImportModule (" traceback" );
226+ if (modTB==NULL )
227+ TRACEBACK_FETCH_ERROR (" can't import traceback" );
228+
229+ obResult = PyObject_CallMethod (modTB, (char *) " print_exception" ,
230+ (char *) " OOOOO" ,
231+ type, value ? value : Py_None,
232+ traceback ? traceback : Py_None,
233+ Py_None,
234+ obStringIO);
235+
236+ if (obResult==NULL )
237+ TRACEBACK_FETCH_ERROR (" traceback.print_exception() failed" );
238+ Py_DECREF (obResult);
239+
240+ obResult = PyObject_CallMethod (obStringIO, (char *) " getvalue" , NULL );
241+ if (obResult==NULL )
242+ TRACEBACK_FETCH_ERROR (" getvalue() failed." );
243+
244+ /* And it should be a string all ready to go - duplicate it. */
245+ if (!PyString_Check (obResult))
246+ TRACEBACK_FETCH_ERROR (" getvalue() did not return a string" );
247+
248+ result = PyString_AsString (obResult);
249+
250+ done:
251+
252+ // All finished - first see if we encountered an error
253+ if (result.isEmpty () && !errMsg.isEmpty ())
254+ {
255+ result = errMsg;
256+ }
257+
258+ Py_XDECREF (modStringIO);
259+ Py_XDECREF (modTB);
260+ Py_XDECREF (obStringIO);
261+ Py_XDECREF (obResult);
262+ Py_XDECREF (value);
263+ Py_XDECREF (traceback);
264+ Py_XDECREF (type);
265+
266+ return result;
267+ }
268+
197269QString QgsPythonUtils::getTypeAsString (PyObject* obj)
198270{
199271 if (obj == NULL )
0 commit comments