Repository: ioos/compliance-checker
Pull request #667 (2019-08-13 12:39:45)

Flexible conventions version check
First commit addresses #295, which bubbled up and affected the implementation of making the version check more flexible. Separate commits were used in the event the changes should be assimilated independently. The second commit addresses no. 76 in #664.


Patch:
diff --git a/compliance_checker/cf/cf.py b/compliance_checker/cf/cf.py
index 5cf4eb8..8f47923 100644
--- a/compliance_checker/cf/cf.py
+++ b/compliance_checker/cf/cf.py
@@ -135,6 +135,33 @@ class CFBaseCheck(BaseCheck):
         self._find_cf_standard_name_table(ds)
         self._find_geophysical_vars(ds)
 
+    def _check_conventions_version(self, ds):
+        '''
+        CF §2.6.1 the NUG defined global attribute Conventions to the string
+        value "CF-<version_number>"; check the Conventions attribute contains
+        the appropriate string.
+
+        :param netCDF4.Dataset ds: An open netCDF dataset
+        :rtype: compliance_checker.base.Result
+        '''
+
+        valid = False
+        reasoning = []
+        correct_version_string = "{}-{}".format(self._cc_spec, self._cc_spec_version).upper()
+        if hasattr(ds, 'Conventions'):
+            conventions = regex.split(r',|\s+', getattr(ds, 'Conventions', ''))
+            for convention in conventions:
+                if convention == correct_version_string:
+                    valid = True
+                    break
+            else:
+                reasoning = ['§2.6.1 Conventions global attribute does not contain '
+                             '"{}"'.format(correct_version_string)]
+        else:
+            valid = False
+            reasoning = ['§2.6.1 Conventions field is not present']
+        return Result(BaseCheck.MEDIUM, valid, self.section_titles['2.6'], msgs=reasoning)
+
     def _dims_in_order(self, dimension_order):
         '''
         :param list dimension_order: A list of axes
@@ -882,7 +909,7 @@ class CF1_6Check(CFNCCheck):
 
     def check_conventions_are_cf_16(self, ds):
         '''
-        Check the global attribute conventions to contain CF-1.6
+        Check the global attribute conventions to contain CF-1.6.
 
         CF §2.6.1 the NUG defined global attribute Conventions to the string
         value "CF-1.6"
@@ -891,21 +918,8 @@ class CF1_6Check(CFNCCheck):
         :rtype: compliance_checker.base.Result
         '''
 
-        valid = False
-        reasoning = []
-        if hasattr(ds, 'Conventions'):
-            conventions = regex.split(r',|\s+', getattr(ds, 'Conventions', ''))
-            for convention in conventions:
-                if convention == 'CF-1.6':
-                    valid = True
-                    break
-            else:
-                reasoning = ['§2.6.1 Conventions global attribute does not contain '
-                             '"CF-1.6"']
-        else:
-            valid = False
-            reasoning = ['§2.6.1 Conventions field is not present']
-        return Result(BaseCheck.MEDIUM, valid, self.section_titles['2.6'], msgs=reasoning)
+        return self._check_conventions_version(ds) # invoke inherited method
+
 
     def check_convention_globals(self, ds):
         '''
@@ -3664,6 +3678,20 @@ class CF1_7Check(CF1_6Check):
 
         return ret_val
 
+    def check_conventions_are_cf_1_7(self, ds):
+        '''
+        Check the global attribute conventions to contain CF-1.7.
+
+        CF §2.6.1 the NUG defined global attribute Conventions to the string
+        value "CF-1.7"
+
+        :param netCDF4.Dataset ds: An open netCDF dataset
+        :rtype: compliance_checker.base.Result
+        '''
+
+        return self._check_conventions_version(ds) # invoke inherited method
+
+
 class CFNCCheck(BaseNCCheck, CFBaseCheck):
 
     @classmethod
diff --git a/compliance_checker/suite.py b/compliance_checker/suite.py
index 41e6fb9..74c0d4d 100644
--- a/compliance_checker/suite.py
+++ b/compliance_checker/suite.py
@@ -279,11 +279,12 @@ class CheckSuite(object):
             if type(ds) in a().supported_ds:
                 valid.append((name, a))
 
-            # add any subclasses of the checker class
-            for subc in a.__subclasses__():
-                if subc not in all_checked:
-                    all_checked.add(subc)
-                    checker_queue.add((name, subc))
+            # add subclasses of SOS checks
+            if "ioos_sos" in name:
+                for subc in a.__subclasses__():
+                    if subc not in all_checked:
+                        all_checked.add(subc)
+                        checker_queue.add((name, subc))
 
         return valid
 


Test patch:
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
index 5c05a8f..55fb7cc 100644
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -1296,3 +1296,30 @@ class TestCF1_7(TestCF1_6):
         score, out_of, messages = self.get_results(results)
         message = u'Cell measure variable box_area referred to by PS is not present in dataset variables'
         assert message in messages
+
+    def test_check_conventions_are_cf_16(self):
+        """Since this class inherits directly from TestCF1_6, it tries to
+        implement every inherited method. This should method should not be tested,
+        as it does not exist in the CF1_7Check class."""
+        pass
+
+    def test_check_conventions_are_cf_1_7(self):
+        """Ensure the check_conventions_are_cf_1_7() check works as expected"""
+
+        # create a temporary variable and test this only
+        with MockTimeSeries() as dataset:
+            # no Conventions attribute
+            result = self.cf.check_conventions_are_cf_1_7(dataset)
+            self.assertFalse(result.value)
+
+        with MockTimeSeries() as dataset:
+            # incorrect Conventions attribute
+            dataset.setncattr("Conventions", "CF-1.9999")
+            result = self.cf.check_conventions_are_cf_1_7(dataset)
+            self.assertFalse(result.value)
+
+        with MockTimeSeries() as dataset:
+            # correct Conventions attribute
+            dataset.setncattr("Conventions", "CF-1.7, ACDD-1.3")
+            result = self.cf.check_conventions_are_cf_1_7(dataset)
+            self.assertTrue(result.value)
diff --git a/compliance_checker/tests/test_cli.py b/compliance_checker/tests/test_cli.py
index 3b28997..72730f3 100644
--- a/compliance_checker/tests/test_cli.py
+++ b/compliance_checker/tests/test_cli.py
@@ -220,7 +220,7 @@ class TestCLI(TestCase):
             ds_loc=STATIC_FILES['ncei_gold_point_1'],
             verbose=0,
             criteria='strict',
-            checker_names=['cf'],
+            checker_names=['cf:1.6'],
             output_filename=self.path,
             output_format='text'
         )