Easy Processing scripts comeback in QGIS 3.6

When QGIS 3.0 was release, I published a Processing script template for QGIS3. While the script template is nicely pythonic, it’s also pretty long and daunting for non-programmers. This fact didn’t go unnoticed and Nathan Woodrow in particular started to work on a QGIS enhancement proposal to improve the situation and make writing Processing scripts easier, while – at the same time – keeping in line with common Python styles.

While the previous template had 57 lines of code, the new template only has 26 lines – 50% less code, same functionality! (Actually, this template provides more functionality since it also tracks progress and ensures that the algorithm can be cancelled.)

from qgis.processing import alg
from qgis.core import QgsFeature, QgsFeatureSink

@alg(name="ex_new", label=alg.tr("Example script (new style)"), group="examplescripts", group_label=alg.tr("Example Scripts"))
@alg.input(type=alg.SOURCE, name="INPUT", label="Input layer")
@alg.input(type=alg.SINK, name="OUTPUT", label="Output layer")
def testalg(instance, parameters, context, feedback, inputs):
    """
    Description goes here. (Don't delete this! Removing this comment will cause errors.)
    """
    source = instance.parameterAsSource(parameters, "INPUT", context)

    (sink, dest_id) = instance.parameterAsSink(
        parameters, "OUTPUT", context,
        source.fields(), source.wkbType(), source.sourceCrs())

    total = 100.0 / source.featureCount() if source.featureCount() else 0
    features = source.getFeatures()
    for current, feature in enumerate(features):
        if feedback.isCanceled():
            break
        out_feature = QgsFeature(feature)
        sink.addFeature(out_feature, QgsFeatureSink.FastInsert)
        feedback.setProgress(int(current * total))

    return {"OUTPUT": dest_id}

The key improvement are the new decorators that turn an ordinary function (such as testalg in the template) into a Processing algorithm. Decorators start with @ and are written above a function definition. The @alg decorator declares that the following function is a Processing algorithm, defines its name and assigns it to an algorithm group. The @alg.input decorator creates an input parameter for the algorithm. Similarly, there is a @alg.output decorator for output parameters.

While the above example demonstrates how to process features individually by getting them from a source and writing them to a sink, some work flows may profit from dealing with layers instead. For example, see the PyQGIS101 tutorial on writing scripts that chain Processing tools.

For more about the @alg decorator check out the official docs.

For a longer example script, check out the original QGIS enhancement proposal thread!

For now, this new way of writing Processing scripts is only supported by QGIS 3.6 but there are plans to back-port this improvement to 3.4 once it is more mature. So give it a try and report back!

Advertisement
5 comments
  1. Hi Anita, thanks so much for pointing to the new method of processing scripts, which is wonderful (at least also a big tribute to Nathan Woodrow and I suppose also to Nyall Dawson). At the beginning I struggled a bit with the @alg INPUT and OUTPUT stuff, but with the new user manual I’ve got it to work. And it is much easier now. I am sure, the parcitipants of my next PYQGIS workshop will love it too!

    best regards
    Stefan

  2. Tohan said:

    My biggest complain so far is that the scripts based on this template seem to be losing the gear icon in the Processing Toolbox! :-( Otherwise it’s great! :-)

    • Strange, please consider opening a ticket on the QGIS bug tracker!

  3. Thank you for presenting this approach! I find it way more intuitive than Extending QgsProcessingAlgorithm. Do you know by chance if there is a way to determine which @alg.input will be shown first in the GUI-Window? Right now, it seems totally random to me and it can be really counterintuitive if several input numbers are asked before an input folder. Thanks and best Regards, Miron

%d bloggers like this: