Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add method to validate a child algorithm in a model
Checks that the algorithm has valid values for all inputs
  • Loading branch information
nyalldawson committed Apr 14, 2020
1 parent 07d072b commit 9f7c7fc
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 0 deletions.
Expand Up @@ -180,6 +180,19 @@ Returns a list of the child algorithm IDs on which the child
algorithm with the specified ``childId`` depends upon.

.. seealso:: :py:func:`dependentChildAlgorithms`
%End

bool validateChildAlgorithm( const QString &childId, QStringList &issues /Out/ ) const;
%Docstring
Validates the child algorithm with matching ID, returning ``True`` if
all mandatory inputs to the algorithm have valid values.

:param childId: ID for child to validate

:return: - ``True`` if the child is valid
- issues: will be set to a list of issues encountered during the validation

.. versionadded:: 3.14
%End

void addModelParameter( QgsProcessingParameterDefinition *definition /Transfer/, const QgsProcessingModelParameter &component );
Expand Down
81 changes: 81 additions & 0 deletions src/core/processing/models/qgsprocessingmodelalgorithm.cpp
Expand Up @@ -1632,6 +1632,87 @@ QSet< QString > QgsProcessingModelAlgorithm::dependsOnChildAlgorithms( const QSt
return algs;
}

bool QgsProcessingModelAlgorithm::validateChildAlgorithm( const QString &childId, QStringList &issues ) const
{
issues.clear();
QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constFind( childId );
if ( childIt != mChildAlgorithms.constEnd() )
{
if ( !childIt->algorithm() )
{
issues << QObject::tr( "Algorithm is not available: <i>%1</i>" ).arg( childIt->algorithmId() );
return false;
}
bool res = true;

// loop through child algorithm parameters and check that they are all valid
const QgsProcessingParameterDefinitions defs = childIt->algorithm()->parameterDefinitions();
for ( const QgsProcessingParameterDefinition *def : defs )
{
if ( childIt->parameterSources().contains( def->name() ) )
{
// is the value acceptable?
const QList< QgsProcessingModelChildParameterSource > sources = childIt->parameterSources().value( def->name() );
for ( const QgsProcessingModelChildParameterSource &source : sources )
{
switch ( source.source() )
{
case QgsProcessingModelChildParameterSource::StaticValue:
if ( !def->checkValueIsAcceptable( source.staticValue() ) )
{
res = false;
issues << QObject::tr( "Value for <i>%1</i> is not acceptable for this parameter" ).arg( def->name() );
}
break;

case QgsProcessingModelChildParameterSource::ModelParameter:
if ( !parameterComponents().contains( source.parameterName() ) )
{
res = false;
issues << QObject::tr( "Model input <i>%1</i> used for parameter <i>%2</i> does not exist" ).arg( source.parameterName(), def->name() );
}
break;

case QgsProcessingModelChildParameterSource::ChildOutput:
if ( !childAlgorithms().contains( source.outputChildId() ) )
{
res = false;
issues << QObject::tr( "Child algorithm <i>%1</i> used for parameter <i>%2</i> does not exist" ).arg( source.outputChildId(), def->name() );
}
break;

case QgsProcessingModelChildParameterSource::Expression:
case QgsProcessingModelChildParameterSource::ExpressionText:
case QgsProcessingModelChildParameterSource::ModelOutput:
break;
}
}
}
else
{
// not specified. Is it optional?

// ignore destination parameters -- they shouldn't ever be mandatory
if ( def->isDestination() )
continue;

if ( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) )
{
res = false;
issues << QObject::tr( "Parameter <i>%1</i> is mandatory" ).arg( def->name() );
}
}
}

return res;
}
else
{
issues << QObject::tr( "Invalid child ID: <i>%1</i>" ).arg( childId );
return false;
}
}

bool QgsProcessingModelAlgorithm::canExecute( QString *errorMessage ) const
{
reattachAlgorithms();
Expand Down
11 changes: 11 additions & 0 deletions src/core/processing/models/qgsprocessingmodelalgorithm.h
Expand Up @@ -160,6 +160,17 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
*/
QSet< QString > dependsOnChildAlgorithms( const QString &childId ) const;

/**
* Validates the child algorithm with matching ID, returning TRUE if
* all mandatory inputs to the algorithm have valid values.
*
* \param childId ID for child to validate
* \param issues will be set to a list of issues encountered during the validation
* \returns TRUE if the child is valid
* \since QGIS 3.14
*/
bool validateChildAlgorithm( const QString &childId, QStringList &issues SIP_OUT ) const;

/**
* Adds a new parameter to the model, with the specified \a definition and graphical \a component.
* Ownership of \a definition is transferred to the model.
Expand Down
59 changes: 59 additions & 0 deletions tests/src/analysis/testqgsprocessing.cpp
Expand Up @@ -588,6 +588,7 @@ class TestQgsProcessing: public QObject
void modelWithProviderWithLimitedTypes();
void modelVectorOutputIsCompatibleType();
void modelAcceptableValues();
void modelValidate();
void tempUtils();
void convertCompatible();
void create();
Expand Down Expand Up @@ -9895,6 +9896,64 @@ void TestQgsProcessing::modelAcceptableValues()
QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
}

void TestQgsProcessing::modelValidate()
{
QgsProcessingModelAlgorithm m;
QgsProcessingModelParameter stringParam1( "string" );
m.addModelParameter( new QgsProcessingParameterString( "string" ), stringParam1 );
QgsProcessingModelChildAlgorithm alg2c1;
alg2c1.setChildId( "cx1" );
alg2c1.setAlgorithmId( "native:centroids" );
m.addChildAlgorithm( alg2c1 );

QStringList errors;
QVERIFY( !m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
QCOMPARE( errors.size(), 2 );
QCOMPARE( errors.at( 0 ), QStringLiteral( "Parameter <i>INPUT</i> is mandatory" ) );
QCOMPARE( errors.at( 1 ), QStringLiteral( "Parameter <i>ALL_PARTS</i> is mandatory" ) );

QgsProcessingModelChildParameterSource badSource;
badSource.setSource( QgsProcessingModelChildParameterSource::StaticValue );
badSource.setStaticValue( 56 );
m.childAlgorithm( QStringLiteral( "cx1" ) ).addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << badSource );

QVERIFY( !m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
QCOMPARE( errors.size(), 2 );
QCOMPARE( errors.at( 0 ), QStringLiteral( "Value for <i>INPUT</i> is not acceptable for this parameter" ) );
QCOMPARE( errors.at( 1 ), QStringLiteral( "Parameter <i>ALL_PARTS</i> is mandatory" ) );

QgsProcessingModelChildParameterSource goodSource;
goodSource.setSource( QgsProcessingModelChildParameterSource::Expression );
m.childAlgorithm( QStringLiteral( "cx1" ) ).addParameterSources( QStringLiteral( "ALL_PARTS" ), QList< QgsProcessingModelChildParameterSource >() << goodSource );

QVERIFY( !m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
QCOMPARE( errors.size(), 1 );
QCOMPARE( errors.at( 0 ), QStringLiteral( "Value for <i>INPUT</i> is not acceptable for this parameter" ) );

badSource.setSource( QgsProcessingModelChildParameterSource::ChildOutput );
badSource.setOutputChildId( QStringLiteral( "cc" ) );
m.childAlgorithm( QStringLiteral( "cx1" ) ).addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << badSource );

QVERIFY( !m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
QCOMPARE( errors.size(), 1 );
QCOMPARE( errors.at( 0 ), QStringLiteral( "Child algorithm <i>cc</i> used for parameter <i>INPUT</i> does not exist" ) );

badSource.setSource( QgsProcessingModelChildParameterSource::ModelParameter );
badSource.setParameterName( QStringLiteral( "cc" ) );
m.childAlgorithm( QStringLiteral( "cx1" ) ).addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << badSource );

QVERIFY( !m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
QCOMPARE( errors.size(), 1 );
QCOMPARE( errors.at( 0 ), QStringLiteral( "Model input <i>cc</i> used for parameter <i>INPUT</i> does not exist" ) );

goodSource.setSource( QgsProcessingModelChildParameterSource::StaticValue );
goodSource.setStaticValue( QStringLiteral( TEST_DATA_DIR ) + "/polys.shp" );
m.childAlgorithm( QStringLiteral( "cx1" ) ).addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << goodSource );

QVERIFY( m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
QCOMPARE( errors.size(), 0 );
}

void TestQgsProcessing::tempUtils()
{
QString tempFolder = QgsProcessingUtils::tempFolder();
Expand Down

0 comments on commit 9f7c7fc

Please sign in to comment.