Skip to content

Commit b9c865a

Browse files
committed
Started two new rules
1 parent 9e52cfe commit b9c865a

4 files changed

Lines changed: 158 additions & 0 deletions

File tree

docs/rule-ref.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,32 @@ New rules will be added by upcoming XRLint releases.
55

66
## Core Rules
77

8+
### :material-lightbulb: `conventions`
9+
10+
Datasets should identify the applicable conventions using the `Conventions` attribute.
11+
The rule has an optional configuration parameter `match` which is a regex pattern that the value of the `Conventions` attribute must match, if any.
12+
[:material-information-variant:](https://cfconventions.org/cf-conventions/cf-conventions.html#identification-of-conventions)
13+
14+
Contained in: `all`-:material-lightning-bolt:
15+
816
### :material-bug: `coords-for-dims`
917

1018
Dimensions of data variables should have corresponding coordinates.
1119

1220
Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning-bolt:
1321

22+
### :material-lightbulb: `dataset-description`
23+
24+
A dataset should provide information about where the data came from and what has been done to it. This information is mainly for the benefit of human readers. The rule accepts the following configuration parameters:
25+
26+
- `global_attrs`: list of global attribute names. Defaults to `['title', 'history']`.
27+
- `var_attrs`: list of variable attribute names. Defaults to `['institution', 'source', 'references', 'comment']`.
28+
- `ignored_vars`: list of ignored variables (regex patterns). Defaults to `['crs', 'spatial_ref']`.
29+
30+
[:material-information-variant:](https://cfconventions.org/cf-conventions/cf-conventions.html#description-of-file-contents)
31+
32+
Contained in: `all`-:material-lightning-bolt:
33+
1434
### :material-lightbulb: `dataset-title-attr`
1535

1636
Datasets should be given a non-empty title.
@@ -67,6 +87,7 @@ Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning
6787
### :material-lightbulb: `var-units-attr`
6888

6989
Every variable should have a valid 'units' attribute.
90+
[:material-information-variant:](https://cfconventions.org/cf-conventions/cf-conventions.html#units)
7091

7192
Contained in: `all`-:material-lightning-bolt: `recommended`-:material-alert:
7293

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import re
2+
3+
4+
from xrlint.node import DatasetNode
5+
from xrlint.plugins.core.plugin import plugin
6+
from xrlint.rule import RuleContext, RuleOp, RuleExit
7+
from xrlint.util.schema import schema
8+
9+
DEFAULT_ATTR_NAMES = ["Conventions", "title", "source", "history", "name"]
10+
11+
12+
@plugin.define_rule(
13+
"conventions",
14+
version="1.0.0",
15+
type="suggestion",
16+
description=(
17+
"Datasets should identify the applicable conventions"
18+
" using the `Conventions` attribute.\n"
19+
" The rule has an optional configuration parameter `match` which"
20+
" is a regex pattern that the value of the `Conventions` attribute"
21+
" must match, if any."
22+
),
23+
docs_url=(
24+
"https://cfconventions.org/cf-conventions/cf-conventions.html"
25+
"#identification-of-conventions"
26+
),
27+
schema=schema(
28+
"object", properties=dict(match=schema("string", title="Regex pattern"))
29+
),
30+
)
31+
class Conventions(RuleOp):
32+
def __init__(self, match: str | None = None):
33+
self.match = re.compile(match) if match else None
34+
35+
def dataset(self, ctx: RuleContext, node: DatasetNode):
36+
if "Conventions" not in node.dataset.attrs:
37+
ctx.report("Missing attribute 'Conventions'.")
38+
else:
39+
conventions_spec = node.dataset.attrs.get("Conventions")
40+
if not isinstance(conventions_spec, str):
41+
ctx.report(
42+
f"Invalid attribute 'Conventions':"
43+
f" expected string, got value {conventions_spec!r}."
44+
)
45+
elif self.match is not None and not self.match.match(conventions_spec):
46+
ctx.report(
47+
f"Invalid attribute 'Conventions':"
48+
f" {conventions_spec!r} doesn't match pattern {self.match.pattern!r}."
49+
)
50+
raise RuleExit
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import re
2+
3+
from xrlint.node import DataArrayNode, DatasetNode
4+
from xrlint.plugins.core.plugin import plugin
5+
from xrlint.rule import RuleContext, RuleOp
6+
from xrlint.util.schema import schema
7+
8+
DEFAULT_GLOBAL_ATTRS = ["title", "history"]
9+
DEFAULT_VAR_ATTRS = ["institution", "source", "references", "comment"]
10+
DEFAULT_IGNORES = ["crs", "spatial_ref"]
11+
12+
13+
@plugin.define_rule(
14+
"dataset-description",
15+
version="1.0.0",
16+
type="suggestion",
17+
description=(
18+
"A dataset should provide information about where the data came"
19+
" from and what has been done to it."
20+
" This information is mainly for the benefit of human readers."
21+
" The rule accepts the following configuration parameters:\n\n"
22+
"- `global_attrs`: list of global attribute names."
23+
f" Defaults to `{DEFAULT_GLOBAL_ATTRS}`.\n"
24+
"- `var_attrs`: list of variable attribute names."
25+
f" Defaults to `{DEFAULT_VAR_ATTRS}`.\n"
26+
"- `ignored_vars`: list of ignored variables (regex patterns)."
27+
f" Defaults to `{DEFAULT_IGNORES}`.\n"
28+
""
29+
),
30+
docs_url=(
31+
"https://cfconventions.org/cf-conventions/cf-conventions.html"
32+
"#description-of-file-contents"
33+
),
34+
schema=schema(
35+
"object",
36+
properties={
37+
"global_attrs": schema(
38+
"array",
39+
items=schema("string"),
40+
default=DEFAULT_GLOBAL_ATTRS,
41+
title="Global dataset attribute names",
42+
),
43+
"var_attrs": schema(
44+
"array",
45+
items=schema("string"),
46+
default=DEFAULT_VAR_ATTRS,
47+
title="Data variable attribute names",
48+
),
49+
"ignored_vars": schema(
50+
"array",
51+
items=schema("string"),
52+
default=DEFAULT_IGNORES,
53+
title="Ignored variables (regex name patterns)",
54+
),
55+
},
56+
),
57+
)
58+
class DatasetDescription(RuleOp):
59+
def __init__(
60+
self, global_attrs: list[str] | None = None, var_attrs: list[str] | None = None
61+
):
62+
self.global_attrs = global_attrs if global_attrs else DEFAULT_GLOBAL_ATTRS
63+
self.var_attrs = var_attrs if var_attrs else DEFAULT_VAR_ATTRS
64+
self.ignored_vars = [
65+
re.compile(p) for p in (var_attrs if var_attrs else DEFAULT_VAR_ATTRS)
66+
]
67+
68+
def dataset(self, ctx: RuleContext, node: DatasetNode):
69+
dataset = node.dataset
70+
for attr_name in self.global_attrs:
71+
if attr_name not in dataset.attrs:
72+
ctx.report(f"Missing dataset attribute {attr_name!r}.")
73+
74+
def data_array(self, ctx: RuleContext, node: DataArrayNode):
75+
dataset = ctx.dataset
76+
if node.name not in dataset.data_vars:
77+
return
78+
79+
for m in self.ignored_vars:
80+
if m.match(str(node.name)):
81+
return
82+
83+
var = node.data_array
84+
for attr_name in self.var_attrs:
85+
if attr_name not in var.attrs and attr_name not in dataset.attrs:
86+
ctx.report(f"Missing data variable attribute {attr_name!r}.")

xrlint/plugins/core/rules/var_units_attr.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
version="1.0.0",
99
type="suggestion",
1010
description="Every variable should have a valid 'units' attribute.",
11+
docs_url="https://cfconventions.org/cf-conventions/cf-conventions.html#units",
1112
)
1213
class VarUnitsAttr(RuleOp):
1314
def data_array(self, ctx: RuleContext, node: DataArrayNode):

0 commit comments

Comments
 (0)