Repository: ioos/compliance-checker
Pull request #680 (2019-10-21 15:35:51)

Multi string conventions
    TracTicket #76: More Than One Name in "Conventions"
    
    Implement backwards-compatible check for CF-1.6 where the Conventions
    attribute is only permitted to contain one name. In CF-1.7, multiple names
    are allowed. Remove the inherited method from the BaseCheck, as that
    implementation does not belong there.



Patch:
diff --git a/compliance_checker/cf/cf.py b/compliance_checker/cf/cf.py
index 56e4dcb..9f69ad1 100644
--- a/compliance_checker/cf/cf.py
+++ b/compliance_checker/cf/cf.py
@@ -139,33 +139,6 @@ 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
@@ -912,17 +885,38 @@ class CF1_6Check(CFNCCheck):
 
     def check_conventions_are_cf_16(self, ds):
         '''
-        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"
+        value "CF-1.6"; check the Conventions attribute contains
+        the appropriate string. Only the one string is allowed in the
+        attribute field.
 
         :param netCDF4.Dataset ds: An open netCDF dataset
         :rtype: compliance_checker.base.Result
         '''
 
-        return self._check_conventions_version(ds) # invoke inherited method
-
+        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', ''))
+            if len(conventions) > 1:
+                return Result(
+                    BaseCheck.MEDIUM,
+                    valid,
+                    self.section_titles['2.6'],
+                    msgs=['§2.6.1 only "CF-1.6" is permitted as the Conventions string']
+                )
+            elif len(conventions) < 1:
+                reasoning = ['§2.6.1 Conventions field is not present']
+            else:
+                if conventions[0] == correct_version_string:
+                    valid = True
+                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 check_convention_globals(self, ds):
         '''
@@ -3754,16 +3748,32 @@ class CF1_7Check(CF1_6Check):
 
     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"
+        value "CF-1.7"; check the Conventions attribute contains
+        the appropriate string. Additionally, the Conventions attribute is
+        permitted to contain more than one metadata convention, unlike
+        CF-1.6.
 
         :param netCDF4.Dataset ds: An open netCDF dataset
         :rtype: compliance_checker.base.Result
         '''
 
-        return self._check_conventions_version(ds) # invoke inherited method
+        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)
      
 
 class CFNCCheck(BaseNCCheck, CFBaseCheck):


Test patch:
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
index 457b1e0..e2503b5 100644
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -259,7 +259,7 @@ class TestCF1_6(BaseTestCase):
         # :Conventions = "CF-1.6 ,ACDD" ;
         dataset = self.load_dataset(STATIC_FILES['conv_multi'])
         result = self.cf.check_conventions_are_cf_16(dataset)
-        self.assertTrue(result.value)
+        self.assertFalse(result.value)
 
         # :Conventions = "NoConvention"
         dataset = self.load_dataset(STATIC_FILES['conv_bad'])