Skip to content

Commit 525bf5f

Browse files
authored
Merge pull request #9215 from elpaso/release-3_4-backports
Release 3 4 backports
2 parents 013ff57 + 5adb34d commit 525bf5f

15 files changed

+212
-29
lines changed

python/core/auto_generated/processing/qgsprocessingalgorithm.sip.in

+5-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ a pre-initialized copy of the algorithm.
6363

6464

6565

66-
QgsProcessingAlgorithm *create( const QVariantMap &configuration = QVariantMap() ) const /TransferBack/;
66+
QgsProcessingAlgorithm *create( const QVariantMap &configuration = QVariantMap() ) const throw( QgsProcessingException ) /TransferBack/;
6767
%Docstring
6868
Creates a copy of the algorithm, ready for execution.
6969

@@ -76,6 +76,9 @@ and outputs according to this configuration. This is generally used only for
7676
algorithms in a model, allowing them to adjust their behavior at run time
7777
according to some user configuration.
7878

79+
Raises a QgsProcessingException if a new algorithm instance could not be created,
80+
e.g. if there is an issue with the subclass' createInstance() method.
81+
7982
.. seealso:: :py:func:`initAlgorithm`
8083
%End
8184

@@ -401,7 +404,7 @@ Associates this algorithm with its provider. No transfer of ownership is involve
401404

402405
protected:
403406

404-
virtual QgsProcessingAlgorithm *createInstance() const = 0 /Factory/;
407+
virtual QgsProcessingAlgorithm *createInstance() const = 0 /Factory,VirtualErrorHandler=processing_exception_handler/;
405408
%Docstring
406409
Creates a new instance of the algorithm class.
407410

python/plugins/processing/gui/AlgorithmDialog.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,11 @@ def on_complete(ok, results):
263263
self.showLog()
264264

265265
task = QgsProcessingAlgRunnerTask(self.algorithm(), parameters, self.context, self.feedback)
266-
task.executed.connect(on_complete)
267-
self.setCurrentTask(task)
266+
if task.isCanceled():
267+
on_complete(False, {})
268+
else:
269+
task.executed.connect(on_complete)
270+
self.setCurrentTask(task)
268271
else:
269272
self.proxy_progress = QgsProxyProgressTask(QCoreApplication.translate("AlgorithmDialog", "Executing “{}”").format(self.algorithm().displayName()))
270273
QgsApplication.taskManager().addTask(self.proxy_progress)

src/analysis/vector/geometry_checker/qgsgeometrygapcheck.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ void QgsGeometryGapCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &
4545
{
4646
geomList.append( layerFeature.geometry().constGet()->clone() );
4747

48-
if ( feedback->isCanceled() )
48+
if ( feedback && feedback->isCanceled() )
4949
{
5050
qDeleteAll( geomList );
5151
geomList.clear();

src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ void QgsGeometryMissingVertexCheck::collectErrors( const QMap<QString, QgsFeatur
4343

4444
for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
4545
{
46-
if ( feedback->isCanceled() )
46+
if ( feedback && feedback->isCanceled() )
4747
{
4848
break;
4949
}
@@ -152,7 +152,7 @@ void QgsGeometryMissingVertexCheck::processPolygon( const QgsCurvePolygon *polyg
152152

153153
if ( featurePool->getFeature( fid, compareFeature, feedback ) )
154154
{
155-
if ( feedback->isCanceled() )
155+
if ( feedback && feedback->isCanceled() )
156156
break;
157157

158158
QgsVertexIterator vertexIterator = compareFeature.geometry().vertices();

src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ void QgsGeometryOverlapCheck::collectErrors( const QMap<QString, QgsFeaturePool
3535
QList<QString> layerIds = featureIds.keys();
3636
for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureA : layerFeaturesA )
3737
{
38-
if ( feedback->isCanceled() )
38+
if ( feedback && feedback->isCanceled() )
3939
break;
4040

4141
// Ensure each pair of layers only gets compared once: remove the current layer from the layerIds, but add it to the layerList for layerFeaturesB
@@ -52,7 +52,7 @@ void QgsGeometryOverlapCheck::collectErrors( const QMap<QString, QgsFeaturePool
5252
const QgsGeometryCheckerUtils::LayerFeatures layerFeaturesB( featurePools, QList<QString>() << layerFeatureA.layer()->id() << layerIds, bboxA, compatibleGeometryTypes(), mContext );
5353
for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureB : layerFeaturesB )
5454
{
55-
if ( feedback->isCanceled() )
55+
if ( feedback && feedback->isCanceled() )
5656
break;
5757

5858
// > : only report overlaps within same layer once

src/app/qgsvectorlayerloadstyledialog.cpp

+21-9
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,21 @@ QgsVectorLayerLoadStyleDialog::QgsVectorLayerLoadStyleDialog( QgsVectorLayer *la
5656
connect( mStyleTypeComboBox, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, [ = ]( int )
5757
{
5858
QgsVectorLayerProperties::StyleType type = currentStyleType();
59-
mFromFileWidget->setVisible( type != QgsVectorLayerProperties::DB );
60-
mFromDbWidget->setVisible( type == QgsVectorLayerProperties::DB );
61-
mDeleteButton->setVisible( type == QgsVectorLayerProperties::DB && mLayer->dataProvider()->isDeleteStyleFromDatabaseSupported() );
62-
mStyleCategoriesListView->setEnabled( currentStyleType() != QgsVectorLayerProperties::SLD );
59+
mFromFileWidget->setVisible( type != QgsVectorLayerProperties::StyleType::DB );
60+
mFromDbWidget->setVisible( type == QgsVectorLayerProperties::StyleType::DB );
61+
mDeleteButton->setVisible( type == QgsVectorLayerProperties::StyleType::DB && mLayer->dataProvider()->isDeleteStyleFromDatabaseSupported() );
62+
mStyleCategoriesListView->setEnabled( currentStyleType() != QgsVectorLayerProperties::StyleType::SLD );
6363
updateLoadButtonState();
6464
} );
6565
mStyleTypeComboBox->addItem( tr( "from file" ), QgsVectorLayerProperties::QML ); // QML is used as entry, but works for SLD too, see currentStyleType()
6666
if ( mLayer->dataProvider()->isSaveAndLoadStyleToDatabaseSupported() )
67-
mStyleTypeComboBox->addItem( tr( "from database (%1)" ).arg( providerName ), QgsVectorLayerProperties::DB );
67+
{
68+
mStyleTypeComboBox->addItem( tr( "from database (%1)" ).arg( providerName ), QgsVectorLayerProperties::StyleType::DB );
69+
if ( settings.value( QStringLiteral( "style/lastLoadStyleTypeSelection" ) ) == QgsVectorLayerProperties::StyleType::DB )
70+
{
71+
mStyleTypeComboBox->setCurrentIndex( mStyleTypeComboBox->findData( QgsVectorLayerProperties::StyleType::DB ) );
72+
}
73+
}
6874

6975
// fill style categories
7076
mModel = new QgsMapLayerStyleCategoriesModel( this );
@@ -105,6 +111,11 @@ QgsVectorLayerLoadStyleDialog::QgsVectorLayerLoadStyleDialog( QgsVectorLayer *la
105111
connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsVectorLayerLoadStyleDialog::showHelp );
106112
connect( mLoadButton, &QPushButton::clicked, this, &QDialog::accept );
107113
connect( mDeleteButton, &QPushButton::clicked, this, &QgsVectorLayerLoadStyleDialog::deleteStyleFromDB );
114+
connect( this, &QgsVectorLayerLoadStyleDialog::rejected, [ = ]
115+
{
116+
QgsSettings().setValue( QStringLiteral( "style/lastLoadStyleTypeSelection" ), currentStyleType() );
117+
} );
118+
108119
setTabOrder( mRelatedTable, mOthersTable );
109120

110121
restoreGeometry( settings.value( QStringLiteral( "Windows/vectorLayerLoadStyle/geometry" ) ).toByteArray() );
@@ -113,8 +124,7 @@ QgsVectorLayerLoadStyleDialog::QgsVectorLayerLoadStyleDialog( QgsVectorLayer *la
113124

114125
QgsVectorLayerLoadStyleDialog::~QgsVectorLayerLoadStyleDialog()
115126
{
116-
QgsSettings settings;
117-
settings.setValue( QStringLiteral( "Windows/vectorLayerLoadStyle/geometry" ), saveGeometry() );
127+
QgsSettings().setValue( QStringLiteral( "Windows/vectorLayerLoadStyle/geometry" ), saveGeometry() );
118128
}
119129

120130
QgsMapLayer::StyleCategories QgsVectorLayerLoadStyleDialog::styleCategories() const
@@ -246,7 +256,9 @@ void QgsVectorLayerLoadStyleDialog::selectionChanged( QTableWidget *styleTable )
246256

247257
void QgsVectorLayerLoadStyleDialog::accept()
248258
{
249-
QgsSettings().setFlagValue( QStringLiteral( "style/lastStyleCategories" ), styleCategories() );
259+
QgsSettings settings;
260+
settings.setFlagValue( QStringLiteral( "style/lastStyleCategories" ), styleCategories() );
261+
settings.setValue( QStringLiteral( "style/lastLoadStyleTypeSelection" ), currentStyleType() );
250262
QDialog::accept();
251263
}
252264

@@ -274,7 +286,7 @@ void QgsVectorLayerLoadStyleDialog::deleteStyleFromDB()
274286
mRelatedTable->setRowCount( 0 );
275287
mOthersTable->setRowCount( 0 );
276288

277-
//Fill UI widgets again from DB. Other users might have change the styles meanwhile.
289+
//Fill UI widgets again from DB. Other users might have changed the styles meanwhile.
278290
QString errorMsg;
279291
QStringList ids, names, descriptions;
280292
//get the list of styles in the db

src/core/processing/qgsprocessingalgorithm.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ QgsProcessingAlgorithm::~QgsProcessingAlgorithm()
3737
QgsProcessingAlgorithm *QgsProcessingAlgorithm::create( const QVariantMap &configuration ) const
3838
{
3939
std::unique_ptr< QgsProcessingAlgorithm > creation( createInstance() );
40+
if ( ! creation )
41+
throw QgsProcessingException( QObject::tr( "Error creating algorithm from createInstance()" ) );
4042
creation->setProvider( provider() );
4143
creation->initAlgorithm( configuration );
4244
return creation.release();

src/core/processing/qgsprocessingalgorithm.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,12 @@ class CORE_EXPORT QgsProcessingAlgorithm
121121
* algorithms in a model, allowing them to adjust their behavior at run time
122122
* according to some user configuration.
123123
*
124+
* Raises a QgsProcessingException if a new algorithm instance could not be created,
125+
* e.g. if there is an issue with the subclass' createInstance() method.
126+
*
124127
* \see initAlgorithm()
125128
*/
126-
QgsProcessingAlgorithm *create( const QVariantMap &configuration = QVariantMap() ) const SIP_TRANSFERBACK;
129+
QgsProcessingAlgorithm *create( const QVariantMap &configuration = QVariantMap() ) const SIP_THROW( QgsProcessingException ) SIP_TRANSFERBACK;
127130

128131
/**
129132
* Returns the algorithm name, used for identifying the algorithm. This string
@@ -408,7 +411,7 @@ class CORE_EXPORT QgsProcessingAlgorithm
408411
*
409412
* This method should return a 'pristine' instance of the algorithm class.
410413
*/
411-
virtual QgsProcessingAlgorithm *createInstance() const = 0 SIP_FACTORY;
414+
virtual QgsProcessingAlgorithm *createInstance() const = 0 SIP_FACTORY SIP_VIRTUALERRORHANDLER( processing_exception_handler );
412415

413416
/**
414417
* Initializes the algorithm using the specified \a configuration.

src/core/processing/qgsprocessingalgrunnertask.cpp

+13-3
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,30 @@ QgsProcessingAlgRunnerTask::QgsProcessingAlgRunnerTask( const QgsProcessingAlgor
2727
, mParameters( parameters )
2828
, mContext( context )
2929
, mFeedback( feedback )
30-
, mAlgorithm( algorithm->create() )
3130
{
3231
if ( !mFeedback )
3332
{
3433
mOwnedFeedback.reset( new QgsProcessingFeedback() );
3534
mFeedback = mOwnedFeedback.get();
3635
}
37-
if ( !mAlgorithm->prepare( mParameters, context, mFeedback ) )
36+
try
37+
{
38+
mAlgorithm.reset( algorithm->create() );
39+
if ( !( mAlgorithm && mAlgorithm->prepare( mParameters, context, mFeedback ) ) )
40+
cancel();
41+
}
42+
catch ( QgsProcessingException &e )
43+
{
44+
QgsMessageLog::logMessage( e.what(), QObject::tr( "Processing" ), Qgis::Critical );
45+
mFeedback->reportError( e.what() );
3846
cancel();
47+
}
3948
}
4049

4150
void QgsProcessingAlgRunnerTask::cancel()
4251
{
43-
mFeedback->cancel();
52+
if ( mFeedback )
53+
mFeedback->cancel();
4454
QgsTask::cancel();
4555
}
4656

src/core/symbology/qgsrulebasedrenderer.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ QgsLegendSymbolList QgsRuleBasedRenderer::Rule::legendSymbolItems( int currentLe
246246

247247
bool QgsRuleBasedRenderer::Rule::isFilterOK( const QgsFeature &f, QgsRenderContext *context ) const
248248
{
249-
if ( ! mFilter || mElseRule )
249+
if ( ! mFilter || mElseRule || ! context )
250250
return true;
251251

252252
context->expressionContext().setFeature( f );
@@ -647,7 +647,7 @@ QSet<QString> QgsRuleBasedRenderer::Rule::legendKeysForFeature( const QgsFeature
647647
QgsRuleBasedRenderer::RuleList QgsRuleBasedRenderer::Rule::rulesForFeature( const QgsFeature &feature, QgsRenderContext *context, bool onlyActive )
648648
{
649649
RuleList lst;
650-
if ( !isFilterOK( feature, context ) )
650+
if ( ! isFilterOK( feature, context ) || ( context && ! isScaleOK( context->rendererScale() ) ) )
651651
return lst;
652652

653653
if ( mSymbol )

src/providers/ogr/qgsogrprovider.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -6199,7 +6199,7 @@ QGISEXTERN int listStyles( const QString &uri, QStringList &ids, QStringList &na
61996199

62006200
listTimestamp.append( ts );
62016201
mapIdToStyleName[fid] = styleName;
6202-
mapIdToDescription[fid] = styleName;
6202+
mapIdToDescription[fid] = description;
62036203
mapTimestampToId[ts].append( fid );
62046204
}
62056205
}

tests/src/python/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ ADD_PYTHON_TEST(PyQgsPointDisplacementRenderer test_qgspointdisplacementrenderer
146146
ADD_PYTHON_TEST(PyQgsPostgresDomain test_qgspostgresdomain.py)
147147
ADD_PYTHON_TEST(PyQgsProcessingRecentAlgorithmLog test_qgsprocessingrecentalgorithmslog.py)
148148
ADD_PYTHON_TEST(PyQgsProcessingInPlace test_qgsprocessinginplace.py)
149+
ADD_PYTHON_TEST(PyQgsProcessingAlgRunner test_qgsprocessingalgrunner.py)
149150
ADD_PYTHON_TEST(PyQgsProjectionSelectionWidgets test_qgsprojectionselectionwidgets.py)
150151
ADD_PYTHON_TEST(PyQgsProjectMetadata test_qgsprojectmetadata.py)
151152
ADD_PYTHON_TEST(PyQgsRange test_qgsrange.py)

tests/src/python/test_provider_ogr_gpkg.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ def testStyle(self):
489489
self.assertEqual(errmsg, "")
490490
self.assertEqual(idlist, ['1', '3', '4', '2'])
491491
self.assertEqual(namelist, ['name', 'name2', 'name3', 'name_test2'])
492-
self.assertEqual(desclist, ['description_bis', 'description2', 'description3', 'name_test2'])
492+
self.assertEqual(desclist, ['description_bis', 'description2', 'description3', 'description_test2'])
493493

494494
# Check that layers_style table is not list in subLayers()
495495
vl = QgsVectorLayer(tmpfile, 'test', 'ogr')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for Processing algorithm runner(s).
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Alessandro Pasotti'
10+
__date__ = '2019-02'
11+
__copyright__ = 'Copyright 2019, The QGIS Project'
12+
# This will get replaced with a git SHA1 when you do a git archive
13+
__revision__ = '$Format:%H$'
14+
15+
import re
16+
from qgis.PyQt.QtCore import QCoreApplication
17+
from qgis.testing import start_app, unittest
18+
from qgis.core import QgsProcessingAlgRunnerTask
19+
20+
from processing.core.Processing import Processing
21+
from processing.core.ProcessingConfig import ProcessingConfig
22+
from qgis.testing import start_app, unittest
23+
from qgis.analysis import QgsNativeAlgorithms
24+
from qgis.core import (
25+
QgsApplication,
26+
QgsSettings,
27+
QgsProcessingContext,
28+
QgsProcessingAlgRunnerTask,
29+
QgsProcessingAlgorithm,
30+
QgsProject,
31+
QgsProcessingFeedback,
32+
)
33+
34+
start_app()
35+
36+
37+
class ConsoleFeedBack(QgsProcessingFeedback):
38+
39+
_error = ''
40+
41+
def reportError(self, error, fatalError=False):
42+
self._error = error
43+
print(error)
44+
45+
46+
class CrashingProcessingAlgorithm(QgsProcessingAlgorithm):
47+
"""
48+
Wrong class in factory createInstance()
49+
"""
50+
51+
INPUT = 'INPUT'
52+
OUTPUT = 'OUTPUT'
53+
54+
def tr(self, string):
55+
return QCoreApplication.translate('Processing', string)
56+
57+
def createInstance(self):
58+
"""Wrong!"""
59+
return ExampleProcessingAlgorithm()
60+
61+
def name(self):
62+
return 'mycrashingscript'
63+
64+
def displayName(self):
65+
return self.tr('My Crashing Script')
66+
67+
def group(self):
68+
return self.tr('Example scripts')
69+
70+
def groupId(self):
71+
return 'examplescripts'
72+
73+
def shortHelpString(self):
74+
return self.tr("Example algorithm short description")
75+
76+
def initAlgorithm(self, config=None):
77+
pass
78+
79+
def processAlgorithm(self, parameters, context, feedback):
80+
return {self.OUTPUT: 'an_id'}
81+
82+
83+
class TestQgsProcessingAlgRunner(unittest.TestCase):
84+
85+
@classmethod
86+
def setUpClass(cls):
87+
"""Run before all tests"""
88+
QCoreApplication.setOrganizationName("QGIS_Test")
89+
QCoreApplication.setOrganizationDomain(
90+
"QGIS_TestPyQgsProcessingInPlace.com")
91+
QCoreApplication.setApplicationName("QGIS_TestPyQgsProcessingInPlace")
92+
QgsSettings().clear()
93+
Processing.initialize()
94+
QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())
95+
cls.registry = QgsApplication.instance().processingRegistry()
96+
97+
def test_bad_script_dont_crash(self): # spellok
98+
"""Test regression #21270 (segfault)"""
99+
100+
context = QgsProcessingContext()
101+
context.setProject(QgsProject.instance())
102+
feedback = ConsoleFeedBack()
103+
104+
task = QgsProcessingAlgRunnerTask(CrashingProcessingAlgorithm(), {}, context=context, feedback=feedback)
105+
self.assertTrue(task.isCanceled())
106+
self.assertIn('name \'ExampleProcessingAlgorithm\' is not defined', feedback._error)
107+
108+
109+
if __name__ == '__main__':
110+
unittest.main()

0 commit comments

Comments
 (0)