Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- Added more rules
- core rule "flags"
- core rule "lon-coordinate"
- core rule "lat-coordinate"
- core rule "time-coordinate" (#15)
- xcube rule "time-naming" (#15)

Expand Down
14 changes: 14 additions & 0 deletions docs/rule-ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ Grid mappings, if any, shall have valid grid mapping coordinate variables.

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

### :material-bug: `lat-coordinate`

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

Contained in: `all`-:material-lightning-bolt:

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

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

Contained in: `all`-:material-lightning-bolt:

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

Every dataset element should have metadata that describes it.
Expand Down
16 changes: 14 additions & 2 deletions notebooks/mkdataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,18 @@ def make_dataset() -> xr.Dataset:
attrs=dict(title="SST-Climatology Subset"),
coords={
"x": xr.DataArray(
np.linspace(-180, 180, nx), dims="x", attrs={"units": "degrees"}
np.linspace(-180, 180, nx), dims="x", attrs={
"standard_name": "longitude",
"long_name": "longitude",
"units": "degrees_east"
}
),
"y": xr.DataArray(
np.linspace(-90, 90, ny), dims="y", attrs={"units": "degrees"}
np.linspace(-90, 90, ny), dims="y", attrs={
"standard_name": "latitude",
"long_name": "latitude",
"units": "degrees_north"
}
),
"time": xr.DataArray(
[365 * i for i in range(nt)],
Expand Down Expand Up @@ -55,6 +63,10 @@ def make_dataset() -> xr.Dataset:
def make_dataset_with_issues() -> xr.Dataset:
"""Create a dataset that produces issues with xrlint core rules."""
invalid_ds = make_dataset()
invalid_ds.x.attrs["units"] = "degrees"
invalid_ds.x.attrs["axis"] = "x"
del invalid_ds.y.attrs["standard_name"]
invalid_ds.y.attrs["axis"] = "y"
invalid_ds.time.attrs["units"] = "days since 2020-01-01 ß0:000:00"
invalid_ds.attrs = {}
invalid_ds.sst.attrs["units"] = 1
Expand Down
164 changes: 87 additions & 77 deletions notebooks/xrlint-linter.ipynb

Large diffs are not rendered by default.

111 changes: 111 additions & 0 deletions tests/plugins/core/rules/test_lat_lon_coordinate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import numpy as np
import xarray as xr

from xrlint.plugins.core.rules.lat_lon_coordinate import LatCoordinate
from xrlint.plugins.core.rules.lat_lon_coordinate import LonCoordinate
from xrlint.testing import RuleTest
from xrlint.testing import RuleTester

valid_dataset_0 = xr.Dataset()
valid_dataset_1 = xr.Dataset(
coords={
"lat": xr.DataArray(
np.array([3, 4, 5]),
dims="lat",
attrs={
"units": "degrees_north",
"standard_name": "latitude",
"long_name": "latitude",
},
),
"lon": xr.DataArray(
np.array([-2, -1, 0, 1]),
dims="lon",
attrs={
"units": "degrees_east",
"standard_name": "longitude",
"long_name": "longitude",
},
),
},
data_vars={
"mask": xr.DataArray(
[[10, 20, 30, 40], [30, 40, 50, 60], [50, 60, 70, 80]], dims=("lat", "lon")
)
},
)

# Valid, because the coord names doesn't matter as long their metadata is ok
valid_dataset_2 = valid_dataset_1.rename_vars({"lon": "x", "lat": "y"})

# Valid, because the coord units have aliases
valid_dataset_3 = valid_dataset_1.copy()
valid_dataset_3.lat.attrs["units"] = "degreeN"
valid_dataset_3.lon.attrs["units"] = "degreeE"

# Valid, because the coord units have aliases
valid_dataset_4 = valid_dataset_1.copy()
del valid_dataset_4.lat.attrs["standard_name"]
del valid_dataset_4.lon.attrs["standard_name"]
valid_dataset_4.lat.attrs["axis"] = "Y"
valid_dataset_4.lon.attrs["axis"] = "X"

invalid_lat_dataset_0 = valid_dataset_1.copy()
del invalid_lat_dataset_0.lat.attrs["standard_name"]

invalid_lon_dataset_0 = valid_dataset_1.copy()
del invalid_lon_dataset_0.lon.attrs["units"]

invalid_lat_dataset_1 = valid_dataset_1.copy()
invalid_lat_dataset_1.lat.attrs["units"] = "deg"

invalid_lon_dataset_1 = valid_dataset_1.copy()
invalid_lon_dataset_1.lon.attrs["long_name"] = "poo"

invalid_lat_dataset_2 = valid_dataset_1.copy()
del invalid_lat_dataset_2.lat.attrs["standard_name"]
invalid_lat_dataset_2.lat.attrs["axis"] = "y" # should be "Y"

invalid_lon_dataset_2 = valid_dataset_1.copy()
del invalid_lon_dataset_2.lon.attrs["standard_name"]
invalid_lon_dataset_2.lon.attrs["axis"] = "x" # should be "X"

LatCoordsTest = RuleTester.define_test(
"lat-coordinate",
LatCoordinate,
valid=[
RuleTest(dataset=valid_dataset_0),
RuleTest(dataset=valid_dataset_1),
RuleTest(dataset=valid_dataset_2),
RuleTest(dataset=valid_dataset_3),
RuleTest(dataset=valid_dataset_4),
RuleTest(dataset=invalid_lon_dataset_0),
RuleTest(dataset=invalid_lon_dataset_1),
RuleTest(dataset=invalid_lon_dataset_2),
],
invalid=[
RuleTest(dataset=invalid_lat_dataset_0),
RuleTest(dataset=invalid_lat_dataset_1),
RuleTest(dataset=invalid_lat_dataset_2),
],
)

LonCoordsTest = RuleTester.define_test(
"lon-coordinate",
LonCoordinate,
valid=[
RuleTest(dataset=valid_dataset_0),
RuleTest(dataset=valid_dataset_1),
RuleTest(dataset=valid_dataset_2),
RuleTest(dataset=valid_dataset_3),
RuleTest(dataset=valid_dataset_4),
RuleTest(dataset=invalid_lat_dataset_0),
RuleTest(dataset=invalid_lat_dataset_1),
RuleTest(dataset=invalid_lat_dataset_2),
],
invalid=[
RuleTest(dataset=invalid_lon_dataset_0),
RuleTest(dataset=invalid_lon_dataset_1),
RuleTest(dataset=invalid_lon_dataset_2),
],
)
37 changes: 24 additions & 13 deletions tests/plugins/core/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,38 @@


class ExportPluginTest(TestCase):
def test_configs_complete(self):
_plugin = export_plugin()
self.assertEqual(
{
"all",
"recommended",
},
set(_plugin.configs.keys()),
)

def test_rules_complete(self):
_plugin = export_plugin()
plugin = export_plugin()
self.assertEqual(
{
"coords-for-dims",
"dataset-title-attr",
"grid-mappings",
"flags",
"grid-mappings",
"lat-coordinate",
"lon-coordinate",
"no-empty-attrs",
"time-coordinate",
"var-units-attr",
},
set(_plugin.rules.keys()),
set(plugin.rules.keys()),
)

def test_configs_complete(self):
plugin = export_plugin()
self.assertEqual(
{
"all",
"recommended",
},
set(plugin.configs.keys()),
)
all_rule_names = set(plugin.rules.keys())
self.assertEqual(
all_rule_names,
set(plugin.configs["all"].rules.keys()),
)
self.assertEqual(
all_rule_names,
set(plugin.configs["recommended"].rules.keys()),
)
32 changes: 21 additions & 11 deletions tests/plugins/xcube/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,9 @@


class ExportPluginTest(TestCase):
def test_configs_complete(self):
_plugin = export_plugin()
self.assertEqual(
{
"all",
"recommended",
},
set(_plugin.configs.keys()),
)

def test_rules_complete(self):
_plugin = export_plugin()
plugin = export_plugin()
self.assertEqual(
{
"any-spatial-data-var",
Expand All @@ -27,5 +18,24 @@ def test_rules_complete(self):
"single-grid-mapping",
"time-naming",
},
set(_plugin.rules.keys()),
set(plugin.rules.keys()),
)

def test_configs_complete(self):
plugin = export_plugin()
self.assertEqual(
{
"all",
"recommended",
},
set(plugin.configs.keys()),
)
all_rule_names = set(f"xcube/{k}" for k in plugin.rules.keys())
self.assertEqual(
all_rule_names,
set(plugin.configs["all"].rules.keys()),
)
self.assertEqual(
all_rule_names,
set(plugin.configs["recommended"].rules.keys()),
)
3 changes: 3 additions & 0 deletions xrlint/plugins/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ def export_plugin() -> Plugin:
"rules": {
"coords-for-dims": "error",
"dataset-title-attr": "warn",
"flags": "error",
"grid-mappings": "error",
"lat-coordinate": "error",
"lon-coordinate": "error",
"no-empty-attrs": "warn",
"time-coordinate": "error",
"var-units-attr": "warn",
Expand Down
Loading
Loading