Skip to content


Merge pull request #7343 from m-kuhn/qgsfunction_handlesnull
Browse files Browse the repository at this point in the history
Add handlesnull parameter to @qgsfunction
  • Loading branch information
m-kuhn committed Jul 2, 2018
2 parents 8c751f8 + 3c03da2 commit 7e7faad
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 8 deletions.
4 changes: 2 additions & 2 deletions doc/index.dox
Expand Up @@ -23,8 +23,8 @@ You can also <a href="">download</a>
this documentation or a <a href="qgis.qch">Qt help file</a>
for offline use.

A Python version of the documentation is available
<a href="">here</a>.
There is also a <a href="">Python version of
the documentation</a> available.

\section index_APIStability Earlier versions of the API

Expand Down
28 changes: 23 additions & 5 deletions python/core/additions/
Expand Up @@ -22,11 +22,11 @@
import string
from builtins import str
from qgis.PyQt.QtCore import QCoreApplication
from qgis._core import QgsExpressionFunction, QgsExpression, QgsMessageLog, QgsFeatureRequest
from qgis._core import QgsExpressionFunction, QgsExpression, QgsMessageLog, QgsFeatureRequest, Qgis

def register_function(function, arg_count, group, usesgeometry=False,
referenced_columns=[QgsFeatureRequest.ALL_ATTRIBUTES], **kwargs):
referenced_columns=[QgsFeatureRequest.ALL_ATTRIBUTES], handlesnull=False, **kwargs):
Register a Python function to be used as a expression function.
Expand All @@ -50,19 +50,21 @@ def myfunc(values, *args):
:param function:
:param arg_count:
:param group:
:param usesgeometry:
:param usesgeometry:
:param handlesnull: Needs to be set to True if this function does not always return NULL if any parameter is NULL. Default False.

class QgsPyExpressionFunction(QgsExpressionFunction):

def __init__(self, func, name, args, group, helptext='', usesGeometry=True,
referencedColumns=QgsFeatureRequest.ALL_ATTRIBUTES, expandargs=False):
referencedColumns=QgsFeatureRequest.ALL_ATTRIBUTES, expandargs=False, handlesNull=False):
QgsExpressionFunction.__init__(self, name, args, group, helptext)
self.function = func
self.expandargs = expandargs
self.uses_geometry = usesGeometry
self.referenced_columns = referencedColumns
self.handles_null = handlesNull

def func(self, values, context, parent, node):
feature = None
Expand Down Expand Up @@ -90,6 +92,9 @@ def usesGeometry(self, node):
def referencedColumns(self, node):
return self.referenced_columns

def handlesNull(self):
return self.handles_null

helptemplate = string.Template("""<h3>$name function</h3><br>$doc""")
name = kwargs.get('name', function.__name__)
helptext = kwargs.get('helpText') or function.__doc__ or ''
Expand Down Expand Up @@ -119,7 +124,7 @@ def referencedColumns(self, node):
function.__name__ = name
helptext = helptemplate.safe_substitute(name=name, doc=helptext)
f = QgsPyExpressionFunction(function, name, arg_count, group, helptext, usesgeometry, referenced_columns,
expandargs, handlesnull)

# This doesn't really make any sense here but does when used from a decorator context
# so it can stay.
Expand All @@ -132,6 +137,19 @@ def qgsfunction(args='auto', group='custom', **kwargs):
Decorator function used to define a user expression function.
:param args: Number of parameters, set to 'auto' to accept a variable length of parameters.
:param group: The expression group to which this expression should be added.
:param \**kwargs:
See below
:Keyword Arguments:
* *referenced_columns* (``list``) --
An array of field names on which this expression works. Can be set to ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. By default empty.
* *usesgeometry* (``bool``) --
Defines if this expression requires the geometry. By default False.
* *handlesnull* (``bool``) --
Defines if this expression has custom handling for NULL values. If False, the result will always be NULL as soon as any parameter is NULL. False by default.
@qgsfunction(2, 'test'):
def add(values, feature, parent):
Expand Down
15 changes: 14 additions & 1 deletion tests/src/python/
Expand Up @@ -17,7 +17,7 @@
from qgis.PyQt.QtCore import QVariant
from qgis.testing import unittest
from qgis.utils import qgsfunction
from qgis.core import QgsExpression, QgsFeatureRequest
from qgis.core import QgsExpression, QgsFeatureRequest, QgsExpressionContext, NULL

class TestQgsExpressionCustomFunctions(unittest.TestCase):
Expand Down Expand Up @@ -67,6 +67,11 @@ def no_referenced_columns_set(values, feature, parent):
def referenced_columns_set(values, feature, parent):
return 2

@qgsfunction(args=-1, group='testing', register=False, handlesnull=True)
def null_mean(values, feature, parent):
vals = [val for val in values if val != NULL]
return sum(vals) / len(vals)

def tearDown(self):

Expand Down Expand Up @@ -161,6 +166,14 @@ def testReferencedColumnsSet(self):
exp = QgsExpression('referenced_columns_set()')
self.assertEqual(set(exp.referencedColumns()), set(['a', 'b']))

def testHandlesNull(self):
context = QgsExpressionContext()
exp = QgsExpression('null_mean(1, 2, NULL, 3)')
result = exp.evaluate(context)
self.assertEqual(result, 2)

def testCantOverrideBuiltinsWithUnregister(self):
success = QgsExpression.unregisterFunction("sqrt")
Expand Down

0 comments on commit 7e7faad

Please sign in to comment.