Previously, we already covered how to run Processing tools as well as how to chain Processing tools to create more complex workflows. This time, we will go one step further and create a Processing script that can be called from the Processing toolbox.

You will find different approaches for writing Processing scripts when you look for examples online. This is the case because QGIS >= 3.6 supports a simpler script style which I’ve written about in “Easy Processing scripts comeback in QGIS 3.6”. The following example works in QGIS 3.4 and above.

When we are done, our Processing script will be able to take a vector layer as input, perform a buffer operation on this layer, and return the buffered output.

Select Create New Script from the Processing toolbox to get started:

To create a new Processing script, we need to implement our own QgsProcessingAlgorithm. This means that we have to implement our own custom class (called ExampleAlgo here) that is based on QgsProcessingAlgorithm:

from qgis.core import QgsProcessingAlgorithm

class ExampleAlgo(QgsProcessingAlgorithm):

This is just the start. Besides the constructor, our algorithm has to provide the following mandatory functions:

  • nameReturns the unique algorithm name, used for identifying the algorithm.
  • displayName: Returns the algorithm name that will be displayed in the Processing toolbox.
  • createInstance: Must return a new copy of your algorithm. 
  • initAlgorithm: Here we define the inputs and outputs of the algorithm. INPUT and OUTPUT are recommended names for the main input and main output parameters, respectively.
  • processAlgorithm: This is where the processing takes place.

The first three functions are not very exciting. We need to pick a name and display name for the algorithm and the algorithm group we want to put it in. The createInstance function always looks same:

    def name(self):
        return 'ex_script'

    def displayName(self):
        return 'Example script'

    def group(self):
        return 'Example Scripts'

    def groupId(self):
        return 'examplescripts'

    def createInstance(self):
        return type(self)()

Next, we need to define the inputs and outputs of our algorithm by adding two corresponding parameters to our algorithm: QgsProcessingParameterVectorLayer is the input and QgsProcessingParameterFeatureSink is the output. Note that this requires that we import these parameters at the beginning of our script:

from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterVectorLayer
from qgis.core import QgsProcessingParameterFeatureSink

The strings ‘Input layer’ and ‘Output layer’ are the labels that will be shown in the automatically generated user interface dialog. (If you look at other more complex Processing script examples, such as the script in the official user manual, you’ll find that these strings can be translated for different QGIS language settings by adding a tr() function.)

    def initAlgorithm(self, config=None):
        self.addParameter(
            QgsProcessingParameterVectorLayer(
                'input', 'INPUT', 
                types=[QgsProcessing.TypeVectorAnyGeometry]
            )
        )
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                'output', 'OUTPUT', 
                type=QgsProcessing.TypeVectorPolygon
            )
        )

Now we can finally implement the actual data processing code in the processAlgorithm() function. This is based on what we already covered in Chaining Processing tools. However, note that because the buffer algorithm is being run as a step in our larger algorithm, the is_child_algorithm option should be set to True:

    def processAlgorithm(self, parameters, context, feedback):
        feedback = QgsProcessingMultiStepFeedback(1, feedback)
        results = {}
        outputs = {}

        # Buffer
        alg_params = {
            'DISSOLVE': False,
            'DISTANCE': 100000,
            'END_CAP_STYLE': 0,  # Round
            'INPUT': parameters['input'],
            'JOIN_STYLE': 0,  # Round
            'MITER_LIMIT': 2,
            'SEGMENTS': 5,
            'OUTPUT': parameters['output']
        }
        outputs['Buffer'] = processing.run(
            'native:buffer', 
            alg_params, 
            context=context, 
            feedback=feedback, 
            is_child_algorithm=True)
        
        results['Output'] = outputs['Buffer']['OUTPUT']
        return results

If we put it all together, the final script looks like this:

from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterVectorLayer
from qgis.core import QgsProcessingParameterFeatureSink
import processing


class ExampleAlgo(QgsProcessingAlgorithm):

    def name(self):
        return 'ex_script'

    def displayName(self):
        return 'Example script'

    def group(self):
        return 'Example Scripts'

    def groupId(self):
        return 'examplescripts'

    def createInstance(self):
        return type(self)()
        
    def initAlgorithm(self, config=None):
        self.addParameter(
            QgsProcessingParameterVectorLayer(
                'input', 'INPUT', 
                types=[QgsProcessing.TypeVectorAnyGeometry]
            )
        )
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                'output', 'OUTPUT', 
                type=QgsProcessing.TypeVectorPolygon
            )
        )

    def processAlgorithm(self, parameters, context, feedback):
        feedback = QgsProcessingMultiStepFeedback(1, feedback)
        results = {}
        outputs = {}

        # Buffer
        alg_params = {
            'DISSOLVE': False,
            'DISTANCE': 100000,
            'END_CAP_STYLE': 0,  # Round
            'INPUT': parameters['input'],
            'JOIN_STYLE': 0,  # Round
            'MITER_LIMIT': 2,
            'SEGMENTS': 5,
            'OUTPUT': parameters['output']
        }
        outputs['Buffer'] = processing.run(
            'native:buffer', 
            alg_params, 
            context=context, 
            feedback=feedback, 
            is_child_algorithm=True)
        
        results['Output'] = outputs['Buffer']['OUTPUT']
        return results

This is a minimal working example! The official user manual provides a much more extensive script and lots of background information. For example, you might want to group your scripts to form groups of similar algorithms. There’s an optional group() function to achieve this.

The script using @alg decorators

If we use QGIS >= 3.6, we can use a simpler script style (which I’ve written about in “Easy Processing scripts comeback in QGIS 3.6”) using @alg function decorators. (This style is so much shorter that I actually added another step to first reproject the layer.) Note how both the INPUT and OUTPUT parameters defined using @alg.input() are automatically accessible as parameters[‘INPUT’] and parameters[‘OUTPUT’] within the function:

from qgis.processing import alg
from qgis.core import QgsCoordinateReferenceSystem
from qgis.core import QgsProcessing
import processing
  
@alg(name="ex_new", label=alg.tr("Example script w decorators"), group="examplescripts", group_label=alg.tr("Example Scripts"))
# 'INPUT' is the recommended name for the main input parameter
@alg.input(type=alg.SOURCE, name="INPUT", label="Input layer")
# 'OUTPUT' is the recommended name for the main output parameter
@alg.input(type=alg.SINK, name="OUTPUT", label="Output layer")
# For more decorators check https://docs.qgis.org/latest/en/docs/user_manual/processing/scripts.html#the-alg-decorator
def testalg(instance, parameters, context, feedback, inputs):
    """
    Description goes here. (Don't delete this! Removing this comment will cause errors.)
    """
    outputs = {}
             
    # Reproject layer
    alg_params = {
        'INPUT': parameters['INPUT'],
        'OPERATION': '',
        'TARGET_CRS': QgsCoordinateReferenceSystem('EPSG:31287'),
        'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
    }
    outputs['ReprojectLayer'] = processing.run('native:reprojectlayer', 
        alg_params, context=context, feedback=feedback, is_child_algorithm=True)
 
    # Buffer
    alg_params = {
        'DISSOLVE': False,
        'DISTANCE': 10000,
        'END_CAP_STYLE': 0,  # Round
        'INPUT': outputs['ReprojectLayer']['OUTPUT'],
        'JOIN_STYLE': 0,  # Round
        'MITER_LIMIT': 2,
        'SEGMENTS': 5,
        'OUTPUT': parameters['OUTPUT']
    }
    outputs['Buffer'] = processing.run('native:buffer', 
        alg_params, context=context, feedback=feedback, is_child_algorithm=True)
 
    return {"OUTPUT": outputs['Buffer']['OUTPUT']}
    

These are the basics of creating a Processing script that can be called from the Processing toolbox.


PyQGIS 101 is a work in progress. I’d appreciate any feedback, particularly from beginners!

5 comments
  1. Great course Anita. I will be using parts of it immediately. I teach QGIS one-on-one on-line using Zoom.

    • Thank you for the feedback and best of luck with your teaching!

  2. This course has opened up a whole new side of QGIS for me, learnt so much thank you

  3. Marie said:

    Merci beaucoup pour ce cours !!!!

  4. Moox said:

    Great Course Anita, I learned a lot, thank you very much!

    One thing I have struggeld so far ist to add a new field in a processing script: I think it should be possible, I jsut can’t find a solution. Woul much appreciate a tip or a link where I can find help.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: