Skip to content

Commit a07ee07

Browse files
committed
update documentation and ioos compliance checker
1 parent 1cc95c6 commit a07ee07

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+18011
-18222
lines changed

cchecker.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ def main():
7272
"Define the criteria for the checks. "
7373
"Either Strict, Normal, or Lenient. Defaults to Normal."
7474
),
75-
nargs="?",
7675
default="normal",
7776
choices=["lenient", "normal", "strict"],
7877
)

compliance_checker/acdd.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from cftime import num2pydate
1414
from pygeoif import from_wkt
1515

16-
from compliance_checker import cfutil
16+
import compliance_checker.cf.util as cfutil
1717
from compliance_checker.base import (
1818
BaseCheck,
1919
BaseNCCheck,
@@ -694,7 +694,7 @@ def __init__(self):
694694
"publisher_name", # publisher,dataCenter
695695
"publisher_url", # publisher
696696
"publisher_email", # publisher
697-
"geospatial_vertical_positive",
697+
("geospatial_vertical_positive", ["up", "down"]),
698698
],
699699
)
700700

@@ -710,7 +710,7 @@ def __init__(self):
710710

711711
self.rec_atts.extend(
712712
[
713-
"geospatial_vertical_positive",
713+
("geospatial_vertical_positive", ["up", "down"]),
714714
"geospatial_bounds_crs",
715715
"geospatial_bounds_vertical_crs",
716716
"publisher_name", # publisher,dataCenter
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
appendix_a = {
2+
"actual_range": {"Type": "N", "Use": ["C", "D", "BO"]},
3+
"add_offset": {"Type": "N", "Use": ["C", "D", "BO"]},
4+
"ancillary_variables": {"Type": "S", "Use": ["D"]},
5+
"axis": {"Type": "S", "Use": ["C", "BI"]},
6+
"bounds": {"Type": "S", "Use": ["C"]},
7+
"calendar": {"Type": "S", "Use": ["C", "BI"]},
8+
"cell_measures": {"Type": "S", "Use": ["D", "Do"]},
9+
"cell_methods": {"Type": "S", "Use": ["D"]},
10+
"cf_role": {"Type": "S", "Use": ["C", "BI"]},
11+
"climatology": {"Type": "S", "Use": ["C"]},
12+
"comment": {"Type": "S", "Use": ["G", "C", "D"]},
13+
"compress": {"Type": "S", "Use": ["C"]},
14+
"computed_standard_name": {"Type": "S", "Use": ["C", "BI"]},
15+
"Conventions": {"Type": "S", "Use": ["G"]},
16+
"coordinate_interpolation": {"Type": "S", "Use": ["D", "Do"]},
17+
"coordinates": {"Type": "S", "Use": ["D", "M", "Do"]},
18+
"dimensions": {"Type": "S", "Use": ["Do"]},
19+
"external_variables": {"Type": "S", "Use": ["G"]},
20+
"_FillValue": {"Type": "D", "Use": ["C", "D", "BO"]},
21+
"featureType": {"Type": "S", "Use": ["G"]},
22+
"flag_masks": {"Type": "D", "Use": ["D"]},
23+
"flag_meanings": {"Type": "S", "Use": ["D"]},
24+
"flag_values": {"Type": "D", "Use": ["D"]},
25+
"formula_terms": {"Type": "S", "Use": ["C", "BO"]},
26+
"geometry": {"Type": "S", "Use": ["C", "D", "Do"]},
27+
"geometry_type": {"Type": "S", "Use": ["M"]},
28+
"grid_mapping": {"Type": "S", "Use": ["D", "M", "Do"]},
29+
"history": {"Type": "S", "Use": ["G", "Gr"]},
30+
"instance_dimension": {"Type": "S", "Use": []},
31+
"institution": {"Type": "S", "Use": ["G", "D"]},
32+
"interior_ring": {"Type": "S", "Use": ["M"]},
33+
"leap_month": {"Type": "N", "Use": ["C", "BI"]},
34+
"leap_year": {"Type": "N", "Use": ["C", "BI"]},
35+
"location": {"Type": "S", "Use": ["D", "Do"]},
36+
"location_index_set": {"Type": "S", "Use": ["D", "Do"]},
37+
"long_name": {"Type": "S", "Use": ["C", "D", "Do", "BI"]},
38+
"mesh": {"Type": "S", "Use": ["D", "Do"]},
39+
"missing_value": {"Type": "D", "Use": ["C", "D", "BO"]},
40+
"month_lengths": {"Type": "N", "Use": ["C", "BI"]},
41+
"node_coordinates": {"Type": "S", "Use": ["M"]},
42+
"node_count": {"Type": "S", "Use": ["M"]},
43+
"nodes": {"Type": "S", "Use": ["C"]},
44+
"part_node_count": {"Type": "S", "Use": ["M"]},
45+
"positive": {"Type": "S", "Use": ["C", "BI"]},
46+
"references": {"Type": "S", "Use": ["G", "D"]},
47+
"sample_dimension": {"Type": "S", "Use": []},
48+
"scale_factor": {"Type": "N", "Use": ["C", "D", "BO"]},
49+
"source": {"Type": "S", "Use": ["G", "D"]},
50+
"standard_error_multiplier": {"Type": "N", "Use": ["D"]},
51+
"standard_name": {"Type": "S", "Use": ["C", "D", "BI"]},
52+
"title": {"Type": "S", "Use": ["G", "Gr"]},
53+
"units": {"Type": "S", "Use": ["C", "D", "BI"]},
54+
"units_metadata": {"Type": "S", "Use": ["C", "D", "BI"]},
55+
"valid_max": {"Type": "N", "Use": ["C", "D", "BO"]},
56+
"valid_min": {"Type": "N", "Use": ["C", "D", "BO"]},
57+
"valid_range": {"Type": "N", "Use": ["C", "D", "BO"]},
58+
}

compliance_checker/cf/cf.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33

4-
from compliance_checker import cfutil # noqa: F401
4+
import compliance_checker.cf.util as cfutil # noqa: F401
55
from compliance_checker.base import ( # noqa: F401
66
BaseCheck,
77
BaseNCCheck,
@@ -31,3 +31,5 @@
3131
from compliance_checker.cf.cf_1_7 import CF1_7Check # noqa: F401
3232
from compliance_checker.cf.cf_1_8 import CF1_8Check # noqa: F401
3333
from compliance_checker.cf.cf_1_9 import CF1_9Check # noqa: F401
34+
from compliance_checker.cf.cf_1_10 import CF1_10Check # noqa: F401
35+
from compliance_checker.cf.cf_1_11 import CF1_11Check # noqa: F401

compliance_checker/cf/cf_1_10.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from compliance_checker.cf.cf_1_9 import CF1_9Check
2+
3+
4+
# no significant features for code implementation between CF 1.9 and CF 1.10
5+
class CF1_10Check(CF1_9Check):
6+
_cc_spec_version = "1.10"
7+
_cc_url = "http://cfconventions.org/Data/cf-conventions/cf-conventions-1.10/cf-conventions.html"

compliance_checker/cf/cf_1_11.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
from functools import lru_cache
2+
3+
from compliance_checker.base import BaseCheck, TestCtx
4+
from compliance_checker.cf.appendix_a import appendix_a
5+
from compliance_checker.cf.cf_1_10 import CF1_10Check
6+
from compliance_checker.cf.util import VariableReferenceError, reference_attr_variables
7+
8+
9+
@lru_cache
10+
def _temperature_standard_names(standard_name_table):
11+
re_ns = {"re": "http://exslt.org/regular-expressions"}
12+
temp_var_name_set = set(
13+
standard_name_table._root.xpath(
14+
"entry[re:test(canonical_units, " r"'(?:K|degree_C)(?:-?\d+)?')]/@id",
15+
namespaces=re_ns,
16+
),
17+
)
18+
# need to include aliases for variable names that match temperature as well
19+
aliases = standard_name_table._root.findall("alias")
20+
all_names = temp_var_name_set.union(
21+
{
22+
alias.attrib["id"]
23+
for alias in aliases
24+
if alias.find("entry_id").text in temp_var_name_set
25+
},
26+
)
27+
return all_names
28+
29+
30+
class CF1_11Check(CF1_10Check):
31+
_cc_spec_version = "1.11"
32+
_cc_url = "http://cfconventions.org/Data/cf-conventions/cf-conventions-1.11/cf-conventions.html"
33+
34+
def __init__(self, options=None):
35+
super().__init__(options)
36+
self.section_titles.update(
37+
{
38+
"3.1.2": "§3.1.2 Temperature units",
39+
},
40+
)
41+
42+
# IMPLEMENTATION CONFORMANCE 3.1 RECOMMENDED
43+
def check_temperature_units_metadata(self, ds):
44+
"""Checks that units_metadata exists for variables with standard name of temperature"""
45+
temperature_variables = ds.get_variables_by_attributes(
46+
standard_name=lambda s: s in _temperature_standard_names(self._std_names),
47+
)
48+
if not temperature_variables:
49+
return []
50+
temperature_units_metadata_ctx = TestCtx(
51+
BaseCheck.MEDIUM,
52+
self.section_titles["3.1.2"],
53+
)
54+
for temperature_variable in temperature_variables:
55+
temperature_units_metadata_ctx.out_of += 1
56+
valid_temperature_units_metadata = [
57+
"temperature: difference",
58+
"temperature: on_scale",
59+
"temperature: unknown",
60+
]
61+
if (
62+
getattr(temperature_variable, "units_metadata", None)
63+
not in valid_temperature_units_metadata
64+
):
65+
temperature_units_metadata_ctx.messages.append(
66+
f"Variable {temperature_variable.name} has a temperature related standard_name "
67+
"and it is recommended that the units_metadata attribute is present and has one of the values "
68+
f"{valid_temperature_units_metadata}",
69+
)
70+
else:
71+
temperature_units_metadata_ctx.score += 1
72+
73+
return [temperature_units_metadata_ctx.to_result()]
74+
75+
def check_time_units_metadata(self, ds):
76+
"""Checks that units_metadata exists for time coordinates with specific calendar attributes"""
77+
time_variables = ds.get_variables_by_attributes(
78+
standard_name="time",
79+
calendar=lambda c: c in {"standard", "proleptic_gregorian", "julian"},
80+
)
81+
if not time_variables:
82+
return []
83+
84+
time_units_metadata_ctx = self.get_test_ctx(
85+
BaseCheck.MEDIUM,
86+
self.section_titles["4.4"],
87+
)
88+
89+
for time_variable in time_variables:
90+
time_units_metadata_ctx.out_of += 1
91+
valid_time_units_metadata = [
92+
"leap_seconds: none",
93+
"leap_seconds: utc",
94+
"leap_seconds: unknown",
95+
]
96+
if (
97+
getattr(time_variable, "units_metadata", None)
98+
not in valid_time_units_metadata
99+
):
100+
time_units_metadata_ctx.messages.append(
101+
f"Variable {time_variable.name} has a calendar attribute of "
102+
f"{time_variable.calendar} and it is recommended that the units_metadata attribute is present "
103+
"and has one of the values "
104+
f"{valid_time_units_metadata}",
105+
)
106+
else:
107+
time_units_metadata_ctx.score += 1
108+
109+
return [time_units_metadata_ctx.to_result()]
110+
111+
def check_bounds_inherit_attributes(self, ds):
112+
"""
113+
A boundary variable inherits the values of some attributes from its parent coordinate variable.
114+
If a coordinate variable has any of the attributes marked "BI" (for "inherit") in the "Use" column of <<attribute-appendix>>, they are assumed to apply to its bounds variable as well.
115+
It is recommended that BI attributes not be included on a boundary variable.
116+
If a BI attribute is included, it must also be present in the parent variable, and it must exactly match the parent attribute's data type and value.
117+
A boundary variable can only have inheritable attributes if they are also present on its parent coordinate variable.
118+
A bounds variable may have any of the attributes marked "BO" for ("own") in the "Use" column of <<attribute-appendix>>.
119+
These attributes take precedence over any corresponding attributes of the parent variable.
120+
In these cases, the parent variable's attribute does not apply to the bounds variable, regardless of whether the latter has its own attribute.
121+
"""
122+
results = []
123+
124+
appendix_a_bi_attrs = {
125+
attribute_name
126+
for attribute_name, data_dict in appendix_a.items()
127+
if "BI" in data_dict["Use"]
128+
}
129+
for parent_variable in ds.get_variables_by_attributes(
130+
bounds=lambda b: b is not None,
131+
):
132+
parent_bi_attrs = set(parent_variable.ncattrs()) & appendix_a_bi_attrs
133+
bounds_variable = reference_attr_variables(ds, parent_variable.bounds)[0]
134+
# nonexistent bounds variable, skip
135+
if isinstance(bounds_variable, VariableReferenceError):
136+
continue
137+
138+
bounds_bi_ctx = self.get_test_ctx(
139+
BaseCheck.MEDIUM,
140+
self.section_titles["7.1"],
141+
)
142+
bounds_ncattr_set = set(bounds_variable.ncattrs())
143+
# IMPLEMENTATION CONFORMANCE 7.3 REQUIRED 4
144+
# A boundary variable can only have inheritable attributes, i.e. any of those marked "BI" in the "Use" column of Appendix A, if they are also present on its parent coordinate variable.
145+
bounds_bi_attrs = bounds_ncattr_set & appendix_a_bi_attrs
146+
# failure case 1, BI attr is only in bounds variable
147+
bounds_bi_only_attrs = bounds_bi_attrs - parent_bi_attrs
148+
# If a boundary variable has an inheritable attribute then its data type and its value must be exactly the same as the parent variable’s attribute.
149+
bounds_bi_ctx.out_of += 1
150+
if bounds_bi_only_attrs:
151+
bounds_bi_ctx.messages.append(
152+
f"Bounds variable {bounds_variable.name} has the following attributes which must appear on the parent variable {parent_variable.name}: "
153+
f"{sorted(bounds_bi_only_attrs)}",
154+
)
155+
else:
156+
bounds_bi_ctx.score += 1
157+
# failure case 2, BI attrs are in both bounds and parent variable
158+
both_bi_attr = bounds_bi_attrs & parent_bi_attrs
159+
no_match_attrs, match_attrs = [], []
160+
for bi_attr in both_bi_attr:
161+
bounds_bi_ctx.out_of += 1
162+
parent_attr_val = getattr(parent_variable, bi_attr)
163+
bounds_attr_val = getattr(bounds_variable, bi_attr)
164+
# IMPLEMENTATION CONFORMANCE 7.3 REQUIRED 5
165+
# If a boundary variable has an inheritable attribute then its data type and its value must be exactly the same as the parent variable’s attribute.
166+
if (
167+
type(parent_attr_val) is not type(bounds_attr_val)
168+
or parent_attr_val != bounds_attr_val
169+
):
170+
no_match_attrs.append(bi_attr)
171+
else:
172+
match_attrs.append(bi_attr)
173+
174+
pass_bi_both = True
175+
if no_match_attrs:
176+
pass_bi_both = False
177+
bounds_bi_ctx.messages.append(
178+
f"Bounds variable {bounds_variable.name} and parent variable {parent_variable.name} have the following non matching boundary related attributes: {sorted(no_match_attrs)}",
179+
)
180+
181+
if match_attrs:
182+
pass_bi_both = False
183+
bounds_bi_ctx.messages.append(
184+
f"Bounds variable {bounds_variable.name} and parent variable {parent_variable.name} have the following matching attributes {sorted(match_attrs)}. It is recommended that only the parent variable of the bounds variable contains these attributes",
185+
)
186+
bounds_bi_ctx.score += pass_bi_both
187+
188+
results.append(bounds_bi_ctx.to_result())
189+
return results
190+
191+
def check_single_cf_role(self, ds):
192+
test_ctx = self.get_test_ctx(
193+
BaseCheck.HIGH,
194+
self.section_titles["9.5"],
195+
)
196+
cf_role_var_names = [
197+
var.name
198+
for var in (ds.get_variables_by_attributes(cf_role=lambda x: x is not None))
199+
]
200+
test_ctx.assert_true(
201+
len(cf_role_var_names) < 2,
202+
"There may only be one variable containing the cf_role attribute. "
203+
f"Currently the following variables have cf_role attributes: {cf_role_var_names}",
204+
)
205+
return test_ctx.to_result()

0 commit comments

Comments
 (0)