Repository: ioos/compliance-checker
Pull request #1209 (2025-06-06 11:47:23)

3.5: Added additional check against wrong flag_value-types
One of our users provided data which use strings instead of arrays as the datatype for flag values. This caused a crash of the compliance-checker. This PR aims to avoid the crash.

```
 	byte A4O_flag_bright(lat, lon) ; 
 		A4O_flag_bright:flag_meanings = "A4O_flag_bright" ; 
 		A4O_flag_bright:flag_values = "0U 1U" ; 
 		A4O_flag_bright:flag_masks = "1U" ; 
 		A4O_flag_bright:long_name = "Level_2 bright mask for TOA reflectance (at 490) > 0.3, that includes possible clouds and sea ice" ; 
```


Patch:
diff --git a/compliance_checker/cf/cf_1_6.py b/compliance_checker/cf/cf_1_6.py
index 885ff44..c2c617f 100644
--- a/compliance_checker/cf/cf_1_6.py
+++ b/compliance_checker/cf/cf_1_6.py
@@ -1185,7 +1185,10 @@ class CF1_6Check(CFNCCheck):
                 # IMPLEMENTATION CONFORMANCE 3.5 RECOMMENDED 1/1
                 # If shapes aren't equal, we can't do proper elementwise
                 # comparison
-                if vals_arr.size != masks_arr.size:
+                if vals_arr.size != masks_arr.size or not (
+                    np.issubdtype(vals_arr.dtype, np.integer)
+                    and np.issubdtype(masks_arr.dtype, np.integer)
+                ):
                     allv = False
                 else:
                     allv = np.all(vals_arr & masks_arr == vals_arr)
@@ -1235,9 +1238,14 @@ class CF1_6Check(CFNCCheck):
 
         # IMPLEMENTATION CONFORMANCE 3.5 REQUIRED 1/8
         # the data type for flag_values should be the same as the variable
+        flag_values_type = (
+            flag_values.dtype.type
+            if hasattr(flag_values, "dtype")
+            else type(flag_values)
+        )
         valid_values.assert_true(
-            variable.dtype.type == flag_values.dtype.type,
-            f"flag_values ({flag_values.dtype.type}) must be the same data type as {name} ({variable.dtype.type})"
+            variable.dtype.type == flag_values_type,
+            f"flag_values ({flag_values_type}) must be the same data type as {name} ({variable.dtype.type})"
             "",
         )
 
@@ -1272,9 +1280,12 @@ class CF1_6Check(CFNCCheck):
 
         valid_masks = TestCtx(BaseCheck.HIGH, self.section_titles["3.5"])
 
+        flag_masks_type = (
+            flag_masks.dtype.type if hasattr(flag_masks, "dtype") else type(flag_masks)
+        )
         valid_masks.assert_true(
-            variable.dtype.type == flag_masks.dtype.type,
-            f"flag_masks ({flag_masks.dtype.type}) must be the same data type as {name} ({variable.dtype.type})"
+            variable.dtype.type == flag_masks_type,
+            f"flag_masks ({flag_masks_type}) must be the same data type as {name} ({variable.dtype.type})"
             "",
         )
 


Test patch:
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
index 39ab418..3146974 100644
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -866,6 +866,9 @@ class TestCF1_6(BaseTestCase):
         # Test with single element.  Will fail, but should not throw exception.
         dataset.variables["conductivity_qc"].flag_values = np.array([1], dtype=np.int8)
         results = self.cf.check_flags(dataset)
+        # Test with wrong type.  Will fail, but should not throw exception.
+        dataset.variables["conductivity_qc"].flag_values = "[1 2]"
+        results = self.cf.check_flags(dataset)
 
     def test_check_flag_masks(self):
         dataset = self.load_dataset(STATIC_FILES["ghrsst"])
@@ -892,6 +895,14 @@ class TestCF1_6(BaseTestCase):
             "flag_masks for variable flags must not contain zero as an "
             "element" in messages
         )
+        # test wrong type for flag_masks
+        flags_var.flag_masks = "[0 1]"
+        results = self.cf.check_flags(dataset)
+        score, out_of, messages = get_results(results)
+        assert (
+            "flag_masks (<class 'str'>) must be the same data type as "
+            "flags (<class 'numpy.float64'>)" in messages
+        )
         # IMPLEMENTATION 3.5 REQUIRED 1/1
         flags_var.flag_masks = np.array([1], dtype="i2")
         flags_var.flag_values = np.array([2], dtype="i2")