From 80b82fb17f8e703b16a0580c565d40f89cd79675 Mon Sep 17 00:00:00 2001
From: Bert Palm <bert.palm@ufz.de>
Date: Fri, 12 Mar 2021 14:32:13 +0100
Subject: [PATCH] fixed (generic) isflagged and its test

---
 saqc/funcs/generic.py                       | 26 ++++++++-
 test/funcs/test_generic_config_functions.py | 63 +++++++++++----------
 2 files changed, 57 insertions(+), 32 deletions(-)

diff --git a/saqc/funcs/generic.py b/saqc/funcs/generic.py
index ed64f7ca0..862b3b981 100644
--- a/saqc/funcs/generic.py
+++ b/saqc/funcs/generic.py
@@ -21,13 +21,33 @@ _OP = {'<': op.lt, '<=': op.le, '==': op.eq, '!=': op.ne, '>': op.gt, '>=': op.g
 
 
 def _dslIsFlagged(
-        flagger: Flagger, var: pd.Series, flag: float = UNFLAGGED, comparator: str = ">"
+        flagger: Flagger, var: pd.Series, flag: float = None, comparator: str = None
 ) -> Union[pd.Series, DictOfSeries]:
     """
     helper function for `flag`
+
+    Param Combinations
+    ------------------
+    - ``isflagged('var')``              : show me (anything) flagged
+    - ``isflagged('var', DOUBT)``       : show me ``flags >= DOUBT``
+    - ``isflagged('var', DOUBT, '==')`` : show me ``flags == DOUBT``
+
+    Raises
+    ------
+    ValueError: if `comparator` is passed but no `flag` vaule. Eg. ``isflagged('var', comparator='>=')``
     """
-    comparison = _OP[comparator]
-    return comparison(flagger[var.name], flag)
+    if flag is None:
+        if comparator is not None:
+            raise ValueError('if `comparator` is used, explicitly pass a `flag` level.')
+        flag = UNFLAGGED
+        comparator = '>'
+
+    # default
+    if comparator is None:
+        comparator = '>='
+
+    _op = _OP[comparator]
+    return _op(flagger[var.name], flag)
 
 
 def _execGeneric(flagger: Flagger, data: DictOfSeries, func: Callable[[pd.Series], pd.Series], field: str,
diff --git a/test/funcs/test_generic_config_functions.py b/test/funcs/test_generic_config_functions.py
index 7677c3c27..81e91d643 100644
--- a/test/funcs/test_generic_config_functions.py
+++ b/test/funcs/test_generic_config_functions.py
@@ -2,22 +2,20 @@
 # -*- coding: utf-8 -*-
 
 import ast
-
 import pytest
 import numpy as np
 import pandas as pd
-
-from dios import DictOfSeries
-
-from test.common import TESTFLAGGER, TESTNODATA, initData, writeIO
+import dios
 
 from saqc.common import *
 from saqc.flagger import Flagger, initFlagsLike
 from saqc.core.visitor import ConfigFunctionParser
 from saqc.core.config import Fields as F
 from saqc.core.register import register
-from saqc import SaQC
 from saqc.funcs.generic import _execGeneric
+from saqc import SaQC
+
+from test.common import TESTNODATA, initData, writeIO
 
 
 @pytest.fixture
@@ -32,7 +30,7 @@ def data_diff():
     col1 = data[data.columns[1]]
     mid = len(col0) // 2
     offset = len(col0) // 8
-    return DictOfSeries(data={col0.name: col0.iloc[: mid + offset], col1.name: col1.iloc[mid - offset :],})
+    return dios.DictOfSeries(data={col0.name: col0.iloc[: mid + offset], col1.name: col1.iloc[mid - offset :],})
 
 
 def _compileGeneric(expr, flagger):
@@ -41,8 +39,8 @@ def _compileGeneric(expr, flagger):
     return kwargs["func"]
 
 
-@pytest.mark.parametrize("flagger", TESTFLAGGER)
-def test_missingIdentifier(data, flagger):
+def test_missingIdentifier(data):
+    flagger = Flagger()
 
     # NOTE:
     # - the error is only raised at runtime during parsing would be better
@@ -57,9 +55,8 @@ def test_missingIdentifier(data, flagger):
             _execGeneric(flagger, data, func, field="", nodata=np.nan)
 
 
-@pytest.mark.parametrize("flagger", TESTFLAGGER)
-def test_syntaxError(flagger):
-
+def test_syntaxError():
+    flagger = Flagger()
     tests = [
         "range(x=5",
         "rangex=5)",
@@ -106,8 +103,7 @@ def test_comparisonOperators(data):
         assert np.all(result == expected)
 
 
-@pytest.mark.parametrize("flagger", TESTFLAGGER)
-def test_arithmeticOperators(data, flagger):
+def test_arithmeticOperators(data):
     flagger = initFlagsLike(data)
     var1, *_ = data.columns
     this = data[var1]
@@ -127,8 +123,7 @@ def test_arithmeticOperators(data, flagger):
         assert np.all(result == expected)
 
 
-@pytest.mark.parametrize("flagger", TESTFLAGGER)
-def test_nonReduncingBuiltins(data, flagger):
+def test_nonReduncingBuiltins(data):
     flagger = initFlagsLike(data)
     var1, *_ = data.columns
     this = var1
@@ -147,10 +142,8 @@ def test_nonReduncingBuiltins(data, flagger):
         assert (result == expected).all()
 
 
-@pytest.mark.parametrize("flagger", TESTFLAGGER)
 @pytest.mark.parametrize("nodata", TESTNODATA)
-def test_reduncingBuiltins(data, flagger, nodata):
-
+def test_reduncingBuiltins(data, nodata):
     data.loc[::4] = nodata
     flagger = initFlagsLike(data)
     var1 = data.columns[0]
@@ -171,10 +164,10 @@ def test_reduncingBuiltins(data, flagger, nodata):
         assert result == expected
 
 
-@pytest.mark.parametrize("flagger", TESTFLAGGER)
 @pytest.mark.parametrize("nodata", TESTNODATA)
-def test_ismissing(data, flagger, nodata):
+def test_ismissing(data, nodata):
 
+    flagger = initFlagsLike(data)
     data.iloc[: len(data) // 2, 0] = np.nan
     data.iloc[(len(data) // 2) + 1 :, 0] = -9999
     this = data.iloc[:, 0]
@@ -190,9 +183,8 @@ def test_ismissing(data, flagger, nodata):
         assert np.all(result == expected)
 
 
-@pytest.mark.parametrize("flagger", TESTFLAGGER)
 @pytest.mark.parametrize("nodata", TESTNODATA)
-def test_bitOps(data, flagger, nodata):
+def test_bitOps(data, nodata):
     var1, var2, *_ = data.columns
     this = var1
 
@@ -220,14 +212,26 @@ def test_isflagged(data):
         (f"isflagged({var1})", flagger[var1] > UNFLAGGED),
         (f"isflagged({var1}, flag=BAD)", flagger[var1] >= BAD),
         (f"isflagged({var1}, UNFLAGGED, '==')", flagger[var1] == UNFLAGGED),
-        (f"~isflagged({var2})", ~(flagger[var2] > UNFLAGGED)),
-        (f"~({var2}>999) & (~isflagged({var2}))", ~(data[var2] > 999) & ~(flagger[var2] > UNFLAGGED)),
+        (f"~isflagged({var2})", flagger[var2] == UNFLAGGED),
+        (f"~({var2}>999) & (~isflagged({var2}))", ~(data[var2] > 999) & (flagger[var2] == UNFLAGGED)),
     ]
 
-    for test, expected in tests:
-        func = _compileGeneric(f"generic.flag(func={test}, flag=BAD)", flagger)
-        result = _execGeneric(flagger, data, func, field=None, nodata=np.nan)
-        assert np.all(result == expected)
+    for i, (test, expected) in enumerate(tests):
+        try:
+            func = _compileGeneric(f"generic.flag(func={test}, flag=BAD)", flagger)
+            result = _execGeneric(flagger, data, func, field=None, nodata=np.nan)
+            assert np.all(result == expected)
+        except Exception:
+            print(i, test)
+            raise
+
+    # test bad combination
+    for comp in ['>', '>=', '==', '!=', '<', '<=']:
+        fails = f"isflagged({var1}, comparator='{comp}')"
+
+        func = _compileGeneric(f"generic.flag(func={fails}, flag=BAD)", flagger)
+        with pytest.raises(ValueError):
+            _execGeneric(flagger, data, func, field=None, nodata=np.nan)
 
 
 def test_variableAssignments(data):
@@ -249,6 +253,7 @@ def test_variableAssignments(data):
     assert set(result_flagger.columns) == set(data.columns) | {"dummy1", "dummy2"}
 
 
+# TODO: why this must(!) fail ? - a comment would be helpful
 @pytest.mark.xfail(strict=True)
 def test_processMultiple(data_diff):
     var1, var2, *_ = data_diff.columns
-- 
GitLab