PyQGIS autoincrement id field

pyqgisqgis-3

Is there a way to autoincrement the id field on a blank layer created in PyQGIS?

For example, this is a blank layer and I want to autoincrement the fid field for anything features that are added.

from qgis.core import QgsVectorLayer, QgsFeature, QgsField, QgsGeometry, QgsPointXY, QgsField, QgsProject

vl = QgsVectorLayer("Polygon", "blank_poly", "memory")
crs = vl.crs()
crs.createFromId(28355) # or whatever
vl.setCrs(crs)
pr = vl.dataProvider()
# Enter editing mode
vl.startEditing()
# add fields
pr.addAttributes( [ QgsField("fid", QVariant.Int),
                QgsField("Label",  QVariant.String,'character', 255),
                QgsField("Source", QVariant.String),
                QgsField("Date", QVariant.Date),
                QgsField("Status", QVariant.String),
                QgsField("Theme", QVariant.String),
                QgsField("SubTheme", QVariant.String),
                QgsField("Comments", QVariant.String,'character', 255),
                QgsField("Area", QVariant.String),
                QgsField("Name", QVariant.String)] )
vl.commitChanges()
# Show in project
QgsProject.instance().addMapLayer(vl)

I have attemped this: Adding feature id column that autoincrements using PyQGIS?

from qgis.core import QgsVectorLayer, QgsFeature, QgsField, QgsGeometry, QgsPointXY, QgsField, QgsProject

vl = QgsVectorLayer("Polygon", "blank_poly", "memory")
crs = vl.crs()
crs.createFromId(28355) # or whatever
vl.setCrs(crs)
pr = vl.dataProvider()
# Enter editing mode
vl.startEditing()
# add fields
pr.addAttributes( [ QgsField("fid", QVariant.Int),
                QgsField("Label",  QVariant.String,'character', 255),
                QgsField("Source", QVariant.String),
                QgsField("Date", QVariant.Date),
                QgsField("Status", QVariant.String),
                QgsField("Theme", QVariant.String),
                QgsField("SubTheme", QVariant.String),
                QgsField("Comments", QVariant.String,'character', 255),
                QgsField("Area", QVariant.String),
                QgsField("Name", QVariant.String)] )

# sequential field id updates
pr.dataProvider().addAttributes(fields)
pr.updateFields()
                
i = 0
with edit(vl):  
    for feature in pr.getFeatures():
        feature.setAttribute(feature.fieldNameIndex('fid'), i)
        i += 1                
                
vl.commitChanges()
# Show in project
QgsProject.instance().addMapLayer(vl)

But I get these errors:

Traceback (most recent call last):
File "C:\PROGRA~1\QGIS32~1.7\apps\Python39\lib\code.py", line 90, in runcode
exec(code, self.locals)
File "", line 1, in
File "", line 23, in
AttributeError: 'QgsVectorDataProvider' object has no attribute 'dataProvider'

I am running on QGIS 3.22.

Best Answer

Firstly, regarding the errors you are getting. Here you are storing the layer Data Provider object in the pr variable:

pr = vl.dataProvider()

Then in these lines:

pr.dataProvider().addAttributes(fields)
pr.updateFields()

You are calling dataProvider() and updateFields() on the QgsVectorDataProvider object, however, these methods come from the QgsMapLayer and QgsVectorLayer classes, so both will cause errors.

If your goal is to update your "fid" field all at once after you have added some features, you can just do this:

with edit(vl):
    for f in vl.getFeatures():
        f['fid'] = f.id()
        # Or if you want your fid to start from zero instead of 1...
        #f['fid'] = f.id()-1
        vl.updateFeature(f)

However, you should know that this approach you are attempting will not have any any effect at the time a feature is added, it will simply update the "fid" column for already existing features.

It sounds to me like what you really want is to set a default value for your "fid" field during the layer creation using pyqgis, so that when any feature is added, the "fid" field is automatically filled with incrementing integers. In that case, you can use the following code:

vl = QgsVectorLayer("Polygon?crs=epsg:28355", "blank_poly", "memory")
pr = vl.dataProvider()
pr.addAttributes( [ QgsField("fid", QVariant.Int),
                QgsField("Label",  QVariant.String,'character', 255),
                QgsField("Source", QVariant.String),
                QgsField("Date", QVariant.Date),
                QgsField("Status", QVariant.String),
                QgsField("Theme", QVariant.String),
                QgsField("SubTheme", QVariant.String),
                QgsField("Comments", QVariant.String,'character', 255),
                QgsField("Area", QVariant.String),
                QgsField("Name", QVariant.String)] )
                
vl.updateFields()

default_val = QgsDefaultValue(
                """
                CASE
                WHEN maximum("fid") is NULL THEN 1
                WHEN "fid" is NULL THEN maximum("fid")+1
                ELSE "fid"
                END
                """,
                applyOnUpdate=True)

vl.setDefaultValueDefinition(vl.fields().lookupField('fid'), default_val)

QgsProject.instance().addMapLayer(vl)
vl.startEditing()

Again, if you want your "fid" to start from zero instead of one, change the expression to:

"""
CASE
WHEN maximum("fid") is NULL THEN 0
WHEN "fid" is NULL THEN maximum("fid")+1
ELSE "fid"
END
"""
Related Question