New QGIS 2.0 API.
What has changed and what it means for you

Published: 13 June 2013

QGIS 2.0 has under gone some radical changes for its upcoming release. Some of these major changes relate to the API, both C++ and Python. Something that always comes up when trying to improve an API is the chance that you might have to remove the old way of doing something so that things are cleaner and improved for the future. These changes will always break existing working code and can be a burden for a little while until everything is adjusted. A little pain for a lot of gain is the way I like to think about it. I will also add that breaking API so radically isn't something that we, as developers, like to do or take lightly, but sometimes it has to be done.

This post is a summary of some of the new APIs, what has changed, and what it means to you as a user.

New Vector API (Iterator pattern)

We would like to introduce multithreaded rendering in the future and the old API for getting a set of features from the provider wasn't going to cut it in a multithreaded environment. In order to allow something that will be more threadable then the old select model we changed to a iterator pattern.

The old used to look like this:

layer.select()
for feature in layer:
    print feature

select() wouldn't return anything rather it would tell the provider of the layer to get ready to return some features when asked e.g the for loop. Want to thread that? Nope. Not going to happen.

The solution:

Create a method that returns an iterator that each thread can loop over on their own without changing the result of the other.

features = layer.getFeatures()
for feature in features:
    print feature

if you look at layer.getFeatures() you can see it returns a QgsFeatureIterator

>>> layer.getFeatures()
<qgis.core.QgsFeatureIterator object at 0x1A05F810>

QgsFeatureIterator has a next() method, just like a normal Python iterator, which you can call to get the next feature, it will just serve them up as you need them.

>>> features = layer.getFeatures()
>>> features.next()
<qgis.core.QgsFeature object at 0x1A062CD8>
>>> features.next()
<qgis.core.QgsFeature object at 0x1A062C48>
>>> features.next()
<qgis.core.QgsFeature object at 0x1A062C00>
>>> features.close()

So in the future (you can't do this yet due to some other restrictions):

>>> features = layer.getFeatures()
>>> features2 = layer.getFeatures()
>>> features.next().id()
1L
>>> features.next().id()
2L
>>> features2.next().id()
1L

Nifty!

New Vector API (Fields)

Something else that changed was how attributes are accessed.

This is the old style:

>>> index = layer.fieldNameIndex("yourcolumn")
>>> feature.attributeMap()[index].toString()
"Hello World"

That is a big pain in the butt just to get a value from a field.

The new method now uses a list of attributes on the feature itself.

>>> feature[5]
"Hello World"
>>> feature["yourcolumn"]
"Hello World"
You can even access them like attributes.
>>> feature.yourcolumn
"Hello World"
**Be careful with accessing fields via the attribute magic. If you have a `id` field `feature.id` will return the feature id method not the field named id. In fact any method with the same name as any [QgsFeature](http://www.qgis.org/api/classQgsFeature.html) methods will return the method rather then the field value. I like this magic but use it wisely.**

SIP API v2

This is the most recent change that has caused all of the 1.8 plugins to no longer work in 2.0. A bit of background: PyQt4 and pyqgis use sip to define their Python API. There are two versions of SIP V1 and V2. Using SIP V1 PyQt, and therefor pyqgis, will take and return QVariant and QString objects in Python.

So:

>>> feature["yourcolumn"]
<PyQt4.QtCore.QVariant object at 0x026AD5E0>

Well that is fine but to get a string value you had to do this:

>>> feature["yourcolumn"].toString()
"Hello World"

but that isn't a normal Python string. It's a QString. QStrings can't be used in normal Python methods so you have to do str(feature["yourcolumn"].toString()). Gross!

With the change to SIP V2 QVariant and QString are now auto converted to native Python types without any extra work.

>>> type(f["Name"])
<type 'unicode'>

Good stuff.

If you are looking for a conversion guide check out: http://hub.qgis.org/wiki/quantum-gis/PythonpluginAPIchangesfrom18to_20

The QGIS plugin repository can house both 1.8 and 2.0 plugins under the same plugin name, and the plugin installer will only download the version compatible with the QGIS version you are using.

The SIP version update was a hard one to make. On one hand we could just leave it the way it was with backwards compatibility and all the messy str(variant.toString()) stuff; OR we can make the switch now while everyone is migrating to the new vector API and have a cleaner API for the future. I picked the later. Once we switch to Python 3 at some stage in the future SIP v2 is the default so it would have broken all the plugins at that point anyway.

I can tell you that I did lose some sleep over if I should push the SIP update or not, wondering what impact it could have on plugin authors and the project in general. In the end I stand by our decision to make the update in QGIS 2.0.

Hopefully these API changes won't cause to much grief for plugin authors and the API is now better to work with.

Fine. But why I do care as a user?

All this means that when QGIS 2.0 comes out you may be missing some plugins that you used to have in 1.8 until all the plugins are migrated. If you have the skills to help updating a plugin please let the plugin author know so you can make their life easier. If you have the ability to fund a plugins update that would be excellent.


Discussion

blog comments powered by Disqus