Skip to content
Snippets Groups Projects
Commit 4045187d authored by David Schäfer's avatar David Schäfer
Browse files

Merge branch 'config_func' into 'develop'

Allow function arguments in config syntax

See merge request !641
parents 8668f59e 7de05513
No related branches found
No related tags found
3 merge requests!685Release 2.4,!684Release 2.4,!641Allow function arguments in config syntax
Pipeline #161258 passed with stages
in 7 minutes and 38 seconds
...@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-3.0-or-later ...@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
[List of commits](https://git.ufz.de/rdm-software/saqc/-/compare/v2.3.0...develop) [List of commits](https://git.ufz.de/rdm-software/saqc/-/compare/v2.3.0...develop)
### Added ### Added
- Methods `logicalAnd` and `logicalOr` - Methods `logicalAnd` and `logicalOr`
- `Flags` supports slicing and column selection with `list` or a `pd.Index`. - `Flags` supports slicing and column selection with `list` or a `pd.Index`
### Changed ### Changed
- Deprecate `interpolate`, `linear` and `shift` in favor of `align` - Deprecate `interpolate`, `linear` and `shift` in favor of `align`
- Rename `interplateInvalid` to `interpolate` - Rename `interplateInvalid` to `interpolate`
...@@ -18,6 +18,7 @@ SPDX-License-Identifier: GPL-3.0-or-later ...@@ -18,6 +18,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
### Removed ### Removed
- Parameter `limit` from `align` - Parameter `limit` from `align`
### Fixed ### Fixed
- `func` arguments in text configurations were not parsed correctly
- fail on duplicated arguments to test methods - fail on duplicated arguments to test methods
## [2.3.0](https://git.ufz.de/rdm-software/saqc/-/tags/v2.3.0) - 2023-01-17 ## [2.3.0](https://git.ufz.de/rdm-software/saqc/-/tags/v2.3.0) - 2023-01-17
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import ast import ast
import importlib
import numpy as np
from saqc.core.register import FUNC_MAP from saqc.core.register import FUNC_MAP
from saqc.parsing.environ import ENVIRONMENT from saqc.parsing.environ import ENVIRONMENT
...@@ -127,18 +130,33 @@ class ConfigFunctionParser(ast.NodeVisitor): ...@@ -127,18 +130,33 @@ class ConfigFunctionParser(ast.NodeVisitor):
key, value = node.arg, node.value key, value = node.arg, node.value
check_tree = True check_tree = True
imports = {}
if key == "func": if key == "func":
visitor = ConfigExpressionParser(value) if (isinstance(value, ast.Name) and value.id in ENVIRONMENT) or (
args = ast.arguments( isinstance(value, ast.Constant) and value.value in ENVIRONMENT
posonlyargs=[], ):
kwonlyargs=[], func = ENVIRONMENT[
kw_defaults=[], value.id if isinstance(value, ast.Name) else value.value
defaults=[], ]
args=[ast.arg(arg=a, annotation=None) for a in visitor.args], # handle the missing attribute for numpy.ufunc
kwarg=None, module = getattr(func, "__module__", "numpy")
vararg=None, if module.startswith("saqc"):
) # if it's an saqc function, we need to import the top level package first
value = ast.Lambda(args=args, body=value) imports["saqc"] = importlib.import_module("saqc")
imports[module] = importlib.import_module(module)
value = ast.parse(f"{module}.{func.__name__}").body[0].value
else:
visitor = ConfigExpressionParser(value)
args = ast.arguments(
posonlyargs=[],
kwonlyargs=[],
kw_defaults=[],
defaults=[],
args=[ast.arg(arg=a, annotation=None) for a in visitor.args],
kwarg=None,
vararg=None,
)
value = ast.Lambda(args=args, body=value)
# NOTE: # NOTE:
# don't pass the generated functions down # don't pass the generated functions down
# to the checks implemented in this class... # to the checks implemented in this class...
...@@ -159,8 +177,7 @@ class ConfigFunctionParser(ast.NodeVisitor): ...@@ -159,8 +177,7 @@ class ConfigFunctionParser(ast.NodeVisitor):
mode="single", mode="single",
) )
# NOTE: only pass a copy to not clutter the ENVIRONMENT # NOTE: only pass a copy to not clutter the ENVIRONMENT
# try: exec(co, {**ENVIRONMENT, **imports}, self.kwargs)
exec(co, {**ENVIRONMENT}, self.kwargs)
# let's do some more validity checks # let's do some more validity checks
if check_tree: if check_tree:
......
...@@ -9,7 +9,9 @@ ...@@ -9,7 +9,9 @@
import numpy as np import numpy as np
import pytest import pytest
from saqc.core import DictOfSeries, Flags, flagging import saqc.lib.ts_operators as ts_ops
from saqc.core import DictOfSeries, Flags, SaQC, flagging
from saqc.parsing.environ import ENVIRONMENT
from saqc.parsing.reader import fromConfig, readFile from saqc.parsing.reader import fromConfig, readFile
from tests.common import initData, writeIO from tests.common import initData, writeIO
...@@ -155,3 +157,21 @@ def test_supportedArguments(data): ...@@ -155,3 +157,21 @@ def test_supportedArguments(data):
for test in tests: for test in tests:
fobj = writeIO(header + "\n" + test) fobj = writeIO(header + "\n" + test)
fromConfig(fobj, data) fromConfig(fobj, data)
@pytest.mark.parametrize(
"func_string", [k for k, v in ENVIRONMENT.items() if callable(v)]
)
def test_funtionArguments(data, func_string):
@flagging()
def testFunction(saqc, field, func, **kwargs):
assert func is ENVIRONMENT[func_string]
return saqc
config = f"""
varname ; test
{data.columns[0]} ; testFunction(func={func_string})
{data.columns[0]} ; testFunction(func="{func_string}")
"""
fromConfig(writeIO(config), data)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment