mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-15 00:07:25 -05:00
Fix qgsfunction decorator args handling
This commit is contained in:
parent
d3d8ba1a30
commit
947931ad2b
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
***************************************************************************
|
||||
qgsfunction.py
|
||||
@ -26,30 +24,31 @@ from qgis.PyQt.QtCore import QCoreApplication
|
||||
from qgis._core import QgsExpressionFunction, QgsExpression, QgsMessageLog, QgsFeatureRequest, Qgis
|
||||
|
||||
|
||||
def register_function(function, arg_count, group, usesgeometry=False,
|
||||
referenced_columns=[QgsFeatureRequest.ALL_ATTRIBUTES], handlesnull=False, **kwargs):
|
||||
def register_function(
|
||||
function,
|
||||
group,
|
||||
usesgeometry=False,
|
||||
referenced_columns=[QgsFeatureRequest.ALL_ATTRIBUTES],
|
||||
handlesnull=False,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Register a Python function to be used as a expression function.
|
||||
|
||||
Functions should take (values, feature, parent) as args:
|
||||
The function signature may contains special parameters:
|
||||
- context: the QgsExpressionContext-related to the current evaluation
|
||||
- feature: the QgsFeature-related to the current evaluation
|
||||
- parent: the QgsExpressionFunction parent
|
||||
|
||||
Example:
|
||||
def myfunc(values, feature, parent):
|
||||
pass
|
||||
If those parameters are present in the function signature, they will be automatically passed to the function,
|
||||
without the need to specify them in the expression.
|
||||
|
||||
They can also shortcut naming feature and parent args by using *args
|
||||
if they are not needed in the function.
|
||||
|
||||
Example:
|
||||
def myfunc(values, *args):
|
||||
pass
|
||||
If the only other parameter in the signature is called "values", parameters will be passed as a list.
|
||||
Otherwise, parameters will be expanded in the parameter list.
|
||||
|
||||
Functions should return a value compatible with QVariant
|
||||
|
||||
Eval errors can be raised using parent.setEvalErrorString("Error message")
|
||||
|
||||
:param function:
|
||||
:param arg_count:
|
||||
:param group:
|
||||
: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.
|
||||
@ -57,12 +56,20 @@ def register_function(function, arg_count, group, usesgeometry=False,
|
||||
"""
|
||||
|
||||
class QgsPyExpressionFunction(QgsExpressionFunction):
|
||||
|
||||
def __init__(self, func, name, args, group, helptext='', usesGeometry=True,
|
||||
referencedColumns=QgsFeatureRequest.ALL_ATTRIBUTES, expandargs=False, handlesNull=False):
|
||||
QgsExpressionFunction.__init__(self, name, args, group, helptext)
|
||||
def __init__(
|
||||
self,
|
||||
func,
|
||||
name,
|
||||
group,
|
||||
helptext="",
|
||||
usesGeometry=True,
|
||||
referencedColumns=QgsFeatureRequest.ALL_ATTRIBUTES,
|
||||
handlesNull=False,
|
||||
paramsAsList=False,
|
||||
):
|
||||
QgsExpressionFunction.__init__(self, name, -1, group, helptext)
|
||||
self.function = func
|
||||
self.expandargs = expandargs
|
||||
self.params_as_list = paramsAsList
|
||||
self.uses_geometry = usesGeometry
|
||||
self.referenced_columns = referencedColumns
|
||||
self.handles_null = handlesNull
|
||||
@ -71,21 +78,26 @@ def register_function(function, arg_count, group, usesgeometry=False,
|
||||
feature = None
|
||||
if context:
|
||||
feature = context.feature()
|
||||
|
||||
try:
|
||||
if self.expandargs:
|
||||
values.append(feature)
|
||||
values.append(parent)
|
||||
if inspect.getfullargspec(self.function).args[-1] == 'context':
|
||||
values.append(context)
|
||||
return self.function(*values)
|
||||
else:
|
||||
if inspect.getfullargspec(self.function).args[-1] == 'context':
|
||||
self.function(values, feature, parent, context)
|
||||
return self.function(values, feature, parent)
|
||||
parameters = inspect.signature(self.function).parameters
|
||||
kwvalues = {}
|
||||
|
||||
# Handle special parameters
|
||||
# those will not be inserted in the parameter list
|
||||
# if they are present in the function signature
|
||||
if "context" in parameters:
|
||||
kwvalues["context"] = context
|
||||
if "feature" in parameters:
|
||||
kwvalues["feature"] = feature
|
||||
if "parent" in parameters:
|
||||
kwvalues["parent"] = parent
|
||||
|
||||
if self.params_as_list:
|
||||
return self.function(values, **kwvalues)
|
||||
return self.function(*values, **kwvalues)
|
||||
except Exception as ex:
|
||||
tb = traceback.format_exception(None, ex, ex.__traceback__)
|
||||
formatted_traceback = ''.join(tb)
|
||||
formatted_traceback = "".join(tb)
|
||||
formatted_exception = f"{ex}:<pre>{formatted_traceback}</pre>"
|
||||
parent.setEvalErrorString(formatted_exception)
|
||||
return None
|
||||
@ -99,53 +111,42 @@ def register_function(function, arg_count, group, usesgeometry=False,
|
||||
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 ''
|
||||
helptemplate = string.Template("<h3>$name function</h3><br>$doc")
|
||||
name = kwargs.get("name", function.__name__)
|
||||
helptext = kwargs.get("helpText") or function.__doc__ or ""
|
||||
helptext = helptext.strip()
|
||||
expandargs = False
|
||||
|
||||
if arg_count == "auto":
|
||||
# Work out the number of args we need.
|
||||
# Number of function args - 2. The last two args are always feature, parent.
|
||||
args = inspect.getfullargspec(function).args
|
||||
number = len(args)
|
||||
arg_count = number - 2
|
||||
if args[-1] == 'context':
|
||||
arg_count -= 1
|
||||
expandargs = True
|
||||
|
||||
register = kwargs.get('register', True)
|
||||
register = kwargs.get("register", True)
|
||||
if register and QgsExpression.isFunctionName(name):
|
||||
if not QgsExpression.unregisterFunction(name):
|
||||
msgtitle = QCoreApplication.translate("UserExpressions", "User expressions")
|
||||
msg = QCoreApplication.translate("UserExpressions",
|
||||
"The user expression {0} already exists and could not be unregistered.").format(
|
||||
name)
|
||||
msg = QCoreApplication.translate(
|
||||
"UserExpressions", "The user expression {0} already exists and could not be unregistered."
|
||||
).format(name)
|
||||
QgsMessageLog.logMessage(msg + "\n", msgtitle, Qgis.Warning)
|
||||
return None
|
||||
|
||||
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.
|
||||
# Legacy: if args was not 'auto', parameters were passed as a list
|
||||
params_as_list = kwargs.get("params_as_list", kwargs.get("args", "auto") != "auto")
|
||||
f = QgsPyExpressionFunction(
|
||||
function, name, group, helptext, usesgeometry, referenced_columns, handlesnull, params_as_list
|
||||
)
|
||||
|
||||
if register:
|
||||
QgsExpression.registerFunction(f)
|
||||
return f
|
||||
|
||||
|
||||
def qgsfunction(args='auto', group='custom', **kwargs):
|
||||
def qgsfunction(args="auto", group="custom", **kwargs):
|
||||
r"""
|
||||
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 args: DEPRECATED since QGIS 3.32. Use the "params_as_list" keyword argument instead if you want to pass parameters as a list.
|
||||
: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.
|
||||
@ -153,24 +154,36 @@ def qgsfunction(args='auto', group='custom', **kwargs):
|
||||
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.
|
||||
* *params_as_list* (``bool``) \since QGIS 3.32 --
|
||||
Defines if the parameters are passed to the function as a list, or if they are expanded. Default to False.
|
||||
|
||||
Example:
|
||||
@qgsfunction(2, 'test'):
|
||||
def add(values, feature, parent):
|
||||
pass
|
||||
Examples:
|
||||
|
||||
Will create and register a function in QgsExpression called 'add' in the
|
||||
'test' group that takes two arguments.
|
||||
@qgsfunction(group="custom")
|
||||
def myfunc(values, feature, parent):
|
||||
return values + [feature.id()]
|
||||
|
||||
or not using feature and parent:
|
||||
This register a function called "myfunc" in the "custom" group. It can then be called with any number of parameters
|
||||
which will be passed as a list to the function. From the function, it is possible to access the feature and the parent
|
||||
|
||||
Example:
|
||||
@qgsfunction(2, 'test'):
|
||||
def add(values, *args):
|
||||
pass
|
||||
>>> myfunc("a", "b", "c")
|
||||
["a", "b", "c", 1]
|
||||
|
||||
|
||||
@qgsfunction(group="custom")
|
||||
def myfunc2(val1, val2, context):
|
||||
return val1 + val2
|
||||
|
||||
This register a function called "myfunc2" in the "custom" group. It expects exactly two parameters, val1 and val2
|
||||
From the function, it is possible to access the feature and the parent
|
||||
|
||||
>>> myfunc2(40, 2)
|
||||
42
|
||||
"""
|
||||
|
||||
kwargs["args"] = args
|
||||
|
||||
def wrapper(func):
|
||||
return register_function(func, args, group, **kwargs)
|
||||
return register_function(func, group, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user