Skip to content

Commit b72db3c

Browse files
committed
spatial-dims-order -> cube-dims-order
1 parent 2ac2d4f commit b72db3c

File tree

10 files changed

+101
-45
lines changed

10 files changed

+101
-45
lines changed

tests/plugins/xcube/rules/test_spatial_dims_order.py renamed to tests/plugins/xcube/rules/test_cube_dims_order.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import numpy as np
22

3-
from xrlint.plugins.xcube.rules.spatial_dims_order import SpatialDimsOrder
3+
from xrlint.plugins.xcube.rules.cube_dims_order import CubeDimsOrder
44

55
import xarray as xr
66

@@ -34,24 +34,28 @@ def make_dataset(dims: tuple[str, str, str]):
3434

3535
valid_dataset_1 = make_dataset(("time", "y", "x"))
3636
valid_dataset_2 = make_dataset(("time", "lat", "lon"))
37+
valid_dataset_3 = make_dataset(("level", "y", "x"))
3738

3839
invalid_dataset_1 = make_dataset(("time", "x", "y"))
3940
invalid_dataset_2 = make_dataset(("x", "y", "time"))
4041
invalid_dataset_3 = make_dataset(("time", "lon", "lat"))
41-
invalid_dataset_4 = make_dataset(("lon", "lat", "time"))
42+
invalid_dataset_4 = make_dataset(("lon", "lat", "level"))
43+
invalid_dataset_5 = make_dataset(("x", "y", "level"))
4244

4345

44-
SpatialDimsOrderTest = RuleTester.define_test(
45-
"spatial-dims-order",
46-
SpatialDimsOrder,
46+
CubeDimsOrderTest = RuleTester.define_test(
47+
"cube-dims-order",
48+
CubeDimsOrder,
4749
valid=[
4850
RuleTest(dataset=valid_dataset_1),
4951
RuleTest(dataset=valid_dataset_2),
52+
RuleTest(dataset=valid_dataset_3),
5053
],
5154
invalid=[
5255
RuleTest(dataset=invalid_dataset_1),
5356
RuleTest(dataset=invalid_dataset_2),
5457
RuleTest(dataset=invalid_dataset_3),
5558
RuleTest(dataset=invalid_dataset_4),
59+
RuleTest(dataset=invalid_dataset_5),
5660
],
5761
)

tests/plugins/xcube/test_plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def test_rules_complete(self):
1818
_plugin = export_plugin()
1919
self.assertEqual(
2020
{
21-
"spatial-dims-order",
21+
"cube-dims-order",
2222
},
2323
set(_plugin.rules.keys()),
2424
)

tests/test_linter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def test_new_linter(self):
3333
self.assertEqual({CORE_PLUGIN_NAME, "xcube"}, set(linter.config.plugins.keys()))
3434
self.assertIsInstance(linter.config.rules, dict)
3535
self.assertIn("dataset-title-attr", linter.config.rules)
36-
self.assertIn("xcube/spatial-dims-order", linter.config.rules)
36+
self.assertIn("xcube/cube-dims-order", linter.config.rules)
3737

3838
linter = new_linter(recommended=False)
3939
self.assertIsInstance(linter, xrl.Linter)

tests/test_result.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from xrlint.config import Config
44
from xrlint.plugin import Plugin, PluginMeta
5-
from xrlint.result import get_rules_meta_for_results, Result, Message
5+
from xrlint.result import get_rules_meta_for_results, Result, Message, Suggestion
66
from xrlint.rule import RuleOp, RuleMeta
77

88

@@ -76,3 +76,16 @@ def test_repr_html(self):
7676
self.assertIsInstance(html, str)
7777
self.assertIn("<table>", html)
7878
self.assertIn("</table>", html)
79+
80+
81+
class SuggestionTest(TestCase):
82+
83+
# noinspection PyUnusedLocal
84+
def test_from_value(self):
85+
self.assertEqual(
86+
Suggestion("Use xr.transpose()"),
87+
Suggestion.from_value("Use xr.transpose()"),
88+
)
89+
90+
suggestion = Suggestion("Use xr.transpose()")
91+
self.assertIs(suggestion, Suggestion.from_value(suggestion))

xrlint/_linter/rule_ctx_impl.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,11 @@ def report(
5757
message: str,
5858
*,
5959
fatal: bool | None = None,
60-
suggestions: list[Suggestion] | None = None,
60+
suggestions: list[Suggestion | str] | None = None,
6161
):
62+
suggestions = (
63+
[Suggestion.from_value(s) for s in suggestions] if suggestions else None
64+
)
6265
m = Message(
6366
message=message,
6467
fatal=fatal,

xrlint/plugins/xcube/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def export_plugin() -> Plugin:
1212
{
1313
"name": "recommended",
1414
"rules": {
15-
f"xcube/{rule_id}": "error" for rule_id, rule in plugin.rules.items()
15+
"xcube/cube-dims-order": "error",
1616
},
1717
}
1818
)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from xrlint.node import DataArrayNode
2+
from xrlint.plugins.xcube.rules import plugin
3+
from xrlint.result import Suggestion
4+
from xrlint.rule import RuleOp, RuleContext
5+
6+
7+
@plugin.define_rule(
8+
"cube-dims-order",
9+
version="1.0.0",
10+
description=(
11+
"Order of dimensions in spatio-temporal datacube variables"
12+
" should be [time, ..., y, x]."
13+
),
14+
)
15+
class CubeDimsOrder(RuleOp):
16+
def data_array(self, ctx: RuleContext, node: DataArrayNode):
17+
if node.in_data_vars():
18+
dims = list(node.data_array.dims)
19+
indexes = {d: i for i, d in enumerate(node.data_array.dims)}
20+
21+
yx_names = None
22+
if "x" in indexes and "y" in indexes:
23+
yx_names = ["y", "x"]
24+
elif "lon" in indexes and "lat" in indexes:
25+
yx_names = ["lat", "lon"]
26+
else:
27+
# TODO: get yx_names/yx_indexes from grid-mapping
28+
pass
29+
if yx_names is None:
30+
# This rule only applies to spatial dimensions
31+
return
32+
33+
t_name = None
34+
if "time" in indexes:
35+
t_name = "time"
36+
37+
n = len(dims)
38+
t_index = indexes[t_name] if t_name else None
39+
y_index = indexes[yx_names[0]]
40+
x_index = indexes[yx_names[1]]
41+
42+
yx_ok = y_index == n - 2 and x_index == n - 1
43+
t_ok = t_index is None or t_index == 0
44+
if not yx_ok or not t_ok:
45+
if t_index is None:
46+
expected_dims = [d for d in dims if d not in yx_names] + yx_names
47+
else:
48+
expected_dims = (
49+
[t_name]
50+
+ [d for d in dims if d != t_name and d not in yx_names]
51+
+ yx_names
52+
)
53+
# noinspection PyTypeChecker
54+
ctx.report(
55+
f"order of dimensions should be"
56+
f" {','.join(expected_dims)}, but was {','.join(dims)}",
57+
suggestions=["Use xarray.transpose(...) to reorder dimensions."],
58+
)

xrlint/plugins/xcube/rules/spatial_dims_order.py

Lines changed: 0 additions & 32 deletions
This file was deleted.

xrlint/result.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import dataclass, field
2-
from typing import Literal, TYPE_CHECKING
2+
from typing import Literal, TYPE_CHECKING, Any
33
import html
44

55
from tabulate import tabulate
@@ -8,6 +8,7 @@
88
from xrlint.constants import SEVERITY_ERROR
99
from xrlint.constants import SEVERITY_WARN
1010
from xrlint.util.formatting import format_problems
11+
from xrlint.util.formatting import format_message_type_of
1112
from xrlint.util.todict import ToDictMixin
1213

1314
if TYPE_CHECKING: # pragma: no cover
@@ -31,6 +32,14 @@ class Suggestion(ToDictMixin):
3132
fix: EditInfo | None = None
3233
"""Not used yet."""
3334

35+
@classmethod
36+
def from_value(cls, value: Any):
37+
if isinstance(value, Suggestion):
38+
return value
39+
if isinstance(value, str):
40+
return Suggestion(value)
41+
raise TypeError(format_message_type_of("value", value, "Suggestion|str"))
42+
3443

3544
@dataclass(kw_only=True)
3645
class Message(ToDictMixin):

xrlint/rule.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,16 @@ def report(
4040
message: str,
4141
*,
4242
fatal: bool | None = None,
43-
suggestions: list[Suggestion] | None = None,
43+
suggestions: list[Suggestion | str] | None = None,
4444
):
4545
"""Report an issue.
4646
4747
Args:
4848
message: mandatory message text
4949
fatal: True, if a fatal error is reported.
5050
suggestions: A list of suggestions for the user
51-
on how to fix the reported issue.
51+
on how to fix the reported issue. Items may
52+
be of type `Suggestion` or `str`.
5253
"""
5354

5455

0 commit comments

Comments
 (0)