PyQGIS – Fix ‘Could Not Load Source Layer for INPUT: Invalid Value’ Error

errorinputpyqgisqgis-pluginsqgis-processing

I'm trying to implement a processing plugins in QGIS 3.28.9, but when I hit Run, it gives me a message that my polygon layer is not a polygon layer, but, it is a polygon, in fact. Can someone help to fix this problem?

plugin error

my polygon test layer

code:

def processAlgorithm(self, parameters, context, feedback):
    
    source = self.parameterAsSource(parameters, self.INPUT, context)

    if source is None or source.wkbType() != QgsWkbTypes.PolygonGeometry:
        raise QgsProcessingException('Invalid input source. Expected a polygon layer.') 


    feedback.setProgressText('Converting polygons to lines...')
    converted_lines = processing.run("qgis:polygonstolines", {
        'INPUT': source,
        'OUTPUT': 'memory:'  # Convert to a temporary memory layer
    })['OUTPUT']

    # Get the output sink
    sink = self.parameterAsSink(parameters, self.OUTUPUT, context,
                                converted_lines.fields(), QgsProcessing.TypeVectorLine)
    if sink is None:
        raise QgsProcessingException('Invalid output sink')
    
    # Add converted lines to the output sink
    sink.addFeatures(converted_lines.getFeatures())
    
    return {self.OUTPUT: sink}

Best Answer

Your layer has a MultiPolygon geometry type. Therefore, the source.wkbType() test will return WkbType.MultiPolygon which is why the processing exception is being raised.

Change your conditional statement to:

if source is None or QgsWkbTypes.geometryType(source.wkbType()) != QgsWkbTypes.PolygonGeometry:
    raise QgsProcessingException('Invalid input source. Expected a polygon layer.')

I assume that you are already passing a list of types to the QgsProcessingParameterFeatureSource constructor so that the input layer dropdown box is filtered e.g.

def initAlgorithm(self, config=None):
    self.addParameter(QgsProcessingParameterFeatureSource(
        self.INPUT,
        self.tr("Input polygon layer"),
        [QgsProcessing.TypeVectorPolygon]))

But, since it is still possible to select a different input type when using browse or select file...

enter image description here

...a check of the input geometry type is indeed prudent.

However, I suggest that an alternative (and perhaps better practice) way to run the input check is to re-implement and override the checkParameterValues method of the QgsProcessingAlgorithm class and do the check there.

def checkParameterValues(self, parameters, context):
    source = self.parameterAsSource(parameters, self.INPUT, context)

    if source is None or QgsWkbTypes.geometryType(source.wkbType()) != QgsWkbTypes.PolygonGeometry:
        return False, self.tr('Invalid input source. Expected a polygon layer.')

    return super().checkParameterValues(parameters, context)

Here, we can implement our own logic and return False and a helpful message string if the check fails.

Following this recommendation given in the docs:

Overridden implementations should also check this base class implementation.

we can use super() which returns an object which represents the base or parent class of a subclass (in this case QgsProcessingAlgorithm) and call the default implementation of checkParameterValues(), returning the result- as in this example.

Now, if you try to run the algorithm with a non-polygon layer input, you will see this:

enter image description here

Edit: There are a few other issues with your code. You have not passed is_child_algorithm=True to your processing.run() call. This is required since the "native:polygonstolines" alg is being run inside a parent algorithm.

As for the persistent error you were getting, I found that to get the child algorithm to accept the parent input, I had to call the materialize() method on the QgsProcessingFeatureSource object to return an in memory copy as a QgsVectorLayer object.

Below is a complete working example. By the way, I guess that you will be adding some more processing steps because, as it is, this whole script is just a wrapper around an existing algorithm. If you add more steps you may want to use QgsProcessingMultiStepFeedback. You can see an example in my recent answer here.

from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing, QgsProcessingAlgorithm,
                        QgsProcessingParameterFeatureSource,
                        QgsProcessingParameterVectorDestination,
                        QgsWkbTypes, QgsFeatureRequest)
                        
import processing
                       
class ConvertPolygons(QgsProcessingAlgorithm):
    INPUT = 'INPUT'
    OUTPUT = 'OUTPUT'
 
    def __init__(self):
        super().__init__()
 
    def name(self):
        return "exportarpolygonos"
     
    def tr(self, text):
        return QCoreApplication.translate("exportarpolygonos", text)
         
    def displayName(self):
        return self.tr("Exportar Polygonos")
 
    def group(self):
        return self.tr("Examples")
 
    def groupId(self):
        return "examples"
 
    def shortHelpString(self):
        return self.tr("Exportar Polygonos")
 
    def helpUrl(self):
        return "https://qgis.org"
         
    def createInstance(self):
        return type(self)()
        
    def checkParameterValues(self, parameters, context):
        source = self.parameterAsSource(parameters, self.INPUT, context)

        if source is None or QgsWkbTypes.geometryType(source.wkbType()) != QgsWkbTypes.PolygonGeometry:
            return False, self.tr('Invalid input source. Expected a polygon layer.')

        return super().checkParameterValues(parameters, context)
   
    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterFeatureSource(
            self.INPUT,
            self.tr("Input polygons"),
            [QgsProcessing.TypeVectorPolygon]))
            
        self.addParameter(QgsProcessingParameterVectorDestination(
                self.OUTPUT,
                self.tr('Output lines')))
 
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context).materialize(QgsFeatureRequest())
        
        results = {}
        outputs = {}
        
        feedback.setProgressText('Converting polygons to lines...')
        
        outputs['converted_lines'] = processing.run("native:polygonstolines",
                        {'INPUT':source, 'OUTPUT':parameters['OUTPUT']},
                        context=context,
                        feedback=feedback,
                        is_child_algorithm=True)
        
        results['OUTPUT'] = outputs['converted_lines']['OUTPUT']
        
        return results
Related Question