Skip to content

Commit 3e82c2c

Browse files
authored
Merge pull request #28 from bcdev/forman-write_empty_chunks_rule
Add rule "no-empty-chunks"
2 parents e4f0ee4 + 4577887 commit 3e82c2c

10 files changed

Lines changed: 108 additions & 25 deletions

File tree

CHANGES.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
## Version 0.3.0 (in development)
44

55
- Added more rules
6-
- core rule "flags"
7-
- core rule "lon-coordinate"
8-
- core rule "lat-coordinate"
9-
- core rule "time-coordinate" (#15)
6+
- core/CF rule "flags"
7+
- core/CF rule "lon-coordinate"
8+
- core/CF rule "lat-coordinate"
9+
- core/CF rule "time-coordinate" (#15)
10+
- core rule "no-empty-chunks"
1011
- xcube rule "time-naming" (#15)
1112

1213
- Fixed problem where referring to values in modules via

docs/rule-ref.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Contained in: `all`-:material-lightning-bolt: `recommended`-:material-alert:
2222
Validate attributes 'flag_values', 'flag_masks' and 'flag_meanings' that make variables that contain flag values self describing.
2323
[:material-information-variant:](https://cfconventions.org/cf-conventions/cf-conventions.html#flags)
2424

25-
Contained in: `all`-:material-lightning-bolt:
25+
Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning-bolt:
2626

2727
### :material-bug: `grid-mappings`
2828

@@ -33,27 +33,34 @@ Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning
3333
### :material-bug: `lat-coordinate`
3434

3535
Latitude coordinate should have standard units and standard names.
36-
[More information.](https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate)
36+
[:material-information-variant:](https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate)
3737

38-
Contained in: `all`-:material-lightning-bolt:
38+
Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning-bolt:
3939

4040
### :material-bug: `lon-coordinate`
4141

4242
Longitude coordinate should have standard units and standard names.
43-
[More information.](https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate)
43+
[:material-information-variant:](https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate)
4444

45-
Contained in: `all`-:material-lightning-bolt:
45+
Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning-bolt:
4646

4747
### :material-lightbulb: `no-empty-attrs`
4848

4949
Every dataset element should have metadata that describes it.
5050

5151
Contained in: `all`-:material-lightning-bolt: `recommended`-:material-alert:
5252

53+
### :material-lightbulb: `no-empty-chunks`
54+
55+
Empty chunks should not be encoded and written. The rule currently applies to Zarr format only.
56+
[:material-information-variant:](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html#xarray-dataset-to-zarr)
57+
58+
Contained in: `all`-:material-lightning-bolt: `recommended`-:material-alert:
59+
5360
### :material-bug: `time-coordinate`
5461

5562
Time coordinate (standard_name='time') should have unambiguous time units encoding.
56-
[More information.](https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate)
63+
[:material-information-variant:](https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate)
5764

5865
Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning-bolt:
5966

@@ -117,7 +124,7 @@ Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning
117124
### :material-bug: `time-naming`
118125

119126
Time coordinate and dimension should be called 'time'.
120-
[More information.](https://xcube.readthedocs.io/en/latest/cubespec.html#temporal-reference)
127+
[:material-information-variant:](https://xcube.readthedocs.io/en/latest/cubespec.html#temporal-reference)
121128

122129
Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning-bolt:
123130

notebooks/mkdataset.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,22 @@ def make_dataset() -> xr.Dataset:
1313
attrs=dict(title="SST-Climatology Subset"),
1414
coords={
1515
"x": xr.DataArray(
16-
np.linspace(-180, 180, nx), dims="x", attrs={
16+
np.linspace(-180, 180, nx),
17+
dims="x",
18+
attrs={
1719
"standard_name": "longitude",
1820
"long_name": "longitude",
19-
"units": "degrees_east"
20-
}
21+
"units": "degrees_east",
22+
},
2123
),
2224
"y": xr.DataArray(
23-
np.linspace(-90, 90, ny), dims="y", attrs={
25+
np.linspace(-90, 90, ny),
26+
dims="y",
27+
attrs={
2428
"standard_name": "latitude",
2529
"long_name": "latitude",
26-
"units": "degrees_north"
27-
}
30+
"units": "degrees_north",
31+
},
2832
),
2933
"time": xr.DataArray(
3034
[365 * i for i in range(nt)],
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import xarray as xr
2+
3+
from xrlint.plugins.core.rules.no_empty_chunks import NoEmptyChunks
4+
from xrlint.testing import RuleTest, RuleTester
5+
6+
# valid, because it does not write empty chunks
7+
valid_dataset_0 = xr.Dataset(attrs=dict(title="OC-Climatology"))
8+
valid_dataset_0.encoding["source"] = "test.zarr"
9+
valid_dataset_0["sst"] = xr.DataArray([273, 274, 272], dims="time")
10+
valid_dataset_0["sst"].encoding["_FillValue"] = 0
11+
valid_dataset_0["sst"].encoding["chunks"] = [
12+
1,
13+
]
14+
valid_dataset_0["sst"].encoding["write_empty_chunks"] = False
15+
# valid, because it does not apply
16+
valid_dataset_1 = valid_dataset_0.copy()
17+
del valid_dataset_1.encoding["source"]
18+
# valid, because it does not apply
19+
valid_dataset_2 = valid_dataset_0.copy()
20+
valid_dataset_2.encoding["source"] = "test.nc"
21+
# valid, because it does not apply
22+
valid_dataset_3 = valid_dataset_0.copy()
23+
valid_dataset_3.sst.encoding["write_empty_chunks"] = True
24+
25+
# valid, because it does not apply
26+
invalid_dataset_0 = valid_dataset_0.copy()
27+
del invalid_dataset_0.sst.encoding["write_empty_chunks"]
28+
29+
NoEmptyChunksTest = RuleTester.define_test(
30+
"no-empty-chunks",
31+
NoEmptyChunks,
32+
valid=[
33+
RuleTest(dataset=valid_dataset_0),
34+
RuleTest(dataset=valid_dataset_1),
35+
RuleTest(dataset=valid_dataset_2),
36+
RuleTest(dataset=valid_dataset_3),
37+
],
38+
invalid=[
39+
RuleTest(dataset=invalid_dataset_0),
40+
],
41+
)

tests/plugins/core/test_plugin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def test_rules_complete(self):
1616
"lon-coordinate",
1717
"no-empty-attrs",
1818
"time-coordinate",
19+
"no-empty-chunks",
1920
"var-units-attr",
2021
},
2122
set(plugin.rules.keys()),

tests/plugins/xcube/test_plugin.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55

66
class ExportPluginTest(TestCase):
7-
87
def test_rules_complete(self):
98
plugin = export_plugin()
109
self.assertEqual(

xrlint/plugins/core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def export_plugin() -> Plugin:
1919
"lat-coordinate": "error",
2020
"lon-coordinate": "error",
2121
"no-empty-attrs": "warn",
22+
"no-empty-chunks": "warn",
2223
"time-coordinate": "error",
2324
"var-units-attr": "warn",
2425
},

xrlint/plugins/core/rules/flags.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ def _validate_flag_values(
7070
) -> int | None:
7171
if not has_meanings:
7272
ctx.report(
73-
f"Missing attribute {FLAG_MEANINGS!r} to explain"
74-
f" attribute {FLAG_VALUES!r}"
73+
f"Missing attribute {FLAG_MEANINGS!r} to explain attribute {FLAG_VALUES!r}"
7574
)
7675
type_ok, flag_count = _check_values(flag_values)
7776
if not type_ok or flag_count is None:
@@ -87,8 +86,7 @@ def _validate_flag_masks(
8786
) -> int | None:
8887
if not has_meanings:
8988
ctx.report(
90-
f"Missing attribute {FLAG_MEANINGS!r} to explain"
91-
f" attribute {FLAG_MASKS!r}"
89+
f"Missing attribute {FLAG_MEANINGS!r} to explain attribute {FLAG_MASKS!r}"
9290
)
9391
type_ok, flag_masks_count = _check_values(flag_masks)
9492
if not type_ok or flag_masks_count is None:
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from xrlint.node import DataArrayNode
2+
from xrlint.plugins.core.rules import plugin
3+
from xrlint.rule import RuleContext, RuleExit, RuleOp
4+
5+
6+
@plugin.define_rule(
7+
"no-empty-chunks",
8+
version="1.0.0",
9+
type="suggestion",
10+
description=(
11+
"Empty chunks should not be encoded and written."
12+
" The rule currently applies to Zarr format only."
13+
),
14+
docs_url=(
15+
"https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html"
16+
"#xarray-dataset-to-zarr"
17+
),
18+
)
19+
class NoEmptyChunks(RuleOp):
20+
def dataset(self, ctx: RuleContext, node: DataArrayNode):
21+
source = ctx.dataset.encoding.get("source")
22+
is_zarr = isinstance(source, str) and source.endswith(".zarr")
23+
if not is_zarr:
24+
# if not a Zarr, no need to check further
25+
raise RuleExit
26+
27+
def data_array(self, ctx: RuleContext, node: DataArrayNode):
28+
if (
29+
"write_empty_chunks" not in node.data_array.encoding
30+
and "chunks" in node.data_array.encoding
31+
and "_FillValue" in node.data_array.encoding
32+
):
33+
ctx.report("Consider writing the dataset using 'write_empty_chunks=True`.")

xrlint/plugins/core/rules/time_coordinate.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@
1515
" unambiguous time units encoding."
1616
),
1717
docs_url=(
18-
"https://cfconventions.org/cf-conventions/cf-conventions.html"
19-
"#time-coordinate"
18+
"https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate"
2019
),
2120
)
2221
class TimeCoordinate(RuleOp):
2322
def data_array(self, ctx: RuleContext, node: DataArrayNode):
24-
2523
array = node.data_array
2624
attrs = array.attrs
2725
encoding = array.encoding

0 commit comments

Comments
 (0)