Skip to content

Commit 9b2e601

Browse files
committedFeb 1, 2019
[processing] Update QgsProcessingModelAlgorithm::asPythonCode for 3.x API
1 parent fb519ea commit 9b2e601

File tree

4 files changed

+239
-53
lines changed

4 files changed

+239
-53
lines changed
 

‎python/core/auto_generated/processing/models/qgsprocessingmodelalgorithm.sip.in

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,13 @@ Sets the source file ``path`` for the model, if available.
346346
.. seealso:: :py:func:`sourceFilePath`
347347
%End
348348

349-
QString asPythonCode() const;
349+
QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, int indentSize ) const;
350350
%Docstring
351-
Attempts to convert the model to executable Python code.
351+
Attempts to convert the model to executable Python code, and returns the generated lines of code.
352+
353+
The ``outputType`` argument dictates the desired script type.
354+
355+
The ``indentSize`` arguments specifies the size of indented lines.
352356
%End
353357

354358
QList< QgsProcessingModelChildParameterSource > availableSourcesForChild( const QString &childId, const QStringList &parameterTypes = QStringList(),

‎src/core/processing/models/qgsprocessingmodelalgorithm.cpp

Lines changed: 167 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -353,50 +353,132 @@ void QgsProcessingModelAlgorithm::setSourceFilePath( const QString &sourceFile )
353353
mSourceFile = sourceFile;
354354
}
355355

356-
QString QgsProcessingModelAlgorithm::asPythonCode() const
356+
QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::PythonOutputType outputType, const int indentSize ) const
357357
{
358358
QStringList lines;
359-
lines << QStringLiteral( "##%1=name" ).arg( name() );
360-
361-
QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
362-
for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
359+
QString indent = QString( ' ' ).repeated( indentSize );
360+
QString currentIndent;
361+
switch ( outputType )
363362
{
364-
QString name = paramIt.value().parameterName();
365-
if ( parameterDefinition( name ) )
363+
case QgsProcessing::PythonQgsProcessingAlgorithmSubclass:
366364
{
367-
lines << parameterDefinition( name )->asScriptCode();
368-
}
369-
}
365+
lines << QStringLiteral( "from qgis.core import QgsProcessing" );
366+
lines << QStringLiteral( "from qgis.core import QgsProcessingAlgorithm" );
367+
// add specific parameter type imports
368+
const auto params = parameterDefinitions();
369+
QStringList importLines; // not a set - we need regular ordering
370+
for ( const QgsProcessingParameterDefinition *def : params )
371+
{
372+
const QString importString = QgsApplication::processingRegistry()->parameterType( def->type() )->pythonImportString();
373+
if ( !importString.isEmpty() && !importLines.contains( importString ) )
374+
importLines << importString;
375+
}
376+
lines << importLines;
377+
lines << QStringLiteral( "import processing" );
378+
lines << QString() << QString();
370379

371-
auto safeName = []( const QString & name )->QString
372-
{
373-
QString n = name.toLower().trimmed();
374-
QRegularExpression rx( QStringLiteral( "[^a-z_]" ) );
375-
n.replace( rx, QString() );
376-
return n;
377-
};
380+
auto safeName = []( const QString & name )->QString
381+
{
382+
QString n = name.toLower().trimmed();
383+
QRegularExpression rx( QStringLiteral( "[^a-z_A-Z0-9]" ) );
384+
n.replace( rx, QString() );
385+
return n;
386+
};
387+
388+
const QString algorithmClassName = safeName( name() );
389+
lines << QStringLiteral( "class %1(QgsProcessingAlgorithm):" ).arg( algorithmClassName );
390+
391+
// createInstance
392+
lines << indent + QStringLiteral( "def createInstance(self):" );
393+
lines << indent + indent + QStringLiteral( "return %1()" ).arg( algorithmClassName );
394+
lines << QString();
395+
396+
// name, displayName
397+
lines << indent + QStringLiteral( "def name(self):" );
398+
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( mModelName );
399+
lines << QString();
400+
lines << indent + QStringLiteral( "def displayName(self):" );
401+
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( mModelName );
402+
lines << QString();
403+
404+
// group, groupId
405+
lines << indent + QStringLiteral( "def group(self):" );
406+
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( mModelGroup );
407+
lines << QString();
408+
lines << indent + QStringLiteral( "def groupId(self):" );
409+
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( mModelGroupId );
410+
lines << QString();
411+
412+
// help
413+
if ( !shortHelpString().isEmpty() )
414+
{
415+
lines << indent + QStringLiteral( "def shortHelpString(self):" );
416+
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( shortHelpString() );
417+
lines << QString();
418+
}
419+
if ( !helpUrl().isEmpty() )
420+
{
421+
lines << indent + QStringLiteral( "def helpUrl(self):" );
422+
lines << indent + indent + QStringLiteral( "return '%1'" ).arg( helpUrl() );
423+
lines << QString();
424+
}
378425

379-
QMap< QString, QgsProcessingModelChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
380-
for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
381-
{
382-
if ( !childIt->isActive() || !childIt->algorithm() )
383-
continue;
426+
// initAlgorithm, parameter definitions
427+
lines << indent + QStringLiteral( "def initAlgorithm(self, config=None):" );
428+
lines.reserve( lines.size() + params.size() );
429+
for ( const QgsProcessingParameterDefinition *def : params )
430+
{
431+
lines << indent + indent + QStringLiteral( "self.addParameter(%1)" ).arg( def->asPythonString() );
432+
}
384433

385-
// look through all outputs for child
386-
QMap<QString, QgsProcessingModelOutput> outputs = childIt->modelOutputs();
387-
QMap<QString, QgsProcessingModelOutput>::const_iterator outputIt = outputs.constBegin();
388-
for ( ; outputIt != outputs.constEnd(); ++outputIt )
434+
lines << QString();
435+
lines << indent + QStringLiteral( "def processAlgorithm(self, parameters, context, feedback):" );
436+
currentIndent = indent + indent;
437+
break;
438+
}
439+
#if 0
440+
case Script:
389441
{
390-
const QgsProcessingOutputDefinition *output = childIt->algorithm()->outputDefinition( outputIt->childOutputName() );
391-
lines << QStringLiteral( "##%1=output %2" ).arg( safeName( outputIt->name() ), output->type() );
442+
QgsStringMap params;
443+
QgsProcessingContext context;
444+
QMap< QString, QgsProcessingModelParameter >::const_iterator paramIt = mParameterComponents.constBegin();
445+
for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt )
446+
{
447+
QString name = paramIt.value().parameterName();
448+
if ( parameterDefinition( name ) )
449+
{
450+
// TODO - generic value to string method
451+
params.insert( name, parameterDefinition( name )->valueAsPythonString( parameterDefinition( name )->defaultValue(), context ) );
452+
}
453+
}
454+
455+
if ( !params.isEmpty() )
456+
{
457+
lines << QStringLiteral( "parameters = {" );
458+
for ( auto it = params.constBegin(); it != params.constEnd(); ++it )
459+
{
460+
lines << QStringLiteral( " '%1':%2," ).arg( it.key(), it.value() );
461+
}
462+
lines << QStringLiteral( "}" )
463+
<< QString();
464+
}
465+
466+
lines << QStringLiteral( "context = QgsProcessingContext()" )
467+
<< QStringLiteral( "context.setProject(QgsProject.instance())" )
468+
<< QStringLiteral( "feedback = QgsProcessingFeedback()" )
469+
<< QString();
470+
471+
break;
392472
}
473+
#endif
474+
393475
}
394476

395-
lines << QStringLiteral( "results={}" );
477+
lines << currentIndent + QStringLiteral( "results={}" );
478+
lines << currentIndent + QStringLiteral( "outputs={}" );
396479

397480
QSet< QString > toExecute;
398-
childIt = mChildAlgorithms.constBegin();
399-
for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt )
481+
for ( auto childIt = mChildAlgorithms.constBegin(); childIt != mChildAlgorithms.constEnd(); ++childIt )
400482
{
401483
if ( childIt->isActive() && childIt->algorithm() )
402484
toExecute.insert( childIt->childId() );
@@ -428,15 +510,66 @@ QString QgsProcessingModelAlgorithm::asPythonCode() const
428510
executedAlg = true;
429511

430512
const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms[ childId ];
431-
lines << child.asPythonCode();
513+
514+
// fill in temporary outputs
515+
const QgsProcessingParameterDefinitions childDefs = child.algorithm()->parameterDefinitions();
516+
QgsStringMap childParams;
517+
for ( const QgsProcessingParameterDefinition *def : childDefs )
518+
{
519+
if ( def->isDestination() )
520+
{
521+
const QgsProcessingDestinationParameter *destParam = static_cast< const QgsProcessingDestinationParameter * >( def );
522+
523+
// is destination linked to one of the final outputs from this model?
524+
bool isFinalOutput = false;
525+
QMap<QString, QgsProcessingModelOutput> outputs = child.modelOutputs();
526+
QMap<QString, QgsProcessingModelOutput>::const_iterator outputIt = outputs.constBegin();
527+
for ( ; outputIt != outputs.constEnd(); ++outputIt )
528+
{
529+
if ( outputIt->childOutputName() == destParam->name() )
530+
{
531+
QString paramName = child.childId() + ':' + outputIt.key();
532+
childParams.insert( destParam->name(), QStringLiteral( "parameters['%1']" ).arg( paramName ) );
533+
isFinalOutput = true;
534+
break;
535+
}
536+
}
537+
538+
if ( !isFinalOutput )
539+
{
540+
// output is temporary
541+
542+
// check whether it's optional, and if so - is it required?
543+
bool required = true;
544+
if ( destParam->flags() & QgsProcessingParameterDefinition::FlagOptional )
545+
{
546+
required = childOutputIsRequired( child.childId(), destParam->name() );
547+
}
548+
549+
// not optional, or required elsewhere in model
550+
if ( required )
551+
{
552+
553+
childParams.insert( destParam->name(), QStringLiteral( "QgsProcessing.TEMPORARY_OUTPUT" ) );
554+
}
555+
}
556+
}
557+
}
558+
559+
lines << child.asPythonCode( outputType, childParams, currentIndent.size(), indentSize );
432560

433561
executed.insert( childId );
434562
}
435563
}
436564

437-
lines << QStringLiteral( "return results" );
565+
switch ( outputType )
566+
{
567+
case QgsProcessing::PythonQgsProcessingAlgorithmSubclass:
568+
lines << currentIndent + QStringLiteral( "return results" );
569+
break;
570+
}
438571

439-
return lines.join( '\n' );
572+
return lines;
440573
}
441574

442575
QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> QgsProcessingModelAlgorithm::variablesForChildAlgorithm( const QString &childId, QgsProcessingContext &context, const QVariantMap &modelParameters, const QVariantMap &results ) const

‎src/core/processing/models/qgsprocessingmodelalgorithm.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,13 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
304304
void setSourceFilePath( const QString &path );
305305

306306
/**
307-
* Attempts to convert the model to executable Python code.
307+
* Attempts to convert the model to executable Python code, and returns the generated lines of code.
308+
*
309+
* The \a outputType argument dictates the desired script type.
310+
*
311+
* The \a indentSize arguments specifies the size of indented lines.
308312
*/
309-
QString asPythonCode() const;
313+
QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, int indentSize ) const;
310314

311315
/**
312316
* Returns a list of possible sources which can be used for the parameters for a child

‎tests/src/analysis/testqgsprocessing.cpp

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6244,10 +6244,8 @@ void TestQgsProcessing::modelerAlgorithm()
62446244
child.removeModelOutput( QStringLiteral( "a" ) );
62456245
QCOMPARE( child.modelOutputs().count(), 1 );
62466246

6247-
62486247
// model algorithm tests
62496248

6250-
62516249
QgsProcessingModelAlgorithm alg( "test", "testGroup" );
62526250
QCOMPARE( alg.name(), QStringLiteral( "test" ) );
62536251
QCOMPARE( alg.displayName(), QStringLiteral( "test" ) );
@@ -6948,19 +6946,66 @@ void TestQgsProcessing::modelExecution()
69486946
QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxx" ).value.toDouble(), -83.3333, 0.001 );
69496947
QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxy" ).value.toDouble(), 46.8719, 0.001 );
69506948

6951-
QStringList actualParts = model2.asPythonCode().split( '\n' );
6952-
QStringList expectedParts = QStringLiteral( "##model=name\n"
6953-
"##DIST=number\n"
6954-
"##SOURCE_LAYER=source\n"
6955-
"##model_out_layer=output outputVector\n"
6956-
"##my_out=output outputVector\n"
6957-
"results={}\n"
6958-
"outputs['cx1']=processing.run('native:buffer', {'DISSOLVE':false,'DISTANCE':parameters['DIST'],'END_CAP_STYLE':1,'INPUT':parameters['SOURCE_LAYER'],'JOIN_STYLE':2,'SEGMENTS':QgsExpression('@myvar*2').evaluate()}, context=context, feedback=feedback)\n"
6959-
"results['MODEL_OUT_LAYER']=outputs['cx1']['OUTPUT']\n"
6960-
"outputs['cx2']=processing.run('native:centroids', {'INPUT':outputs['cx1']['OUTPUT']}, context=context, feedback=feedback)\n"
6961-
"outputs['cx3']=processing.run('native:extractbyexpression', {'EXPRESSION':true,'INPUT':outputs['cx1']['OUTPUT'],'OUTPUT':parameters['MY_OUT']}, context=context, feedback=feedback)\n"
6962-
"results['MY_OUT']=outputs['cx3']['OUTPUT']\n"
6963-
"return results" ).split( '\n' );
6949+
QStringList actualParts = model2.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, 2 );
6950+
QgsDebugMsg( actualParts.join( '\n' ) );
6951+
QStringList expectedParts = QStringLiteral( "from qgis.core import QgsProcessing\n"
6952+
"from qgis.core import QgsProcessingAlgorithm\n"
6953+
"from qgis.core import QgsProcessingParameterFeatureSource\n"
6954+
"from qgis.core import QgsProcessingParameterNumber\n"
6955+
"from qgis.core import QgsProcessingParameterFeatureSink\n"
6956+
"import processing\n"
6957+
"\n"
6958+
"\n"
6959+
"class model(QgsProcessingAlgorithm):\n"
6960+
" def createInstance(self):\n"
6961+
" return model()\n"
6962+
"\n"
6963+
" def name(self):\n"
6964+
" return 'model'\n"
6965+
"\n"
6966+
" def displayName(self):\n"
6967+
" return 'model'\n"
6968+
"\n"
6969+
" def group(self):\n"
6970+
" return ''\n"
6971+
"\n"
6972+
" def groupId(self):\n"
6973+
" return ''\n"
6974+
"\n"
6975+
" def initAlgorithm(self, config=None):\n"
6976+
" self.addParameter(QgsProcessingParameterFeatureSource('SOURCE_LAYER', '', defaultValue=None))\n"
6977+
" self.addParameter(QgsProcessingParameterNumber('DIST', '', type=QgsProcessingParameterNumber.Double, defaultValue=None))\n"
6978+
" self.addParameter(QgsProcessingParameterFeatureSink('cx1:MODEL_OUT_LAYER', '', type=QgsProcessing.TypeVectorPolygon, createByDefault=True, defaultValue=None))\n"
6979+
" self.addParameter(QgsProcessingParameterFeatureSink('cx3:MY_OUT', '', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue=None))\n"
6980+
"\n"
6981+
" def processAlgorithm(self, parameters, context, feedback):\n"
6982+
" results={}\n"
6983+
" outputs={}\n"
6984+
" alg_params = {\n"
6985+
" 'DISSOLVE':False,\n"
6986+
" 'DISTANCE':parameters['DIST'],\n"
6987+
" 'END_CAP_STYLE':1,\n"
6988+
" 'INPUT':parameters['SOURCE_LAYER'],\n"
6989+
" 'JOIN_STYLE':2,\n"
6990+
" 'SEGMENTS':QgsExpression('@myvar*2').evaluate(),\n"
6991+
" 'OUTPUT':parameters['cx1:MODEL_OUT_LAYER'],\n"
6992+
" }\n"
6993+
" outputs['cx1']=processing.run('native:buffer', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n"
6994+
" results['cx1:MODEL_OUT_LAYER']=outputs['cx1']['OUTPUT']\n"
6995+
" alg_params = {\n"
6996+
" 'INPUT':outputs['cx1']['OUTPUT'],\n"
6997+
" 'OUTPUT':QgsProcessing.TEMPORARY_OUTPUT,\n"
6998+
" }\n"
6999+
" outputs['cx2']=processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n"
7000+
" alg_params = {\n"
7001+
" 'EXPRESSION':'true',\n"
7002+
" 'INPUT':outputs['cx1']['OUTPUT'],\n"
7003+
" 'OUTPUT':parameters['MY_OUT'],\n"
7004+
" 'OUTPUT':parameters['cx3:MY_OUT'],\n"
7005+
" }\n"
7006+
" outputs['cx3']=processing.run('native:extractbyexpression', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n"
7007+
" results['cx3:MY_OUT']=outputs['cx3']['OUTPUT']\n"
7008+
" return results" ).split( '\n' );
69647009
QCOMPARE( actualParts, expectedParts );
69657010
}
69667011

0 commit comments

Comments
 (0)
Please sign in to comment.