Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[processing] When converting models to Python, use friendlier (more
descriptive) names when storing child algorithm results

Makes for much easier to understand scripts
  • Loading branch information
nyalldawson committed Feb 14, 2019
1 parent 3ceea2a commit d342ce9
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 38 deletions.
Expand Up @@ -298,7 +298,7 @@ Loads this child from a QVariant.
.. seealso:: :py:func:`toVariant`
%End

QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize ) const;
QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize, const QMap<QString, QString> &friendlyChildNames ) const;
%Docstring
Attempts to convert the child to executable Python code, and returns a list of the generated lines of code.

Expand All @@ -307,6 +307,8 @@ The ``outputType`` argument specifies the type of script to generate.
Additional parameters to be passed to the child algorithm are specified in the ``extraParameters`` argument.

The ``currentIndent`` and ``indentSize`` are used to set the base line indent and size of further indented lines respectively.

The ``friendlyChildNames`` argument gives a map of child id to a friendly algorithm name, to be used in the code to identify that algorithm instead of the raw child id.
%End

};
Expand Down
Expand Up @@ -240,9 +240,11 @@ Loads this source from a QVariantMap.
.. seealso:: :py:func:`toVariant`
%End

QString asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsProcessingParameterDefinition *definition ) const;
QString asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsProcessingParameterDefinition *definition, const QMap< QString, QString > &friendlydChildNames ) const;
%Docstring
Attempts to convert the source to executable Python code.

The ``friendlyChildNames`` argument gives a map of child id to a friendly algorithm name, to be used in the code to identify that algorithm instead of the raw child id.
%End

};
Expand Down
28 changes: 24 additions & 4 deletions src/core/processing/models/qgsprocessingmodelalgorithm.cpp
Expand Up @@ -361,23 +361,42 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
QString indent = QString( ' ' ).repeated( indentSize );
QString currentIndent;

auto safeName = []( const QString & name )->QString
QMap< QString, QString> friendlyChildNames;
auto safeName = []( const QString & name, bool capitalize )->QString
{
QString n = name.toLower().trimmed();
QRegularExpression rx( QStringLiteral( "[^\\sa-z_A-Z0-9]" ) );
n.replace( rx, QString() );
QRegularExpression rx2( QStringLiteral( "^\\d*" ) ); // name can't start in a digit
n.replace( rx2, QString() );
return QgsStringUtils::capitalize( n, QgsStringUtils::UpperCamelCase );
if ( !capitalize )
n = n.replace( ' ', '_' );
return capitalize ? QgsStringUtils::capitalize( n, QgsStringUtils::UpperCamelCase ) : n;
};

const QString algorithmClassName = safeName( name() );
auto uniqueSafeName = [&friendlyChildNames, &safeName ]( const QString & name, bool capitalize )->QString
{
const QString base = safeName( name, capitalize );
QString candidate = base;
int i = 1;
while ( friendlyChildNames.contains( candidate ) )
{
i++;
candidate = QStringLiteral( "%1_%2" ).arg( base ).arg( i );
}
return candidate;
};

const QString algorithmClassName = safeName( name(), true );

QSet< QString > toExecute;
for ( auto childIt = mChildAlgorithms.constBegin(); childIt != mChildAlgorithms.constEnd(); ++childIt )
{
if ( childIt->isActive() && childIt->algorithm() )
{
toExecute.insert( childIt->childId() );
friendlyChildNames.insert( childIt->childId(), uniqueSafeName( childIt->description().isEmpty() ? childIt->childId() : childIt->description(), !childIt->description().isEmpty() ) );
}
}
const int totalSteps = toExecute.count();

Expand Down Expand Up @@ -462,6 +481,7 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth

lines << currentIndent + QStringLiteral( "results = {}" );
lines << currentIndent + QStringLiteral( "outputs = {}" );
lines << QString();

QSet< QString > executed;
bool executedAlg = true;
Expand Down Expand Up @@ -536,7 +556,7 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
}
}

lines << child.asPythonCode( outputType, childParams, currentIndent.size(), indentSize );
lines << child.asPythonCode( outputType, childParams, currentIndent.size(), indentSize, friendlyChildNames );
currentStep++;
if ( currentStep < totalSteps )
{
Expand Down
Expand Up @@ -162,7 +162,7 @@ bool QgsProcessingModelChildAlgorithm::loadVariant( const QVariant &child )
return true;
}

QStringList QgsProcessingModelChildAlgorithm::asPythonCode( const QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize ) const
QStringList QgsProcessingModelChildAlgorithm::asPythonCode( const QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize, const QMap<QString, QString> &friendlyChildNames ) const
{
QStringList lines;
const QString baseIndent = QString( ' ' ).repeated( currentIndent );
Expand All @@ -181,7 +181,7 @@ QStringList QgsProcessingModelChildAlgorithm::asPythonCode( const QgsProcessing:
const auto parts = paramIt.value();
for ( const QgsProcessingModelChildParameterSource &source : parts )
{
QString part = source.asPythonCode( outputType, def );
QString part = source.asPythonCode( outputType, def, friendlyChildNames );
if ( !part.isEmpty() )
sourceParts << part;
}
Expand All @@ -207,11 +207,11 @@ QStringList QgsProcessingModelChildAlgorithm::asPythonCode( const QgsProcessing:
}
lines << baseIndent + QStringLiteral( "}" );

lines << baseIndent + QStringLiteral( "outputs['%1'] = processing.run('%2', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ).arg( mId, mAlgorithmId );
lines << baseIndent + QStringLiteral( "outputs['%1'] = processing.run('%2', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ).arg( friendlyChildNames.value( mId, mId ), mAlgorithmId );

for ( auto outputIt = mModelOutputs.constBegin(); outputIt != mModelOutputs.constEnd(); ++outputIt )
{
lines << baseIndent + QStringLiteral( "results['%1:%2'] = outputs['%1']['%3']" ).arg( mId, outputIt.key(), outputIt.value().childOutputName() );
lines << baseIndent + QStringLiteral( "results['%1:%2'] = outputs['%3']['%4']" ).arg( mId, outputIt.key(), friendlyChildNames.value( mId, mId ), outputIt.value().childOutputName() );
}

return lines;
Expand Down
Expand Up @@ -285,8 +285,10 @@ class CORE_EXPORT QgsProcessingModelChildAlgorithm : public QgsProcessingModelCo
* Additional parameters to be passed to the child algorithm are specified in the \a extraParameters argument.
*
* The \a currentIndent and \a indentSize are used to set the base line indent and size of further indented lines respectively.
*
* The \a friendlyChildNames argument gives a map of child id to a friendly algorithm name, to be used in the code to identify that algorithm instead of the raw child id.
*/
QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize ) const;
QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize, const QMap<QString, QString> &friendlyChildNames ) const;

private:

Expand Down
Expand Up @@ -147,15 +147,15 @@ bool QgsProcessingModelChildParameterSource::loadVariant( const QVariantMap &map
return true;
}

QString QgsProcessingModelChildParameterSource::asPythonCode( const QgsProcessing::PythonOutputType, const QgsProcessingParameterDefinition *definition ) const
QString QgsProcessingModelChildParameterSource::asPythonCode( const QgsProcessing::PythonOutputType, const QgsProcessingParameterDefinition *definition, const QMap< QString, QString > &friendlydChildNames ) const
{
switch ( mSource )
{
case ModelParameter:
return QStringLiteral( "parameters['%1']" ).arg( mParameterName );

case ChildOutput:
return QStringLiteral( "outputs['%1']['%2']" ).arg( mChildId, mOutputName );
return QStringLiteral( "outputs['%1']['%2']" ).arg( friendlydChildNames.value( mChildId, mChildId ), mOutputName );

case StaticValue:
if ( definition )
Expand Down
Expand Up @@ -215,8 +215,10 @@ class CORE_EXPORT QgsProcessingModelChildParameterSource

/**
* Attempts to convert the source to executable Python code.
*
* The \a friendlyChildNames argument gives a map of child id to a friendly algorithm name, to be used in the code to identify that algorithm instead of the raw child id.
*/
QString asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsProcessingParameterDefinition *definition ) const;
QString asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsProcessingParameterDefinition *definition, const QMap< QString, QString > &friendlydChildNames ) const;

private:

Expand Down

0 comments on commit d342ce9

Please sign in to comment.