Skip to content

Commit 353e655

Browse files
authored
Merge pull request #65 from abkfenris/acdd
Initial ACDD Tests
2 parents 1a1680d + 0ee6c0d commit 353e655

18 files changed

Lines changed: 701 additions & 1 deletion

CHANGES.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
# XRLint Change History
22

3+
34
## Version 0.5.2 (in development)
45

56
### Adjustments and Enhancements
67

78
- Core rule 'time-coordinates' now support ms, µs and ns. (#66)
89

10+
- Implemented an initial set of
11+
[Attribute Conventions Data Discovery (ACCD)](https://wiki.esipfed.org/Category:Attribute_Conventions_Dataset_Discovery)
12+
rules adapted from the [IOOS Compliance Checker](https://github.com/ioos/compliance-checker/)
13+
library (many thanks to @abkfenris):
14+
- Configs for ACDD 1.0, 1.1, and 1.3, and with selectable levels of severity.
15+
The recommended set uses ACDD 1.3 with the highly recomended rules as errors.
16+
- Global attribute existance rules.
17+
- Checks that ACDD is in the conventions attribute.
18+
- Makes sure the date attributes are ISO formatted.
19+
- Metadata links are URLs.
20+
- The ID attribute should not be blank.
21+
22+
- Load plugins from entry points allowing plugins to be discovered from installed libraries.
23+
- Automatically generate rule documentation removing the manual need to run `mkruleref.py`.
24+
25+
926
## Version 0.5.1 (from 2025-02-21)
1027

1128
- XRLint now also loads default configuration from files named

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,7 @@ The following plugins provide XRLint's [inbuilt rules](https://bcdev.github.io/x
3838
[xcube datasets](https://xcube.readthedocs.io/en/latest/cubespec.html).
3939
Note, this plugin is fully optional. You must manually configure
4040
it to apply its rules. It may be moved into a separate GitHub repo later.
41+
- `xrlint.plugins.acdd`: implements rules for [Attribute Conventions Dataset Discovery](https://wiki.esipfed.org/Category:Attribute_Conventions_Dataset_Discovery).
42+
Note, this plugin is fully optional. You must manually configure it to apply its rules.
4143

4244

docs/rule-ref.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,75 @@ Time coordinate and dimension should be called 'time'.
199199

200200
Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning-bolt:
201201

202+
## ACDD Rules
203+
204+
### :material-bug: `1.0-attrs-highly-recommended`
205+
206+
Global attributes that are highly reccomended by ACDD-1.0.
207+
[More...](https://wiki.esipfed.org/Category:Attribute_Conventions_Dataset_Discovery)
208+
209+
Contained in: `acdd_1.0`-:material-lightning-bolt:
210+
211+
### :material-bug: `1.0-attrs-recommended`
212+
213+
Global attributes that are recommended by ACDD-1.0.
214+
[More...](https://wiki.esipfed.org/Category:Attribute_Conventions_Dataset_Discovery)
215+
216+
Contained in: `acdd_1.0`-:material-alert:
217+
218+
### :material-bug: `1.0-attrs-suggested`
219+
220+
Global attributes that are suggested by ACDD-1.0.
221+
[More...](https://wiki.esipfed.org/Category:Attribute_Conventions_Dataset_Discovery)
222+
223+
Contained in: `acdd_1.0`-:material-alert:
224+
225+
### :material-bug: `1.3-attrs-highly-recommended`
226+
227+
Global attributes that are highly recommended by ACDD-1.3.
228+
[More...](https://wiki.esipfed.org/Attribute_Convention_for_Data_Discovery_1-3)
229+
230+
Contained in: `acdd_1.3`-:material-lightning-bolt: `acdd_1.3_strict`-:material-lightning-bolt: `acdd_1.3_strict_reccomended`-:material-lightning-bolt: `acdd_1.3_warn`-:material-alert: `recommended`-:material-lightning-bolt:
231+
232+
### :material-bug: `1.3-attrs-recommended`
233+
234+
Global attributes that are recommended by ACDD-1.3.
235+
[More...](https://wiki.esipfed.org/Attribute_Convention_for_Data_Discovery_1-3)
236+
237+
Contained in: `acdd_1.3`-:material-alert: `acdd_1.3_strict`-:material-lightning-bolt: `acdd_1.3_strict_reccomended`-:material-lightning-bolt: `acdd_1.3_warn`-:material-alert: `recommended`-:material-alert:
238+
239+
### :material-bug: `1.3-attrs-suggested`
240+
241+
Global attributes that are suggested by ACDD 1.3.
242+
[More...](https://wiki.esipfed.org/Attribute_Convention_for_Data_Discovery_1-3)
243+
244+
Contained in: `acdd_1.3`-:material-alert: `acdd_1.3_strict`-:material-lightning-bolt: `acdd_1.3_strict_reccomended`-:material-alert: `acdd_1.3_warn`-:material-alert: `recommended`-:material-alert:
245+
246+
### :material-bug: `1.3-conventions`
247+
248+
The `Conventions` global attribute should include `ACDD-1.3`.
249+
[More...](https://wiki.esipfed.org/Attribute_Convention_for_Data_Discovery_1-3)
250+
251+
Contained in: `acdd_1.3`-:material-lightning-bolt: `acdd_1.3_strict`-:material-lightning-bolt: `acdd_1.3_strict_reccomended`-:material-lightning-bolt: `acdd_1.3_warn`-:material-alert: `recommended`-:material-lightning-bolt:
252+
253+
### :material-bug: `1.3-dates-iso-format`
254+
255+
ACDD date attributes must be in ISO format.
256+
[More...](https://wiki.esipfed.org/Attribute_Convention_for_Data_Discovery_1-3)
257+
258+
Contained in: `acdd_1.3`-:material-alert: `acdd_1.3_strict`-:material-lightning-bolt: `acdd_1.3_strict_reccomended`-:material-alert: `acdd_1.3_warn`-:material-alert: `recommended`-:material-alert:
259+
260+
### :material-bug: `1.3-metadata-link`
261+
262+
The `metadata` attribute should be a URL.
263+
[More...](https://wiki.esipfed.org/Attribute_Convention_for_Data_Discovery_1-3)
264+
265+
Contained in: `acdd_1.3`-:material-alert: `acdd_1.3_strict`-:material-lightning-bolt: `acdd_1.3_strict_reccomended`-:material-alert: `acdd_1.3_warn`-:material-alert: `recommended`-:material-alert:
266+
267+
### :material-bug: `1.3-no-blanks-in-id`
268+
269+
The `id` attribute should not contain blanks.
270+
[More...](https://wiki.esipfed.org/Attribute_Convention_for_Data_Discovery_1-3)
271+
272+
Contained in: `acdd_1.3`-:material-alert: `acdd_1.3_strict`-:material-lightning-bolt: `acdd_1.3_strict_reccomended`-:material-alert: `acdd_1.3_warn`-:material-alert: `recommended`-:material-alert:
273+

mkruleref.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727
def write_rule_ref_page():
2828
import xrlint.plugins.core
2929
import xrlint.plugins.xcube
30+
import xrlint.plugins.acdd
3031

3132
core = xrlint.plugins.core.export_plugin()
3233
xcube = xrlint.plugins.xcube.export_plugin()
34+
acdd = xrlint.plugins.acdd.export_plugin()
3335
with open("docs/rule-ref.md", "w") as stream:
3436
stream.write("# Rule Reference\n\n")
3537
stream.write(
@@ -41,6 +43,8 @@ def write_rule_ref_page():
4143
write_plugin_rules(stream, core)
4244
stream.write("## xcube Rules\n\n")
4345
write_plugin_rules(stream, xcube)
46+
stream.write("## ACDD Rules\n\n")
47+
write_plugin_rules(stream, acdd)
4448

4549

4650
def write_plugin_rules(stream, plugin: Plugin):

pyproject.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ build-backend = "setuptools.build_meta"
66
name = "xrlint"
77
dynamic = ["version", "readme"]
88
authors = [
9-
{name = "Norman Fomferra (Brockmann Consult GmbH)"}
9+
{name = "Norman Fomferra (Brockmann Consult GmbH)"},
10+
{name = "Alex Kerney (GMRI / NERACOOS)", email = "akerney@gmri.org"}
1011
]
1112
description = "A linter for xarray datasets."
1213
keywords = [
@@ -17,6 +18,7 @@ requires-python = ">=3.10"
1718
dependencies = [
1819
"click",
1920
"fsspec",
21+
"isodate>=0.7.2",
2022
"pyyaml",
2123
"tabulate",
2224
"xarray",
@@ -93,3 +95,10 @@ Documentation = "https://bcdev.github.io/xrlint"
9395
Repository = "https://github.com/bcdev/xrlint"
9496
Changelog = "https://github.com/bcdev/xrlint/blob/main/CHANGES.md"
9597
Issues = "https://github.com/bcdev/xrlint/issues"
98+
99+
[tool.uv]
100+
default-groups = "all"
101+
102+
# [tool.uv.pip]
103+
# all-extras = true
104+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import xarray as xr
2+
from xrlint.testing import RuleTest, RuleTester
3+
4+
from xrlint.plugins.acdd.rules.attributes import (
5+
Attributes_1_3_Highly_Recommended,
6+
)
7+
8+
valid_1_3_highly_rec_dataset = xr.Dataset(
9+
attrs={
10+
"title": "This is only a test",
11+
"summary": "This is only a test dataset.",
12+
"keywords": "test, example, sample",
13+
"Conventions": "ACDD-1.3",
14+
},
15+
)
16+
invalid_1_3_highly_rec_dataset = xr.Dataset()
17+
18+
19+
Attributes_1_3_Highly_RecommendedTest = RuleTester.define_test(
20+
"1.3-attrs-highly-recommended",
21+
Attributes_1_3_Highly_Recommended,
22+
valid=[RuleTest(dataset=valid_1_3_highly_rec_dataset)],
23+
invalid=[
24+
RuleTest(
25+
dataset=invalid_1_3_highly_rec_dataset,
26+
expected=[
27+
"Missing highly recommended attribute 'title'",
28+
"Missing highly recommended attribute 'keywords'",
29+
"Missing highly recommended attribute 'summary'",
30+
"Missing highly recommended attribute 'Conventions'",
31+
],
32+
),
33+
],
34+
)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import xarray as xr
2+
from xrlint.testing import RuleTest, RuleTester
3+
4+
from xrlint.plugins.acdd.rules.conventions import Conventions
5+
6+
valid_dataset_0 = xr.Dataset(attrs={"Conventions": "ACDD-1.3"})
7+
8+
invalid_dataset_0 = xr.Dataset()
9+
invalid_dataset_1 = xr.Dataset(attrs={"Conventions": "ACDD-1.2"})
10+
invalid_dataset_2 = xr.Dataset(attrs={"Conventions": 1.3})
11+
12+
13+
ConventionsTest = RuleTester.define_test(
14+
"1.3-conventions",
15+
Conventions,
16+
valid=[
17+
RuleTest(dataset=valid_dataset_0),
18+
],
19+
invalid=[
20+
RuleTest(
21+
dataset=invalid_dataset_0,
22+
expected=["Missing attribute 'Conventions'."],
23+
),
24+
RuleTest(
25+
dataset=invalid_dataset_1,
26+
expected=[
27+
"Attribute 'Conventions' needs to contain 'ACDD-1.3' in addition to the current: 'ACDD-1.2'",
28+
],
29+
),
30+
RuleTest(
31+
dataset=invalid_dataset_2,
32+
expected=["Invalid attribute 'Conventions': 1.3"],
33+
),
34+
],
35+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import xarray as xr
2+
from xrlint.testing import RuleTest, RuleTester
3+
4+
from xrlint.plugins.acdd.rules.no_id_blanks import NoBlanksInID
5+
6+
valid_dataset_0 = xr.Dataset(attrs={"id": "testing_dataset"})
7+
8+
invalid_dataset_0 = xr.Dataset()
9+
invalid_dataset_1 = xr.Dataset(attrs={"id": "testing dataset"})
10+
11+
12+
IdBlanksTest = RuleTester.define_test(
13+
"1.3-no-blanks-in-id",
14+
NoBlanksInID,
15+
valid=[RuleTest(dataset=valid_dataset_0)],
16+
invalid=[
17+
RuleTest(
18+
dataset=invalid_dataset_0,
19+
expected=["Missing attribute 'id'"],
20+
),
21+
RuleTest(
22+
dataset=invalid_dataset_1,
23+
expected=["There should not be blanks in the id field"],
24+
),
25+
],
26+
)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import xarray as xr
2+
from xrlint.testing import RuleTest, RuleTester
3+
4+
from xrlint.plugins.acdd.rules.iso_dates import IsoDates
5+
6+
valid_dataset_0 = xr.Dataset(attrs={"date_created": "2023-10-05T12:34:56Z"})
7+
valid_dataset_1 = xr.Dataset(attrs={"date_modified": "2023-10-05"})
8+
valid_dataset_2 = xr.Dataset(attrs={"date_issued": "2023-10-05T12:34:56+00:00"})
9+
valid_dataset_3 = xr.Dataset(
10+
attrs={"date_metadata_modified": "2023-10-05T12:34:56-05:00"},
11+
)
12+
valid_dataset_4 = xr.Dataset() # No date attributes
13+
14+
15+
invalid_dataset_0 = xr.Dataset(attrs={"date_created": "10/05/2023"})
16+
invalid_dataset_1 = xr.Dataset(attrs={"date_issued": "2023-13-05"})
17+
invalid_dataset_2 = xr.Dataset(attrs={"date_modified": "2023-10-05 12:34:56"})
18+
invalid_dataset_3 = xr.Dataset(attrs={"date_metadata_modified": "2023/10/05"})
19+
invalid_dataset_4 = xr.Dataset(
20+
attrs={"date_created": "2023-10-05T25:34:56Z"},
21+
) # Invalid hour
22+
23+
IsoDatesTest = RuleTester.define_test(
24+
"1.3-dates-iso-format",
25+
IsoDates,
26+
valid=[
27+
RuleTest(dataset=valid_dataset_0),
28+
RuleTest(dataset=valid_dataset_1),
29+
RuleTest(dataset=valid_dataset_2),
30+
RuleTest(dataset=valid_dataset_3),
31+
RuleTest(dataset=valid_dataset_4),
32+
],
33+
invalid=[
34+
RuleTest(
35+
dataset=invalid_dataset_0,
36+
expected=["Attribute 'date_created' is not in ISO format: '10/05/2023'"],
37+
),
38+
RuleTest(
39+
dataset=invalid_dataset_1,
40+
expected=["Attribute 'date_issued' is not in ISO format: '2023-13-05'"],
41+
),
42+
RuleTest(
43+
dataset=invalid_dataset_2,
44+
expected=[
45+
"Attribute 'date_modified' is not in ISO format: '2023-10-05 12:34:56'",
46+
],
47+
),
48+
RuleTest(
49+
dataset=invalid_dataset_3,
50+
expected=[
51+
"Attribute 'date_metadata_modified' is not in ISO format: '2023/10/05'",
52+
],
53+
),
54+
RuleTest(
55+
dataset=invalid_dataset_4,
56+
expected=[
57+
"Attribute 'date_created' is not in ISO format: '2023-10-05T25:34:56Z'",
58+
],
59+
),
60+
],
61+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import xarray as xr
2+
from xrlint.testing import RuleTest, RuleTester
3+
4+
from xrlint.plugins.acdd.rules.metadata_link import MetadataLink
5+
6+
valid_dataset_0 = xr.Dataset(attrs={"metadata_link": "http://example.com/metadata"})
7+
valid_dataset_1 = xr.Dataset(attrs={"metadata_link": "https://example.com/metadata"})
8+
valid_dataset_2 = xr.Dataset()
9+
10+
invalid_dataset_0 = xr.Dataset(attrs={"metadata_link": "example.com/metadata"})
11+
12+
Metadata_LinkTest = RuleTester.define_test(
13+
"1.3-metadata-link",
14+
MetadataLink,
15+
valid=[
16+
RuleTest(dataset=valid_dataset_0),
17+
RuleTest(dataset=valid_dataset_1),
18+
RuleTest(dataset=valid_dataset_2),
19+
],
20+
invalid=[
21+
RuleTest(
22+
dataset=invalid_dataset_0,
23+
expected=[
24+
"Metadata URL should include http:// or https://: 'example.com/metadata'",
25+
],
26+
),
27+
],
28+
)

0 commit comments

Comments
 (0)