Skip to content
Permalink
Browse files

Merge pull request #4764 from nyalldawson/processing_exception

Throw c++ exception when a Python exception occurs while running a algorithm
  • Loading branch information
nyalldawson committed Jun 23, 2017
2 parents 2906d1f + c3e24b7 commit 19dd0976d7810152ebb456e28b4c25d8911a4028
Showing with 399 additions and 213 deletions.
  1. +1 −0 cmake_templates/Doxyfile.in
  2. +91 −0 python/core/core.sip
  3. +4 −2 python/core/processing/qgsprocessingalgorithm.sip
  4. +2 −0 python/core/processing/qgsprocessingcontext.sip
  5. +13 −1 python/core/qgsexception.sip
  6. +1 −1 python/core/qgsfeaturerequest.sip
  7. +13 −28 python/plugins/processing/core/GeoAlgorithm.py
  8. +7 −7 python/plugins/processing/core/Processing.py
  9. +10 −7 python/plugins/processing/gui/AlgorithmDialog.py
  10. +3 −3 python/plugins/processing/gui/AlgorithmExecutor.py
  11. +1 −1 python/plugins/processing/gui/BatchAlgorithmDialog.py
  12. +1 −1 python/plugins/processing/gui/ProcessingToolbox.py
  13. +1 −1 python/plugins/processing/gui/menus.py
  14. +23 −18 python/plugins/processing/tests/AlgorithmsTestBase.py
  15. +32 −0 python/plugins/processing/tests/QgisAlgorithmsTest.py
  16. +10 −2 python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
  17. +12 −8 python/plugins/processing/tools/dataobjects.py
  18. +3 −2 scripts/sipify.pl
  19. +1 −1 src/app/composer/qgscomposermapwidget.cpp
  20. +1 −1 src/app/gps/qgsgpsmarker.cpp
  21. +1 −1 src/app/qgsdecorationlayoutextent.cpp
  22. +1 −1 src/app/qgsdecorationnortharrow.cpp
  23. +1 −1 src/app/qgsmapcanvasdockwidget.cpp
  24. +1 −1 src/app/qgsmaptooladdfeature.cpp
  25. +1 −1 src/app/qgsmaptoolannotation.cpp
  26. +1 −1 src/app/qgsmaptoolfeatureaction.cpp
  27. +1 −1 src/app/qgsmaptoolselectutils.cpp
  28. +1 −1 src/app/qgsmaptoolshowhidelabels.cpp
  29. +1 −1 src/app/qgsmeasuretool.cpp
  30. +0 −1 src/core/CMakeLists.txt
  31. +1 −1 src/core/composer/qgscomposerattributetablev2.cpp
  32. +1 −1 src/core/composer/qgscomposermapgrid.cpp
  33. +1 −1 src/core/composer/qgscomposermapoverview.cpp
  34. +1 −1 src/core/expression/qgsexpressionfunction.cpp
  35. +20 −2 src/core/processing/qgsprocessingalgorithm.cpp
  36. +4 −2 src/core/processing/qgsprocessingalgorithm.h
  37. +3 −2 src/core/processing/qgsprocessingalgrunnertask.cpp
  38. +16 −1 src/core/processing/qgsprocessingcontext.h
  39. +9 −7 src/core/processing/qgsprocessingmodelalgorithm.cpp
  40. +1 −10 src/core/processing/qgsprocessingutils.cpp
  41. +1 −1 src/core/providers/memory/qgsmemoryfeatureiterator.cpp
  42. +5 −0 src/core/qgis_sip.h
  43. +1 −1 src/core/qgscachedfeatureiterator.cpp
  44. +1 −1 src/core/qgscoordinatetransform.cpp
  45. +1 −1 src/core/qgscoordinateutils.cpp
  46. +0 −32 src/core/qgscsexception.h
  47. +1 −1 src/core/qgsdistancearea.cpp
  48. +42 −7 src/core/qgsexception.h
  49. +1 −1 src/core/qgsfeatureiterator.cpp
  50. +2 −2 src/core/qgsfeaturerequest.h
  51. +1 −1 src/core/qgsjsonutils.cpp
  52. +1 −1 src/core/qgsmaprendererjob.cpp
  53. +1 −1 src/core/qgsmapsettings.cpp
  54. +1 −1 src/core/qgspallabeling.cpp
  55. +1 −1 src/core/qgstracer.cpp
  56. +1 −1 src/core/qgsvectorfilewriter.cpp
  57. +1 −1 src/core/qgsvectorlayer.cpp
  58. +1 −1 src/core/qgsvectorlayerexporter.cpp
  59. +5 −1 src/core/qgsvectorlayerfeatureiterator.cpp
  60. +1 −1 src/core/qgsvectorlayerrenderer.cpp
  61. +1 −1 src/core/raster/qgsrasterlayerrenderer.cpp
  62. +1 −1 src/core/raster/qgsrasterprojector.cpp
  63. +1 −1 src/gui/qgsextentgroupbox.cpp
  64. +1 −1 src/gui/qgsmapcanvas.cpp
  65. +1 −1 src/gui/qgsmapcanvasannotationitem.cpp
  66. +1 −1 src/gui/qgsmaptoolcapture.cpp
  67. +1 −1 src/gui/qgsmaptoolidentify.cpp
  68. +1 −1 src/gui/qgssourceselectdialog.cpp
  69. +1 −1 src/plugins/grass/qgsgrassnewmapset.cpp
  70. +1 −1 src/plugins/grass/qgsgrassregion.cpp
  71. +1 −1 src/providers/arcgisrest/qgsafsfeatureiterator.cpp
  72. +1 −1 src/providers/db2/qgsdb2featureiterator.cpp
  73. +1 −1 src/providers/delimitedtext/qgsdelimitedtextfeatureiterator.cpp
  74. +1 −1 src/providers/gpx/qgsgpxfeatureiterator.cpp
  75. +1 −1 src/providers/mssql/qgsmssqlfeatureiterator.cpp
  76. +1 −1 src/providers/ogr/qgsogrfeatureiterator.cpp
  77. +1 −1 src/providers/oracle/qgsoraclefeatureiterator.cpp
  78. +1 −1 src/providers/postgres/qgspostgresfeatureiterator.cpp
  79. +1 −1 src/providers/spatialite/qgsspatialitefeatureiterator.cpp
  80. +1 −1 src/providers/virtual/qgsvirtuallayerfeatureiterator.cpp
  81. +1 −1 src/providers/wcs/qgswcsprovider.cpp
  82. +1 −1 src/providers/wfs/qgswfsfeatureiterator.cpp
  83. +1 −1 src/providers/wms/qgswmscapabilities.cpp
  84. +1 −1 src/providers/wms/qgswmsprovider.cpp
  85. +1 −1 src/server/qgsconfigparserutils.cpp
  86. +1 −1 src/server/qgsserverprojectparser.cpp
  87. +1 −1 src/server/services/wcs/qgswcsdescribecoverage.cpp
  88. +1 −1 src/server/services/wcs/qgswcsgetcapabilities.cpp
  89. +1 −1 src/server/services/wcs/qgswcsutils.cpp
  90. +1 −1 src/server/services/wfs/qgswfsdescribefeaturetype.cpp
  91. +1 −1 src/server/services/wfs/qgswfsgetcapabilities.cpp
  92. +1 −1 src/server/services/wms/qgswmsgetcapabilities.cpp
  93. +1 −1 src/server/services/wms/qgswmsgetcontext.cpp
  94. +1 −1 src/server/services/wms/qgswmsrenderer.cpp
@@ -2073,6 +2073,7 @@ EXPAND_AS_DEFINED = "SIP_ABSTRACT" \
"SIP_TRANSFER" \
"SIP_TRANSFERBACK" \
"SIP_TRANSFERTHIS" \
"SIP_VIRTUALERRORHANDLER" \
"SIP_WHEN_FEATURE"

# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
@@ -1,6 +1,93 @@
%Module(name=qgis._core,
keyword_arguments="Optional")

%ModuleCode

#include "qgsexception.h"

QString getTraceback()
{
#define TRACEBACK_FETCH_ERROR(what) {errMsg = what; goto done;}

// acquire global interpreter lock to ensure we are in a consistent state
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();

QString errMsg;
QString result;

PyObject *modStringIO = nullptr;
PyObject *modTB = nullptr;
PyObject *obStringIO = nullptr;
PyObject *obResult = nullptr;

PyObject *type, *value, *traceback;

PyErr_Fetch( &type, &value, &traceback );
PyErr_NormalizeException( &type, &value, &traceback );

const char *iomod = "io";

modStringIO = PyImport_ImportModule( iomod );
if ( !modStringIO )
TRACEBACK_FETCH_ERROR( QString( "can't import %1" ).arg( iomod ) );

obStringIO = PyObject_CallMethod( modStringIO, ( char * ) "StringIO", nullptr );

/* Construct a cStringIO object */
if ( !obStringIO )
TRACEBACK_FETCH_ERROR( "cStringIO.StringIO() failed" );

modTB = PyImport_ImportModule( "traceback" );
if ( !modTB )
TRACEBACK_FETCH_ERROR( "can't import traceback" );

obResult = PyObject_CallMethod( modTB, ( char * ) "print_exception",
( char * ) "OOOOO",
type, value ? value : Py_None,
traceback ? traceback : Py_None,
Py_None,
obStringIO );

if ( !obResult )
TRACEBACK_FETCH_ERROR( "traceback.print_exception() failed" );

Py_DECREF( obResult );

obResult = PyObject_CallMethod( obStringIO, ( char * ) "getvalue", nullptr );
if ( !obResult )
TRACEBACK_FETCH_ERROR( "getvalue() failed." );

/* And it should be a string all ready to go - duplicate it. */
if ( !PyUnicode_Check( obResult ) )
TRACEBACK_FETCH_ERROR( "getvalue() did not return a string" );

result = QString::fromUtf8( PyUnicode_AsUTF8( obResult ) );

done:

// All finished - first see if we encountered an error
if ( result.isEmpty() && !errMsg.isEmpty() )
{
result = errMsg;
}

Py_XDECREF( modStringIO );
Py_XDECREF( modTB );
Py_XDECREF( obStringIO );
Py_XDECREF( obResult );
Py_XDECREF( value );
Py_XDECREF( traceback );
Py_XDECREF( type );

// we are done calling python API, release global interpreter lock
PyGILState_Release( gstate );

return result;
}

%End

%Import QtXml/QtXmlmod.sip
%Import QtNetwork/QtNetworkmod.sip
%Import QtSql/QtSqlmod.sip
@@ -413,3 +500,7 @@
%Include expression/qgsexpressionnodeimpl.sip
%Include expression/qgsexpressionnode.sip
%Include expression/qgsexpressionfunction.sip

%VirtualErrorHandler processing_exception_handler
throw QgsProcessingException( getTraceback() );
%End
@@ -215,14 +215,16 @@ class QgsProcessingAlgorithm
%End

QVariantMap run( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const;
QgsProcessingContext &context, QgsProcessingFeedback *feedback, bool *ok /Out/ = 0 ) const;
%Docstring
Executes the algorithm using the specified ``parameters``.

The ``context`` argument specifies the context in which the algorithm is being run.

Algorithm progress should be reported using the supplied ``feedback`` object.

If specified, ``ok`` will be set to true if algorithm was successfully run.

:return: A map of algorithm outputs. These may be output layer references, or calculated
values such as statistical calculations.
:rtype: QVariantMap
@@ -292,7 +294,7 @@ class QgsProcessingAlgorithm
%End

virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const = 0;
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const = 0 /VirtualErrorHandler=processing_exception_handler/;
%Docstring
Runs the algorithm using the specified ``parameters``. Algorithms should implement
their custom processing logic here.
@@ -137,6 +137,8 @@ Destination project
void setInvalidGeometryCheck( const QgsFeatureRequest::InvalidGeometryCheck &check );
%Docstring
Sets the behavior used for checking invalid geometries in input layers.
Settings this to anything but QgsFeatureRequest.GeometryNoCheck will also
reset the invalidGeometryCallback() to a default implementation.
.. seealso:: invalidGeometryCheck()
%End

@@ -1,7 +1,7 @@
%Exception QgsCsException(SIP_Exception) /PyName=QgsCsException/
{
%TypeHeaderCode
#include <qgscsexception.h>
#include <qgsexception.h>
%End
%RaiseCode
SIP_BLOCK_THREADS
@@ -10,6 +10,18 @@
%End
};

%Exception QgsProcessingException(SIP_Exception) /PyName=QgsProcessingException/
{
%TypeHeaderCode
#include <qgsexception.h>
%End
%RaiseCode
SIP_BLOCK_THREADS
PyErr_SetString(sipException_QgsProcessingException, sipExceptionRef.what().toUtf8().constData() );
SIP_UNBLOCK_THREADS
%End
};

%Exception QgsException(SIP_Exception) /PyName=QgsException/
{
%TypeHeaderCode
@@ -364,7 +364,7 @@ Get feature IDs that should be fetched.
QgsFeatureRequest &setInvalidGeometryCallback( SIP_PYCALLABLE / AllowNone / );
%Docstring
Sets a callback function to use when encountering an invalid geometry and
invalidGeometryCheck() is set to GeometryAbortOnInvalid. This function will be
invalidGeometryCheck() is set to GeometryAbortOnInvalid or GeometrySkipInvalid. This function will be
called using the feature with invalid geometry as a parameter.
.. versionadded:: 3.0
.. seealso:: invalidGeometryCallback()
@@ -113,7 +113,7 @@ def execute(self, parameters, context=None, feedback=None, model=None):
if feedback is None:
feedback = QgsProcessingFeedback()
if context is None:
context = dataobjects.createContext()
context = dataobjects.createContext(feedback)

self.model = model
try:
@@ -327,35 +327,20 @@ def executeAlgorithm(alg, parameters, context=None, feedback=None, model=None):
if feedback is None:
feedback = QgsProcessingFeedback()
if context is None:
context = dataobjects.createContext()
context = dataobjects.createContext(feedback)

#self.model = model
try:
#self.setOutputCRS()
#self.resolveOutputs()
#self.evaluateParameterValues()
#self.runPreExecutionScript(feedback)
result = alg.run(parameters, context, feedback)
#self.processAlgorithm(parameters, context, feedback)
feedback.setProgress(100)
return result
#self.convertUnsupportedFormats(context, feedback)
#self.runPostExecutionScript(feedback)
except GeoAlgorithmExecutionException as gaee:
lines = [self.tr('Error while executing algorithm')]
lines = []
lines.append(traceback.format_exc())
feedback.reportError(gaee.msg)
QgsMessageLog.logMessage(gaee.msg, self.tr('Processing'), QgsMessageLog.CRITICAL)
raise GeoAlgorithmExecutionException(gaee.msg, lines, gaee)
#except Exception as e:
# If something goes wrong and is not caught in the
# algorithm, we catch it here and wrap it
#lines = [self.tr('Uncaught error while executing algorithm')]
# lines = []
# lines.append(traceback.format_exc())
#QgsMessageLog.logMessage('\n'.join(lines), self.tr('Processing'), QgsMessageLog.CRITICAL)
#raise GeoAlgorithmExecutionException(str(e) + self.tr('\nSee log for more details'), lines, e)

#self.setOutputCRS()
#self.resolveOutputs()
#self.evaluateParameterValues()
#self.runPreExecutionScript(feedback)
result, ok = alg.run(parameters, context, feedback)
#self.processAlgorithm(parameters, context, feedback)
feedback.setProgress(100)
return result, ok
#self.convertUnsupportedFormats(context, feedback)
#self.runPostExecutionScript(feedback)

def helpUrl(self):
return QgsHelp.helpUrl("processing_algs/{}/{}".format(
@@ -193,11 +193,17 @@ def runAlgorithm(algOrName, onFinish, *args, **kwargs):
return
i = i + 1

feedback = None
if kwargs is not None and "feedback" in list(kwargs.keys()):
feedback = kwargs["feedback"]
elif iface is not None:
feedback = MessageBarProgress(alg.displayName())

context = None
if kwargs is not None and 'context' in list(kwargs.keys()):
context = kwargs["context"]
else:
context = dataobjects.createContext()
context = dataobjects.createContext(feedback)

ok, msg = alg.checkParameterValues(parameters, context)
if not ok:
@@ -226,12 +232,6 @@ def runAlgorithm(algOrName, onFinish, *args, **kwargs):
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
overrideCursor = True

feedback = None
if kwargs is not None and "feedback" in list(kwargs.keys()):
feedback = kwargs["feedback"]
elif iface is not None:
feedback = MessageBarProgress(alg.displayName())

ret, results = execute(alg, parameters, context, feedback)
if ret:
if onFinish is not None:
@@ -164,12 +164,11 @@ def checkExtentCRS(self):
def accept(self):
super(AlgorithmDialog, self)._saveGeometry()

context = dataobjects.createContext()
feedback = self.createFeedback()
context = dataobjects.createContext(feedback)

checkCRS = ProcessingConfig.getSetting(ProcessingConfig.WARN_UNMATCHING_CRS)
try:
feedback = self.createFeedback()

parameters = self.getParamValues()

if checkCRS and not self.alg.validateInputCrs(parameters, context):
@@ -244,10 +243,14 @@ def accept(self):
if command:
ProcessingLog.addToLog(command)
self.buttonCancel.setEnabled(self.alg.flags() & QgsProcessingAlgorithm.FlagCanCancel)
result = executeAlgorithm(self.alg, parameters, context, feedback)
feedback.pushInfo(self.tr('Execution completed in {0:0.2f} seconds'.format(time.time() - start_time)))
feedback.pushInfo(self.tr('Results:'))
feedback.pushCommandInfo(pformat(result))
result, ok = executeAlgorithm(self.alg, parameters, context, feedback)
if ok:
feedback.pushInfo(self.tr('Execution completed in {0:0.2f} seconds'.format(time.time() - start_time)))
feedback.pushInfo(self.tr('Results:'))
feedback.pushCommandInfo(pformat(result))
else:
feedback.reportError(
self.tr('Execution failed after {0:0.2f} seconds'.format(time.time() - start_time)))
feedback.pushInfo('')

self.buttonCancel.setEnabled(False)
@@ -56,11 +56,11 @@ def execute(alg, parameters, context=None, feedback=None):
if feedback is None:
feedback = QgsProcessingFeedback()
if context is None:
context = dataobjects.createContext()
context = dataobjects.createContext(feedback)

try:
results = alg.run(parameters, context, feedback)
return True, results
results, ok = alg.run(parameters, context, feedback)
return ok, results
except GeoAlgorithmExecutionException as e:
QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', QgsMessageLog.CRITICAL)
if feedback is not None:
@@ -78,8 +78,8 @@ def accept(self):
alg_parameters = []
load = []

context = dataobjects.createContext()
feedback = self.createFeedback()
context = dataobjects.createContext(feedback)

for row in range(self.mainWidget.tblParameters.rowCount()):
col = 0
@@ -253,7 +253,6 @@ def executeAlgorithmAsBatchProcess(self):
def executeAlgorithm(self):
item = self.algorithmTree.currentItem()
if isinstance(item, TreeAlgorithmItem):
context = dataobjects.createContext()
alg = QgsApplication.processingRegistry().algorithmById(item.alg.id())
ok, message = alg.canExecute()
if not ok:
@@ -287,6 +286,7 @@ def executeAlgorithm(self):
self.addRecentAlgorithms(True)
else:
feedback = MessageBarProgress()
context = dataobjects.createContext(feedback)
parameters = {}
ret, results = execute(alg, parameters, context, feedback)
handleAlgorithmResults(alg, parameters, context, feedback)
@@ -203,7 +203,6 @@ def _executeAlgorithm(alg):
dlg.exec_()
return

context = dataobjects.createContext()
if (alg.countVisibleParameters()) > 0:
dlg = alg.createCustomParametersWidget(None)
if not dlg:
@@ -220,6 +219,7 @@ def _executeAlgorithm(alg):
canvas.setMapTool(prevMapTool)
else:
feedback = MessageBarProgress()
context = dataobjects.createContext(feedback)
parameters = {}
ret, results = execute(alg, parameters, context, feedback)
handleAlgorithmResults(alg, context, feedback)

0 comments on commit 19dd097

Please sign in to comment.
You can’t perform that action at this time.