Skip to content

Commit 7e7faad

Browse files
authored
Merge pull request #7343 from m-kuhn/qgsfunction_handlesnull
Add handlesnull parameter to @qgsfunction
2 parents 8c751f8 + 3c03da2 commit 7e7faad

File tree

3 files changed

+39
-8
lines changed

3 files changed

+39
-8
lines changed

doc/index.dox

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ You can also <a href="https://qgis.org/downloads/qgis-api-doc.zip">download</a>
2323
this documentation or a <a href="qgis.qch">Qt help file</a>
2424
for offline use.
2525

26-
A Python version of the documentation is available
27-
<a href="https://qgis.org/pyqgis/master">here</a>.
26+
There is also a <a href="https://qgis.org/pyqgis/master">Python version of
27+
the documentation</a> available.
2828

2929
\section index_APIStability Earlier versions of the API
3030

python/core/additions/qgsfunction.py

+23-5
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
import string
2323
from builtins import str
2424
from qgis.PyQt.QtCore import QCoreApplication
25-
from qgis._core import QgsExpressionFunction, QgsExpression, QgsMessageLog, QgsFeatureRequest
25+
from qgis._core import QgsExpressionFunction, QgsExpression, QgsMessageLog, QgsFeatureRequest, Qgis
2626

2727

2828
def register_function(function, arg_count, group, usesgeometry=False,
29-
referenced_columns=[QgsFeatureRequest.ALL_ATTRIBUTES], **kwargs):
29+
referenced_columns=[QgsFeatureRequest.ALL_ATTRIBUTES], handlesnull=False, **kwargs):
3030
"""
3131
Register a Python function to be used as a expression function.
3232
@@ -50,19 +50,21 @@ def myfunc(values, *args):
5050
:param function:
5151
:param arg_count:
5252
:param group:
53-
:param usesgeometry:
53+
:param usesgeometry:
54+
:param handlesnull: Needs to be set to True if this function does not always return NULL if any parameter is NULL. Default False.
5455
:return:
5556
"""
5657

5758
class QgsPyExpressionFunction(QgsExpressionFunction):
5859

5960
def __init__(self, func, name, args, group, helptext='', usesGeometry=True,
60-
referencedColumns=QgsFeatureRequest.ALL_ATTRIBUTES, expandargs=False):
61+
referencedColumns=QgsFeatureRequest.ALL_ATTRIBUTES, expandargs=False, handlesNull=False):
6162
QgsExpressionFunction.__init__(self, name, args, group, helptext)
6263
self.function = func
6364
self.expandargs = expandargs
6465
self.uses_geometry = usesGeometry
6566
self.referenced_columns = referencedColumns
67+
self.handles_null = handlesNull
6668

6769
def func(self, values, context, parent, node):
6870
feature = None
@@ -90,6 +92,9 @@ def usesGeometry(self, node):
9092
def referencedColumns(self, node):
9193
return self.referenced_columns
9294

95+
def handlesNull(self):
96+
return self.handles_null
97+
9398
helptemplate = string.Template("""<h3>$name function</h3><br>$doc""")
9499
name = kwargs.get('name', function.__name__)
95100
helptext = kwargs.get('helpText') or function.__doc__ or ''
@@ -119,7 +124,7 @@ def referencedColumns(self, node):
119124
function.__name__ = name
120125
helptext = helptemplate.safe_substitute(name=name, doc=helptext)
121126
f = QgsPyExpressionFunction(function, name, arg_count, group, helptext, usesgeometry, referenced_columns,
122-
expandargs)
127+
expandargs, handlesnull)
123128

124129
# This doesn't really make any sense here but does when used from a decorator context
125130
# so it can stay.
@@ -132,6 +137,19 @@ def qgsfunction(args='auto', group='custom', **kwargs):
132137
"""
133138
Decorator function used to define a user expression function.
134139
140+
:param args: Number of parameters, set to 'auto' to accept a variable length of parameters.
141+
:param group: The expression group to which this expression should be added.
142+
:param \**kwargs:
143+
See below
144+
145+
:Keyword Arguments:
146+
* *referenced_columns* (``list``) --
147+
An array of field names on which this expression works. Can be set to ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. By default empty.
148+
* *usesgeometry* (``bool``) --
149+
Defines if this expression requires the geometry. By default False.
150+
* *handlesnull* (``bool``) --
151+
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.
152+
135153
Example:
136154
@qgsfunction(2, 'test'):
137155
def add(values, feature, parent):

tests/src/python/test_qgsexpression.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from qgis.PyQt.QtCore import QVariant
1818
from qgis.testing import unittest
1919
from qgis.utils import qgsfunction
20-
from qgis.core import QgsExpression, QgsFeatureRequest
20+
from qgis.core import QgsExpression, QgsFeatureRequest, QgsExpressionContext, NULL
2121

2222

2323
class TestQgsExpressionCustomFunctions(unittest.TestCase):
@@ -67,6 +67,11 @@ def no_referenced_columns_set(values, feature, parent):
6767
def referenced_columns_set(values, feature, parent):
6868
return 2
6969

70+
@qgsfunction(args=-1, group='testing', register=False, handlesnull=True)
71+
def null_mean(values, feature, parent):
72+
vals = [val for val in values if val != NULL]
73+
return sum(vals) / len(vals)
74+
7075
def tearDown(self):
7176
QgsExpression.unregisterFunction('testfun')
7277

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

169+
def testHandlesNull(self):
170+
context = QgsExpressionContext()
171+
QgsExpression.registerFunction(self.null_mean)
172+
exp = QgsExpression('null_mean(1, 2, NULL, 3)')
173+
result = exp.evaluate(context)
174+
self.assertFalse(exp.hasEvalError())
175+
self.assertEqual(result, 2)
176+
164177
def testCantOverrideBuiltinsWithUnregister(self):
165178
success = QgsExpression.unregisterFunction("sqrt")
166179
self.assertFalse(success)

0 commit comments

Comments
 (0)