3

I have about 40 layers with attribute tables that have many NULL columns. I downloaded the plugin "Delete NULL Fields from Vector Data", which works. I'd like to automate it using the QGIS Python API. The script I tried returned the following:

Traceback (most recent call last): File "C:\PROGRA~1\QGIS32~2.3\apps\Python39\lib\code.py", line 90, in runcode exec(code, self.locals) File "", line 1, in File "", line 17, in File "C:\PROGRA~1/QGIS32~2.3/apps/qgis/./python/plugins\processing\tools\general.py", line 108, in run return Processing.runAlgorithm(algOrName, parameters, onFinish, feedback, context) File "C:\PROGRA~1/QGIS32~2.3/apps/qgis/./python/plugins\processing\core\Processing.py", line 169, in runAlgorithm raise QgsProcessingException(msg) _core.QgsProcessingException: Error: Algorithm qgis:Delete_Null_Fields not found

This is my script:

# specify output folder and new filename
 output_folder = '/path/to/output/folder'
 new_name = layer.name() + '_nonull'
# get a list of all layers in the project
 layers = QgsProject.instance().mapLayers().values()
 
 # loop through each layer and delete null fields
 for layer in layers:
 
 # run the plugin
 alg_params = {
 'INPUT': layer,
 'FIELDS': None,
 'OUTPUT': output_folder + '/' + new_name + '.shp'
 }
 nonull_layer = processing.run('qgis:Delete_Null_Fields', alg_params)['OUTPUT']
 
 # add the new layer to the project
 QgsProject.instance().addMapLayer(nonull_layer, False)
 
 # save the new layer to a file
 file_info = QFileInfo(layer.source())
 output_path = file_info.path() + '/' + new_name + '.shp'
 QgsVectorLayer.exportLayer(nonull_layer, output_path, 'ESRI Shapefile')

From what I understood, the API was unable to find the plugin from the name I gave. I got it from the dev's git page. I also tried initialising it first using the dev's script before my code starts:

def classFactory(iface): # pylint: disable=invalid-name
 """Load Delete_Null_Fields class from file Delete_Null_Fields.
 :param iface: A QGIS interface instance.
 :type iface: QgsInterface
 """
 #
 from .delete_null_fields import Delete_Null_Fields
 return Delete_Null_Fields(iface)

But it didn't work. Importantly, I am very new to programming. What is wrong with the code and how could I achieve my batch processing?

PolyGeo
65.5k29 gold badges115 silver badges349 bronze badges
asked Apr 28, 2023 at 15:25
1
  • 1
    This plugin is a GUI plugin, not a processing plugin. Commented Apr 29, 2023 at 0:12

1 Answer 1

3

It looks like you are trying to run a gui based Python plugin as a processing algorithm, which simply isn't going to work. To use a processing.run() call like this, you must pass the method an actual processing algorithm which has been added to the processing toolbox by a processing provider (either native or 3rd party).

While it is technically possible to programmatically access the classes and methods of an installed Python plugin, for example the following will open the dialog of the plugin:

plugin = qgis.utils.plugins['delete_null_fields']
# print(plugin)
plugin.run()

how useful this actually is depends very much on the structure of the plugin.

In this instance, to achieve what you want, I think it is easier to write our own script.

Fortunately, we can write a fairly quick and simple script containing a function which will create a copy of the input layer, delete any fields with all NULL values and save the output to a specified folder, leaving the original input layer unchanged. We can then iterate over all the project layers, skipping any non-vector layers, and call our function, passing the layer object, the output path and output format as arguments.

Paste the script below into a new editor in the Python console, edit the output path and click run.

import os
# Define a function with a few parameters to do what what we want
def removeNullColumns(layer, output_dir, file_format):
 # Create an in memory copy of the input layer
 temp_layer = layer.materialize(QgsFeatureRequest())
 # Compile list of fields with all NULL values
 fld_ids = []
 for fld in temp_layer.fields():
 contains_values = False
 for ft in temp_layer.getFeatures():
 if ft[fld.name()] != NULL:
 contains_values = True
 break
 if not contains_values:
 fld_ids.append(temp_layer.fields().lookupField(fld.name()))
 
 # Delete the null fields and update the temp layer fileds
 temp_layer.dataProvider().deleteAttributes(fld_ids)
 temp_layer.updateFields()
 
 # Save the temp layer with input layer name appended with '_no_null'
 # to the specified output folder in the specified format
 save_params = {'INPUT':temp_layer,
 'OUTPUT':os.path.join(output_dir, f'{layer.name()}_no_null{file_format}')}
 processing.run("native:savefeatures", save_params)
 # If you want to load all the output layer, replace the line above with:
 # processing.runAndLoadResults("native:savefeatures", save_params)
# Define path to your output folder location
destination_folder = '/path/to/output/folder'# ******************EDIT THIS LINE
# Specify the desired output format. If you want Geopackage change to '.gpkg'
output_format = '.shp'
# Iterate over loaded layers, skipping any non-vector layers
# and call the removeNullColumns() function for each one
for lyr in QgsProject.instance().mapLayers().values():
 if not isinstance(lyr, QgsVectorLayer):
 continue
 removeNullColumns(lyr, destination_folder, output_format)
print('Done')
answered Apr 29, 2023 at 4:04

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.