Skip to content

Commit

Permalink
[processing] When converting models to Python, use friendlier (more
Browse files Browse the repository at this point in the history
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.
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit d342ce9

Please sign in to comment.