From 0aa216e751d887149749b8ff97f1f86d3bd126a0 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Thu, 30 Jan 2025 17:52:30 +0100 Subject: [PATCH 01/12] Starting 0.4.2 --- CHANGES.md | 2 ++ xrlint/version.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 7a90756..a1c95a1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ # XRLint Change History +## Version 0.4.2 (in development) + ## Version 0.4.1 (from 2025-01-31) ### Changes diff --git a/xrlint/version.py b/xrlint/version.py index 45dc5ad..8a96ab0 100644 --- a/xrlint/version.py +++ b/xrlint/version.py @@ -1 +1 @@ -version = "0.4.1" +version = "0.4.2.dev0" From 9d69a448912226e8088f4c2ac651eda458195b11 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Thu, 30 Jan 2025 17:54:18 +0100 Subject: [PATCH 02/12] Updated notebooks --- notebooks/mkdataset.py | 35 +- notebooks/xrlint-cli.ipynb | 119 ++++++- notebooks/xrlint-linter.ipynb | 632 ++++++++++++++++++++++++++++------ 3 files changed, 650 insertions(+), 136 deletions(-) diff --git a/notebooks/mkdataset.py b/notebooks/mkdataset.py index 28d4c24..0b0aa6c 100644 --- a/notebooks/mkdataset.py +++ b/notebooks/mkdataset.py @@ -1,16 +1,27 @@ import numpy as np import xarray as xr -nx = 2 -ny = 3 -nt = 4 +nx = 720 +ny = 360 +nt = 5 +ncx = 180 +ncy = 180 +nct = 1 def make_dataset() -> xr.Dataset: """Create a dataset that passes xrlint core rules.""" return xr.Dataset( - attrs=dict(title="SST-Climatology Subset"), + attrs=dict( + Conventions="CF-1.10", + title="SST-Climatology Subset", + history="2025-01-31 17:31:00 - created;", + institution="BC", + source="SST CCI L4", + references="https://climate.esa.int/en/projects/sea-surface-temperature/", + comment="Demo dataset", + ), coords={ "x": xr.DataArray( np.linspace(-180, 180, nx), @@ -53,15 +64,25 @@ def make_dataset() -> xr.Dataset: "sst": xr.DataArray( np.random.random((nt, ny, nx)), dims=["time", "y", "x"], - attrs={"units": "kelvin", "grid_mapping": "spatial_ref"}, + attrs={ + "standard_name": "sea_surface_temperature", + "long_name": "sea surface temperature", + "units": "kelvin", + "grid_mapping": "spatial_ref", + }, ), "sst_anomaly": xr.DataArray( np.random.random((nt, ny, nx)), dims=["time", "y", "x"], - attrs={"units": "kelvin", "grid_mapping": "spatial_ref"}, + attrs={ + "standard_name": "sea_surface_temperature_anomaly", + "long_name": "sea surface temperature anomaly", + "units": "kelvin", + "grid_mapping": "spatial_ref", + }, ), }, - ) + ).chunk(time=nct, y=ncy, x=ncx) def make_dataset_with_issues() -> xr.Dataset: diff --git a/notebooks/xrlint-cli.ipynb b/notebooks/xrlint-cli.ipynb index 0118519..7f9a2ce 100644 --- a/notebooks/xrlint-cli.ipynb +++ b/notebooks/xrlint-cli.ipynb @@ -89,10 +89,10 @@ "metadata": {}, "outputs": [ { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - "Configuration template written to xrlint_config.yaml\n" + "Error: file xrlint_config.yaml already exists.\n" ] } ], @@ -108,7 +108,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -119,7 +119,10 @@ "source": [ "from mkdataset import make_dataset, make_dataset_with_issues\n", "\n", - "make_dataset().to_zarr(\"valid.zarr\", mode=\"w\")\n", + "make_dataset().to_zarr(\"valid.zarr\", mode=\"w\", encoding=dict(\n", + " sst=dict(write_empty_chunks=False), \n", + " sst_anomaly=dict(write_empty_chunks=False)\n", + "))\n", "make_dataset_with_issues().to_zarr(\"invalid.zarr\", mode=\"w\")" ] }, @@ -133,14 +136,47 @@ "output_type": "stream", "text": [ "\n", - "valid.zarr - ok\n", + "valid.zarr:\n", + "dataset.coords['x'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", + "dataset.coords['y'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", + "dataset.data_vars['sst'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", + "dataset.data_vars['sst_anomaly'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", + "dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units\n", "\n", "invalid.zarr:\n", - "dataset warn Missing 'title' attribute in dataset. dataset-title-attr\n", - "dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs\n", - "dataset.data_vars['sst'] warn Invalid 'units' attribute in variable 'sst'. var-units-attr\n", + "dataset warn Missing attribute 'title'. content-desc\n", + "dataset warn Missing attribute 'history'. content-desc\n", + "dataset.data_vars['sst'] warn Missing attribute 'institution'. content-desc\n", + "dataset.data_vars['sst'] warn Missing attribute 'source'. content-desc\n", + "dataset.data_vars['sst'] warn Missing attribute 'references'. content-desc\n", + "dataset.data_vars['sst'] warn Missing attribute 'comment'. content-desc\n", + "dataset.data_vars['sst_anomaly'] warn Missing attribute 'institution'. content-desc\n", + "dataset.data_vars['sst_anomaly'] warn Missing attribute 'source'. content-desc\n", + "dataset.data_vars['sst_anomaly'] warn Missing attribute 'references'. content-desc\n", + "dataset.data_vars['sst_anomaly'] warn Missing attribute 'comment'. content-desc\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'institution'. content-desc\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'source'. content-desc\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'references'. content-desc\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'comment'. content-desc\n", + "dataset warn Missing attribute 'Conventions'. conventions\n", + "dataset warn Missing 'title' attribute in dataset. dataset-title-attr\n", + "dataset.coords['y'] error Attribute 'standard_name' should be 'latitude', was None. lat-coordinate\n", + "dataset.coords['y'] error Attribute 'axis' should be 'Y', was 'y'. lat-coordinate\n", + "dataset.coords['x'] error Attribute 'units' should be 'degrees_east', was 'degrees'. lon-coordinate\n", + "dataset.coords['x'] error Attribute 'axis' should be 'X', was 'x'. lon-coordinate\n", + "dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs\n", + "dataset.coords['x'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", + "dataset.coords['y'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", + "dataset.data_vars['sst'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", + "dataset.data_vars['sst_anomaly'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", + "dataset.data_vars['sst_avg'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", + "dataset.coords['time'] error Invalid 'units' attribute: 'days since 2020-01-01 UTC'. time-coordinate\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'standard_name'. var-desc\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'long_name'. var-desc\n", + "dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units\n", + "dataset.data_vars['sst'] warn Invalid 'units' attribute in variable 'sst'. var-units\n", "\n", - "3 warnings\n", + "36 problems (5 errors and 31 warnings)\n", "\n" ] } @@ -161,18 +197,55 @@ "
\n", "

Results

\n", "
\n", - "

valid.zarr - ok

\n", + "

valid.zarr:

\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
dataset.coords['x'] warnConsider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.coords['y'] warnConsider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.data_vars['sst'] warnConsider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.data_vars['sst_anomaly']warnConsider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.coords['time'] warnMissing 'units' attribute in variable 'time'. var-units

5 warnings

\n", "
\n", "
\n", "
\n", "

invalid.zarr:

\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "
dataset warnMissing 'title' attribute in dataset. dataset-title-attr
dataset.attrs warnMissing metadata, attributes are empty. no-empty-attrs
dataset.data_vars['sst']warnInvalid 'units' attribute in variable 'sst'.var-units-attr
dataset warn Missing attribute 'title'. content-desc
dataset warn Missing attribute 'history'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'source'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'references'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'comment'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'source'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'references'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'comment'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'source'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'references'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'comment'. content-desc
dataset warn Missing attribute 'Conventions'. conventions
dataset warn Missing 'title' attribute in dataset. dataset-title-attr
dataset.coords['y'] errorAttribute 'standard_name' should be 'latitude', was None. lat-coordinate
dataset.coords['y'] errorAttribute 'axis' should be 'Y', was 'y'. lat-coordinate
dataset.coords['x'] errorAttribute 'units' should be 'degrees_east', was 'degrees'. lon-coordinate
dataset.coords['x'] errorAttribute 'axis' should be 'X', was 'x'. lon-coordinate
dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs
dataset.coords['x'] warn Consider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.coords['y'] warn Consider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.data_vars['sst'] warn Consider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.data_vars['sst_anomaly']warn Consider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.data_vars['sst_avg'] warn Consider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.coords['time'] errorInvalid 'units' attribute: 'days since 2020-01-01 UTC'. time-coordinate
dataset.data_vars['sst_avg'] warn Missing attribute 'standard_name'. var-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'long_name'. var-desc
dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units
dataset.data_vars['sst'] warn Invalid 'units' attribute in variable 'sst'. var-units

3 warnings

\n", + "

31 problems (5 errors and 26 warnings)

\n", "
\n", "
\n", "\n" @@ -193,16 +266,23 @@ "output_type": "stream", "text": [ "{\n", - " \"name\": \"\",\n", " \"plugins\": {\n", " \"__core__\": \"xrlint.plugins.core:export_plugin\"\n", " },\n", " \"rules\": {\n", + " \"content-desc\": 1,\n", + " \"conventions\": 1,\n", " \"coords-for-dims\": 2,\n", " \"dataset-title-attr\": 1,\n", " \"grid-mappings\": 2,\n", + " \"lat-coordinate\": 2,\n", + " \"lon-coordinate\": 2,\n", " \"no-empty-attrs\": 1,\n", - " \"var-units-attr\": 1\n", + " \"no-empty-chunks\": 1,\n", + " \"time-coordinate\": 2,\n", + " \"var-desc\": 1,\n", + " \"var-flags\": 2,\n", + " \"var-units\": 1\n", " }\n", "}\n" ] @@ -212,6 +292,13 @@ "!xrlint --print-config valid.zarr" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": null, diff --git a/notebooks/xrlint-linter.ipynb b/notebooks/xrlint-linter.ipynb index cc8a02d..0c0e48d 100644 --- a/notebooks/xrlint-linter.ipynb +++ b/notebooks/xrlint-linter.ipynb @@ -39,7 +39,7 @@ { "data": { "text/plain": [ - "'0.4.0.dev0'" + "'0.4.1'" ] }, "execution_count": 2, @@ -436,60 +436,252 @@ " stroke: currentColor;\n", " fill: currentColor;\n", "}\n", - "
<xarray.Dataset> Size: 464B\n",
-       "Dimensions:      (x: 2, y: 3, time: 4)\n",
+       "
<xarray.Dataset> Size: 21MB\n",
+       "Dimensions:      (x: 720, y: 360, time: 5)\n",
        "Coordinates:\n",
-       "  * x            (x) float64 16B -180.0 180.0\n",
-       "  * y            (y) float64 24B -90.0 0.0 90.0\n",
-       "  * time         (time) int64 32B 0 365 730 1095\n",
+       "  * x            (x) float64 6kB -180.0 -179.5 -179.0 ... 179.0 179.5 180.0\n",
+       "  * y            (y) float64 3kB -90.0 -89.5 -89.0 -88.5 ... 88.5 89.0 89.5 90.0\n",
+       "  * time         (time) int64 40B 0 365 730 1095 1460\n",
        "    spatial_ref  int64 8B 0\n",
        "Data variables:\n",
-       "    sst          (time, y, x) float64 192B 0.8258 0.8193 ... 0.7022 0.5899\n",
-       "    sst_anomaly  (time, y, x) float64 192B 0.4368 0.9209 ... 0.1712 0.1026\n",
+       "    sst          (time, y, x) float64 10MB dask.array<chunksize=(1, 180, 180), meta=np.ndarray>\n",
+       "    sst_anomaly  (time, y, x) float64 10MB dask.array<chunksize=(1, 180, 180), meta=np.ndarray>\n",
        "Attributes:\n",
-       "    title:    SST-Climatology Subset
" + " Conventions: CF-1.10\n", + " title: SST-Climatology Subset\n", + " history: 2025-01-31 17:31:00 - created;\n", + " institution: BC\n", + " source: SST CCI L4\n", + " references: https://climate.esa.int/en/projects/sea-surface-temperature/\n", + " comment: Demo dataset
" ], "text/plain": [ - " Size: 464B\n", - "Dimensions: (x: 2, y: 3, time: 4)\n", + " Size: 21MB\n", + "Dimensions: (x: 720, y: 360, time: 5)\n", "Coordinates:\n", - " * x (x) float64 16B -180.0 180.0\n", - " * y (y) float64 24B -90.0 0.0 90.0\n", - " * time (time) int64 32B 0 365 730 1095\n", + " * x (x) float64 6kB -180.0 -179.5 -179.0 ... 179.0 179.5 180.0\n", + " * y (y) float64 3kB -90.0 -89.5 -89.0 -88.5 ... 88.5 89.0 89.5 90.0\n", + " * time (time) int64 40B 0 365 730 1095 1460\n", " spatial_ref int64 8B 0\n", "Data variables:\n", - " sst (time, y, x) float64 192B 0.8258 0.8193 ... 0.7022 0.5899\n", - " sst_anomaly (time, y, x) float64 192B 0.4368 0.9209 ... 0.1712 0.1026\n", + " sst (time, y, x) float64 10MB dask.array\n", + " sst_anomaly (time, y, x) float64 10MB dask.array\n", "Attributes:\n", - " title: SST-Climatology Subset" + " Conventions: CF-1.10\n", + " title: SST-Climatology Subset\n", + " history: 2025-01-31 17:31:00 - created;\n", + " institution: BC\n", + " source: SST CCI L4\n", + " references: https://climate.esa.int/en/projects/sea-surface-temperature/\n", + " comment: Demo dataset" ] }, "execution_count": 3, @@ -523,7 +715,7 @@ "

<dataset> - ok

\n" ], "text/plain": [ - "Result(config=Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'__core__': Plugin(meta=PluginMeta(name='__core__', version='0.4.0.dev0', ref='xrlint.plugins.core:export_plugin'), rules={'coords-for-dims': Rule(meta=RuleMeta(name='coords-for-dims', version='1.0.0', description='Dimensions of data variables should have corresponding coordinates.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'dataset-title-attr': Rule(meta=RuleMeta(name='dataset-title-attr', version='1.0.0', description='Datasets should be given a non-empty title.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'flags': Rule(meta=RuleMeta(name='flags', version='1.0.0', description=\"Validate attributes 'flag_values', 'flag_masks' and 'flag_meanings' that make variables that contain flag values self describing. \", schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#flags', type='suggestion'), op_class=), 'grid-mappings': Rule(meta=RuleMeta(name='grid-mappings', version='1.0.0', description='Grid mappings, if any, shall have valid grid mapping coordinate variables.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'lat-coordinate': Rule(meta=RuleMeta(name='lat-coordinate', version='1.0.0', description='Latitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate', type='problem'), op_class=), 'lon-coordinate': Rule(meta=RuleMeta(name='lon-coordinate', version='1.0.0', description='Longitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate', type='problem'), op_class=), 'no-empty-attrs': Rule(meta=RuleMeta(name='no-empty-attrs', version='1.0.0', description='Every dataset element should have metadata that describes it.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'no-empty-chunks': Rule(meta=RuleMeta(name='no-empty-chunks', version='1.0.0', description='Empty chunks should not be encoded and written. The rule currently applies to Zarr format only.', schema=None, ref=None, docs_url='https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html#xarray-dataset-to-zarr', type='suggestion'), op_class=), 'time-coordinate': Rule(meta=RuleMeta(name='time-coordinate', version='1.0.0', description='Time coordinates should have valid and unambiguous time units encoding.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate', type='problem'), op_class=), 'var-units-attr': Rule(meta=RuleMeta(name='var-units-attr', version='1.0.0', description=\"Every variable should have a valid 'units' attribute.\", schema=None, ref=None, docs_url=None, type='suggestion'), op_class=)}, processors={}, configs={'recommended': [Config(name='recommended', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'flags': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None)], 'all': [Config(name='all', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'flags': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=2, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)]})}, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'flags': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), file_path='', messages=[], fixable_error_count=0, fixable_warning_count=0, error_count=0, fatal_error_count=0, warning_count=0)" + "Result(config=Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'__core__': Plugin(meta=PluginMeta(name='__core__', version='0.4.1', ref='xrlint.plugins.core:export_plugin'), rules={'content-desc': Rule(meta=RuleMeta(name='content-desc', version='1.0.0', description=\"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:\\n\\n- `globals`: list of names of required global attributes. Defaults to `['title', 'history']`.\\n- `commons`: list of names of required variable attributes that can also be defined globally. Defaults to `['institution', 'source', 'references', 'comment']`.\\n- `no_vars`: do not check variables at all. Defaults to `False`.\\n- `ignored_vars`: list of ignored variables (regex patterns). Defaults to `['crs', 'spatial_ref']`.\\n\", schema={'type': 'object', 'properties': {'globals': {'type': 'array', 'default': ['title', 'history'], 'items': {'type': 'string'}, 'title': 'Global attribute names'}, 'commons': {'type': 'array', 'default': ['institution', 'source', 'references', 'comment'], 'items': {'type': 'string'}, 'title': 'Common attribute names'}, 'skip_vars': {'type': 'boolean', 'default': False, 'title': 'Do not check variables'}, 'ignored_vars': {'type': 'array', 'default': ['crs', 'spatial_ref'], 'items': {'type': 'string'}, 'title': 'Ignored variables (regex name patterns)'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#description-of-file-contents', type='suggestion'), op_class=), 'conventions': Rule(meta=RuleMeta(name='conventions', version='1.0.0', description='Datasets should identify the applicable conventions using the `Conventions` attribute.\\n The rule has an optional configuration parameter `match` which is a regex pattern that the value of the `Conventions` attribute must match, if any. If not provided, the rule just verifies that the attribute exists and whether it is a character string.', schema={'type': 'object', 'properties': {'match': {'type': 'string', 'title': 'Regex pattern'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#identification-of-conventions', type='suggestion'), op_class=), 'coords-for-dims': Rule(meta=RuleMeta(name='coords-for-dims', version='1.0.0', description='Dimensions of data variables should have corresponding coordinates.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'dataset-title-attr': Rule(meta=RuleMeta(name='dataset-title-attr', version='1.0.0', description='Datasets should be given a non-empty title.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'grid-mappings': Rule(meta=RuleMeta(name='grid-mappings', version='1.0.0', description='Grid mappings, if any, shall have valid grid mapping coordinate variables.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'lat-coordinate': Rule(meta=RuleMeta(name='lat-coordinate', version='1.0.0', description='Latitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate', type='problem'), op_class=), 'lon-coordinate': Rule(meta=RuleMeta(name='lon-coordinate', version='1.0.0', description='Longitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate', type='problem'), op_class=), 'no-empty-attrs': Rule(meta=RuleMeta(name='no-empty-attrs', version='1.0.0', description='Every dataset element should have metadata that describes it.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'no-empty-chunks': Rule(meta=RuleMeta(name='no-empty-chunks', version='1.0.0', description='Empty chunks should not be encoded and written. The rule currently applies to Zarr format only.', schema=None, ref=None, docs_url='https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html#xarray-dataset-to-zarr', type='suggestion'), op_class=), 'time-coordinate': Rule(meta=RuleMeta(name='time-coordinate', version='1.0.0', description='Time coordinates should have valid and unambiguous time units encoding.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate', type='problem'), op_class=), 'var-desc': Rule(meta=RuleMeta(name='var-desc', version='1.0.0', description=\"Check that each data variable provides an identification and description of the content. The rule can be configured by parameter `attrs` which is a list of names of attributes that provides descriptive information. It defaults to `['standard_name', 'long_name']`.\", schema={'type': 'object', 'properties': {'attrs': {'type': 'array', 'default': ['standard_name', 'long_name'], 'items': {'type': 'string'}, 'title': 'Attribute names to check'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#standard-name', type='suggestion'), op_class=), 'var-flags': Rule(meta=RuleMeta(name='var-flags', version='1.0.0', description=\"Validate attributes 'flag_values', 'flag_masks' and 'flag_meanings' that make variables that contain flag values self describing. \", schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#flags', type='suggestion'), op_class=), 'var-units': Rule(meta=RuleMeta(name='var-units', version='1.0.0', description='Every variable should provide a description of its units.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#units', type='suggestion'), op_class=)}, processors={}, configs={'recommended': [Config(name='recommended', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'content-desc': RuleConfig(severity=1, args=(), kwargs={}), 'conventions': RuleConfig(severity=1, args=(), kwargs={}), 'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-desc': RuleConfig(severity=1, args=(), kwargs={}), 'var-flags': RuleConfig(severity=2, args=(), kwargs={}), 'var-units': RuleConfig(severity=1, args=(), kwargs={})}, settings=None)], 'all': [Config(name='all', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'content-desc': RuleConfig(severity=2, args=(), kwargs={}), 'conventions': RuleConfig(severity=2, args=(), kwargs={}), 'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=2, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-desc': RuleConfig(severity=2, args=(), kwargs={}), 'var-flags': RuleConfig(severity=2, args=(), kwargs={}), 'var-units': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)]})}, rules={'content-desc': RuleConfig(severity=1, args=(), kwargs={}), 'conventions': RuleConfig(severity=1, args=(), kwargs={}), 'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-desc': RuleConfig(severity=1, args=(), kwargs={}), 'var-flags': RuleConfig(severity=2, args=(), kwargs={}), 'var-units': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), file_path='', messages=[], fixable_error_count=0, fixable_warning_count=0, error_count=0, fatal_error_count=0, warning_count=0)" ] }, "execution_count": 5, @@ -920,59 +1112,250 @@ " stroke: currentColor;\n", " fill: currentColor;\n", "}\n", - "
<xarray.Dataset> Size: 512B\n",
-       "Dimensions:      (x: 2, y: 3, time: 4)\n",
+       "
<xarray.Dataset> Size: 23MB\n",
+       "Dimensions:      (x: 720, y: 360, time: 5)\n",
        "Coordinates:\n",
-       "  * x            (x) float64 16B -180.0 180.0\n",
-       "  * y            (y) float64 24B -90.0 0.0 90.0\n",
-       "  * time         (time) int64 32B 0 365 730 1095\n",
+       "  * x            (x) float64 6kB -180.0 -179.5 -179.0 ... 179.0 179.5 180.0\n",
+       "  * y            (y) float64 3kB -90.0 -89.5 -89.0 -88.5 ... 88.5 89.0 89.5 90.0\n",
+       "  * time         (time) int64 40B 0 365 730 1095 1460\n",
        "    spatial_ref  int64 8B 0\n",
        "Data variables:\n",
-       "    sst          (time, y, x) float64 192B 0.6958 0.9708 ... 0.2524 0.9463\n",
-       "    sst_anomaly  (time, y, x) float64 192B 0.5294 0.5702 ... 0.3938 0.9155\n",
-       "    sst_avg      (x, y) float64 48B 0.812 0.2143 0.8362 0.9635 0.1922 0.518
" + " sst (time, y, x) float64 10MB dask.array<chunksize=(1, 180, 180), meta=np.ndarray>\n", + " sst_anomaly (time, y, x) float64 10MB dask.array<chunksize=(1, 180, 180), meta=np.ndarray>\n", + " sst_avg (x, y) float64 2MB 0.008034 0.805 0.8186 ... 0.5543 0.803 0.705
" ], "text/plain": [ - " Size: 512B\n", - "Dimensions: (x: 2, y: 3, time: 4)\n", + " Size: 23MB\n", + "Dimensions: (x: 720, y: 360, time: 5)\n", "Coordinates:\n", - " * x (x) float64 16B -180.0 180.0\n", - " * y (y) float64 24B -90.0 0.0 90.0\n", - " * time (time) int64 32B 0 365 730 1095\n", + " * x (x) float64 6kB -180.0 -179.5 -179.0 ... 179.0 179.5 180.0\n", + " * y (y) float64 3kB -90.0 -89.5 -89.0 -88.5 ... 88.5 89.0 89.5 90.0\n", + " * time (time) int64 40B 0 365 730 1095 1460\n", " spatial_ref int64 8B 0\n", "Data variables:\n", - " sst (time, y, x) float64 192B 0.6958 0.9708 ... 0.2524 0.9463\n", - " sst_anomaly (time, y, x) float64 192B 0.5294 0.5702 ... 0.3938 0.9155\n", - " sst_avg (x, y) float64 48B 0.812 0.2143 0.8362 0.9635 0.1922 0.518" + " sst (time, y, x) float64 10MB dask.array\n", + " sst_anomaly (time, y, x) float64 10MB dask.array\n", + " sst_avg (x, y) float64 2MB 0.008034 0.805 0.8186 ... 0.5543 0.803 0.705" ] }, "execution_count": 6, @@ -998,19 +1381,36 @@ "

<dataset>:

\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "
dataset warn Missing 'title' attribute in dataset. dataset-title-attr
dataset.coords['y'] errorAttribute 'standard_name' should be 'latitude', was None. lat-coordinate
dataset.coords['y'] errorAttribute 'axis' should be 'Y', was 'y'. lat-coordinate
dataset.coords['x'] errorAttribute 'units' should be 'degrees_east', was 'degrees'.lon-coordinate
dataset.coords['x'] errorAttribute 'axis' should be 'X', was 'x'. lon-coordinate
dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs
dataset.coords['time'] errorInvalid 'units' attribute: 'days since 2020-01-01 UTC'. time-coordinate
dataset.data_vars['sst']warn Invalid 'units' attribute in variable 'sst'. var-units-attr
dataset warn Missing attribute 'title'. content-desc
dataset warn Missing attribute 'history'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'source'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'references'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'comment'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'source'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'references'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'comment'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'source'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'references'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'comment'. content-desc
dataset warn Missing attribute 'Conventions'. conventions
dataset warn Missing 'title' attribute in dataset. dataset-title-attr
dataset.coords['y'] errorAttribute 'standard_name' should be 'latitude', was None. lat-coordinate
dataset.coords['y'] errorAttribute 'axis' should be 'Y', was 'y'. lat-coordinate
dataset.coords['x'] errorAttribute 'units' should be 'degrees_east', was 'degrees'.lon-coordinate
dataset.coords['x'] errorAttribute 'axis' should be 'X', was 'x'. lon-coordinate
dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs
dataset.coords['time'] errorInvalid 'units' attribute: 'days since 2020-01-01 UTC'. time-coordinate
dataset.data_vars['sst_avg'] warn Missing attribute 'standard_name'. var-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'long_name'. var-desc
dataset.data_vars['sst'] warn Invalid 'units' attribute in variable 'sst'. var-units

8 problems (5 errors and 3 warnings)

\n" + "

25 problems (5 errors and 20 warnings)

\n" ], "text/plain": [ - "Result(config=Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'__core__': Plugin(meta=PluginMeta(name='__core__', version='0.4.0.dev0', ref='xrlint.plugins.core:export_plugin'), rules={'coords-for-dims': Rule(meta=RuleMeta(name='coords-for-dims', version='1.0.0', description='Dimensions of data variables should have corresponding coordinates.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'dataset-title-attr': Rule(meta=RuleMeta(name='dataset-title-attr', version='1.0.0', description='Datasets should be given a non-empty title.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'flags': Rule(meta=RuleMeta(name='flags', version='1.0.0', description=\"Validate attributes 'flag_values', 'flag_masks' and 'flag_meanings' that make variables that contain flag values self describing. \", schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#flags', type='suggestion'), op_class=), 'grid-mappings': Rule(meta=RuleMeta(name='grid-mappings', version='1.0.0', description='Grid mappings, if any, shall have valid grid mapping coordinate variables.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'lat-coordinate': Rule(meta=RuleMeta(name='lat-coordinate', version='1.0.0', description='Latitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate', type='problem'), op_class=), 'lon-coordinate': Rule(meta=RuleMeta(name='lon-coordinate', version='1.0.0', description='Longitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate', type='problem'), op_class=), 'no-empty-attrs': Rule(meta=RuleMeta(name='no-empty-attrs', version='1.0.0', description='Every dataset element should have metadata that describes it.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'no-empty-chunks': Rule(meta=RuleMeta(name='no-empty-chunks', version='1.0.0', description='Empty chunks should not be encoded and written. The rule currently applies to Zarr format only.', schema=None, ref=None, docs_url='https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html#xarray-dataset-to-zarr', type='suggestion'), op_class=), 'time-coordinate': Rule(meta=RuleMeta(name='time-coordinate', version='1.0.0', description='Time coordinates should have valid and unambiguous time units encoding.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate', type='problem'), op_class=), 'var-units-attr': Rule(meta=RuleMeta(name='var-units-attr', version='1.0.0', description=\"Every variable should have a valid 'units' attribute.\", schema=None, ref=None, docs_url=None, type='suggestion'), op_class=)}, processors={}, configs={'recommended': [Config(name='recommended', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'flags': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None)], 'all': [Config(name='all', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'flags': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=2, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)]})}, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'flags': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), file_path='', messages=[Message(message=\"Missing 'title' attribute in dataset.\", node_path='dataset', rule_id='dataset-title-attr', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'standard_name' should be 'latitude', was None.\", node_path=\"dataset.coords['y']\", rule_id='lat-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'axis' should be 'Y', was 'y'.\", node_path=\"dataset.coords['y']\", rule_id='lat-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'units' should be 'degrees_east', was 'degrees'.\", node_path=\"dataset.coords['x']\", rule_id='lon-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'axis' should be 'X', was 'x'.\", node_path=\"dataset.coords['x']\", rule_id='lon-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message='Missing metadata, attributes are empty.', node_path='dataset.attrs', rule_id='no-empty-attrs', severity=1, fatal=None, fix=None, suggestions=[Suggestion(desc='Make sure to add appropriate metadata attributes to dataset elements.', data=None, fix=None)]), Message(message=\"Invalid 'units' attribute: 'days since 2020-01-01 UTC'.\", node_path=\"dataset.coords['time']\", rule_id='time-coordinate', severity=2, fatal=None, fix=None, suggestions=[Suggestion(desc=\"Specify 'units' attribute using the UDUNITS format, e.g., 'seconds since 2010-10-8 15:15:42.5 -6:00' or 'days since 2000-01-01 +0:00'.\", data=None, fix=None)]), Message(message=\"Invalid 'units' attribute in variable 'sst'.\", node_path=\"dataset.data_vars['sst']\", rule_id='var-units-attr', severity=1, fatal=None, fix=None, suggestions=None)], fixable_error_count=0, fixable_warning_count=0, error_count=5, fatal_error_count=0, warning_count=3)" + "Result(config=Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'__core__': Plugin(meta=PluginMeta(name='__core__', version='0.4.1', ref='xrlint.plugins.core:export_plugin'), rules={'content-desc': Rule(meta=RuleMeta(name='content-desc', version='1.0.0', description=\"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:\\n\\n- `globals`: list of names of required global attributes. Defaults to `['title', 'history']`.\\n- `commons`: list of names of required variable attributes that can also be defined globally. Defaults to `['institution', 'source', 'references', 'comment']`.\\n- `no_vars`: do not check variables at all. Defaults to `False`.\\n- `ignored_vars`: list of ignored variables (regex patterns). Defaults to `['crs', 'spatial_ref']`.\\n\", schema={'type': 'object', 'properties': {'globals': {'type': 'array', 'default': ['title', 'history'], 'items': {'type': 'string'}, 'title': 'Global attribute names'}, 'commons': {'type': 'array', 'default': ['institution', 'source', 'references', 'comment'], 'items': {'type': 'string'}, 'title': 'Common attribute names'}, 'skip_vars': {'type': 'boolean', 'default': False, 'title': 'Do not check variables'}, 'ignored_vars': {'type': 'array', 'default': ['crs', 'spatial_ref'], 'items': {'type': 'string'}, 'title': 'Ignored variables (regex name patterns)'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#description-of-file-contents', type='suggestion'), op_class=), 'conventions': Rule(meta=RuleMeta(name='conventions', version='1.0.0', description='Datasets should identify the applicable conventions using the `Conventions` attribute.\\n The rule has an optional configuration parameter `match` which is a regex pattern that the value of the `Conventions` attribute must match, if any. If not provided, the rule just verifies that the attribute exists and whether it is a character string.', schema={'type': 'object', 'properties': {'match': {'type': 'string', 'title': 'Regex pattern'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#identification-of-conventions', type='suggestion'), op_class=), 'coords-for-dims': Rule(meta=RuleMeta(name='coords-for-dims', version='1.0.0', description='Dimensions of data variables should have corresponding coordinates.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'dataset-title-attr': Rule(meta=RuleMeta(name='dataset-title-attr', version='1.0.0', description='Datasets should be given a non-empty title.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'grid-mappings': Rule(meta=RuleMeta(name='grid-mappings', version='1.0.0', description='Grid mappings, if any, shall have valid grid mapping coordinate variables.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'lat-coordinate': Rule(meta=RuleMeta(name='lat-coordinate', version='1.0.0', description='Latitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate', type='problem'), op_class=), 'lon-coordinate': Rule(meta=RuleMeta(name='lon-coordinate', version='1.0.0', description='Longitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate', type='problem'), op_class=), 'no-empty-attrs': Rule(meta=RuleMeta(name='no-empty-attrs', version='1.0.0', description='Every dataset element should have metadata that describes it.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'no-empty-chunks': Rule(meta=RuleMeta(name='no-empty-chunks', version='1.0.0', description='Empty chunks should not be encoded and written. The rule currently applies to Zarr format only.', schema=None, ref=None, docs_url='https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html#xarray-dataset-to-zarr', type='suggestion'), op_class=), 'time-coordinate': Rule(meta=RuleMeta(name='time-coordinate', version='1.0.0', description='Time coordinates should have valid and unambiguous time units encoding.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate', type='problem'), op_class=), 'var-desc': Rule(meta=RuleMeta(name='var-desc', version='1.0.0', description=\"Check that each data variable provides an identification and description of the content. The rule can be configured by parameter `attrs` which is a list of names of attributes that provides descriptive information. It defaults to `['standard_name', 'long_name']`.\", schema={'type': 'object', 'properties': {'attrs': {'type': 'array', 'default': ['standard_name', 'long_name'], 'items': {'type': 'string'}, 'title': 'Attribute names to check'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#standard-name', type='suggestion'), op_class=), 'var-flags': Rule(meta=RuleMeta(name='var-flags', version='1.0.0', description=\"Validate attributes 'flag_values', 'flag_masks' and 'flag_meanings' that make variables that contain flag values self describing. \", schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#flags', type='suggestion'), op_class=), 'var-units': Rule(meta=RuleMeta(name='var-units', version='1.0.0', description='Every variable should provide a description of its units.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#units', type='suggestion'), op_class=)}, processors={}, configs={'recommended': [Config(name='recommended', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'content-desc': RuleConfig(severity=1, args=(), kwargs={}), 'conventions': RuleConfig(severity=1, args=(), kwargs={}), 'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-desc': RuleConfig(severity=1, args=(), kwargs={}), 'var-flags': RuleConfig(severity=2, args=(), kwargs={}), 'var-units': RuleConfig(severity=1, args=(), kwargs={})}, settings=None)], 'all': [Config(name='all', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'content-desc': RuleConfig(severity=2, args=(), kwargs={}), 'conventions': RuleConfig(severity=2, args=(), kwargs={}), 'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=2, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-desc': RuleConfig(severity=2, args=(), kwargs={}), 'var-flags': RuleConfig(severity=2, args=(), kwargs={}), 'var-units': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)]})}, rules={'content-desc': RuleConfig(severity=1, args=(), kwargs={}), 'conventions': RuleConfig(severity=1, args=(), kwargs={}), 'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-desc': RuleConfig(severity=1, args=(), kwargs={}), 'var-flags': RuleConfig(severity=2, args=(), kwargs={}), 'var-units': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), file_path='', messages=[Message(message=\"Missing attribute 'title'.\", node_path='dataset', rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'history'.\", node_path='dataset', rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'institution'.\", node_path=\"dataset.data_vars['sst']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'source'.\", node_path=\"dataset.data_vars['sst']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'references'.\", node_path=\"dataset.data_vars['sst']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'comment'.\", node_path=\"dataset.data_vars['sst']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'institution'.\", node_path=\"dataset.data_vars['sst_anomaly']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'source'.\", node_path=\"dataset.data_vars['sst_anomaly']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'references'.\", node_path=\"dataset.data_vars['sst_anomaly']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'comment'.\", node_path=\"dataset.data_vars['sst_anomaly']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'institution'.\", node_path=\"dataset.data_vars['sst_avg']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'source'.\", node_path=\"dataset.data_vars['sst_avg']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'references'.\", node_path=\"dataset.data_vars['sst_avg']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'comment'.\", node_path=\"dataset.data_vars['sst_avg']\", rule_id='content-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'Conventions'.\", node_path='dataset', rule_id='conventions', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing 'title' attribute in dataset.\", node_path='dataset', rule_id='dataset-title-attr', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'standard_name' should be 'latitude', was None.\", node_path=\"dataset.coords['y']\", rule_id='lat-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'axis' should be 'Y', was 'y'.\", node_path=\"dataset.coords['y']\", rule_id='lat-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'units' should be 'degrees_east', was 'degrees'.\", node_path=\"dataset.coords['x']\", rule_id='lon-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'axis' should be 'X', was 'x'.\", node_path=\"dataset.coords['x']\", rule_id='lon-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message='Missing metadata, attributes are empty.', node_path='dataset.attrs', rule_id='no-empty-attrs', severity=1, fatal=None, fix=None, suggestions=[Suggestion(desc='Make sure to add appropriate metadata attributes to dataset elements.', data=None, fix=None)]), Message(message=\"Invalid 'units' attribute: 'days since 2020-01-01 UTC'.\", node_path=\"dataset.coords['time']\", rule_id='time-coordinate', severity=2, fatal=None, fix=None, suggestions=[Suggestion(desc=\"Specify 'units' attribute using the UDUNITS format, e.g., 'seconds since 2010-10-8 15:15:42.5 -6:00' or 'days since 2000-01-01 +0:00'.\", data=None, fix=None)]), Message(message=\"Missing attribute 'standard_name'.\", node_path=\"dataset.data_vars['sst_avg']\", rule_id='var-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'long_name'.\", node_path=\"dataset.data_vars['sst_avg']\", rule_id='var-desc', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Invalid 'units' attribute in variable 'sst'.\", node_path=\"dataset.data_vars['sst']\", rule_id='var-units', severity=1, fatal=None, fix=None, suggestions=None)], fixable_error_count=0, fixable_warning_count=0, error_count=5, fatal_error_count=0, warning_count=20)" ] }, "execution_count": 7, @@ -1057,10 +1457,12 @@ " {\"plugins\": {\"xcube\": \"xrlint.plugins.xcube\"}},\n", " \"xcube/recommended\", \n", " rules={\n", + " \"content-desc\": \"off\",\n", " \"no-empty-attrs\": \"warn\",\n", " \"dataset-title-attr\": \"warn\",\n", " \"grid-mappings\": \"error\",\n", - " \"var-units-attr\": \"error\",\n", + " \"var-desc\": \"off\",\n", + " \"var-units\": \"error\",\n", " }\n", ")" ] @@ -1076,6 +1478,7 @@ "

<dataset>:

\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -1083,15 +1486,15 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "
dataset warn Missing attribute 'Conventions'. conventions
dataset warn Missing 'title' attribute in dataset. dataset-title-attr
dataset.coords['y'] errorAttribute 'standard_name' should be 'latitude', was None. lat-coordinate
dataset.coords['y'] errorAttribute 'axis' should be 'Y', was 'y'. lat-coordinate
dataset.coords['x'] errorAttribute 'axis' should be 'X', was 'x'. lon-coordinate
dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs
dataset.coords['time'] errorInvalid 'units' attribute: 'days since 2020-01-01 UTC'. time-coordinate
dataset.data_vars['sst'] errorInvalid 'units' attribute in variable 'sst'. var-units-attr
dataset.data_vars['sst'] errorInvalid 'units' attribute in variable 'sst'. var-units
dataset.data_vars['sst_avg'] errorOrder of dimensions should be y,x, but found x,y. xcube/cube-dims-order
dataset.data_vars['sst'] warn Missing attribute 'color_bar_name'. xcube/data-var-colors
dataset.data_vars['sst_anomaly']warn Missing attribute 'color_bar_name'. xcube/data-var-colors

11 problems (7 errors and 4 warnings)

\n" + "

12 problems (7 errors and 5 warnings)

\n" ], "text/plain": [ - "Result(config=Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'__core__': Plugin(meta=PluginMeta(name='__core__', version='0.4.0.dev0', ref='xrlint.plugins.core:export_plugin'), rules={'coords-for-dims': Rule(meta=RuleMeta(name='coords-for-dims', version='1.0.0', description='Dimensions of data variables should have corresponding coordinates.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'dataset-title-attr': Rule(meta=RuleMeta(name='dataset-title-attr', version='1.0.0', description='Datasets should be given a non-empty title.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'flags': Rule(meta=RuleMeta(name='flags', version='1.0.0', description=\"Validate attributes 'flag_values', 'flag_masks' and 'flag_meanings' that make variables that contain flag values self describing. \", schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#flags', type='suggestion'), op_class=), 'grid-mappings': Rule(meta=RuleMeta(name='grid-mappings', version='1.0.0', description='Grid mappings, if any, shall have valid grid mapping coordinate variables.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'lat-coordinate': Rule(meta=RuleMeta(name='lat-coordinate', version='1.0.0', description='Latitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate', type='problem'), op_class=), 'lon-coordinate': Rule(meta=RuleMeta(name='lon-coordinate', version='1.0.0', description='Longitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate', type='problem'), op_class=), 'no-empty-attrs': Rule(meta=RuleMeta(name='no-empty-attrs', version='1.0.0', description='Every dataset element should have metadata that describes it.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'no-empty-chunks': Rule(meta=RuleMeta(name='no-empty-chunks', version='1.0.0', description='Empty chunks should not be encoded and written. The rule currently applies to Zarr format only.', schema=None, ref=None, docs_url='https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html#xarray-dataset-to-zarr', type='suggestion'), op_class=), 'time-coordinate': Rule(meta=RuleMeta(name='time-coordinate', version='1.0.0', description='Time coordinates should have valid and unambiguous time units encoding.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate', type='problem'), op_class=), 'var-units-attr': Rule(meta=RuleMeta(name='var-units-attr', version='1.0.0', description=\"Every variable should have a valid 'units' attribute.\", schema=None, ref=None, docs_url=None, type='suggestion'), op_class=)}, processors={}, configs={'recommended': [Config(name='recommended', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'flags': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None)], 'all': [Config(name='all', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'flags': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=2, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)]}), 'xcube': Plugin(meta=PluginMeta(name='xcube', version='0.4.0.dev0', ref='xrlint.plugins.xcube:export_plugin'), rules={'any-spatial-data-var': Rule(meta=RuleMeta(name='any-spatial-data-var', version='1.0.0', description='A datacube should have spatial data variables.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#data-model-and-format', type='problem'), op_class=), 'cube-dims-order': Rule(meta=RuleMeta(name='cube-dims-order', version='1.0.0', description='Order of dimensions in spatio-temporal datacube variables should be [time, ..., y, x].', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#data-model-and-format', type='problem'), op_class=), 'data-var-colors': Rule(meta=RuleMeta(name='data-var-colors', version='1.0.0', description='Spatial data variables should encode xcube color mappings in their metadata.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#encoding-of-colors', type='suggestion'), op_class=), 'grid-mapping-naming': Rule(meta=RuleMeta(name='grid-mapping-naming', version='1.0.0', description=\"Grid mapping variables should be called 'spatial_ref' or 'crs' for compatibility with rioxarray and other packages.\", schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#spatial-reference', type='suggestion'), op_class=), 'increasing-time': Rule(meta=RuleMeta(name='increasing-time', version='1.0.0', description='Time coordinate labels should be monotonically increasing.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#temporal-reference', type='problem'), op_class=), 'lat-lon-naming': Rule(meta=RuleMeta(name='lat-lon-naming', version='1.0.0', description=\"Latitude and longitude coordinates and dimensions should be called 'lat' and 'lon'.\", schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#spatial-reference', type='problem'), op_class=), 'ml-dataset-meta': Rule(meta=RuleMeta(name='ml-dataset-meta', version='1.0.0', description=\"Multi-level datasets should provide a '.zlevels' meta-info file, and if so, it should be consistent. Without the meta-info file the multi-level dataset cannot be reliably extended by new time slices as the aggregation method used for each variable must be specified.\", schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/mldatasets.html#the-xcube-levels-format', type='suggestion'), op_class=), 'ml-dataset-time': Rule(meta=RuleMeta(name='ml-dataset-time', version='1.0.0', description='The `time` dimension of multi-level datasets should use a chunk size of 1. This allows for faster image tile generation for visualisation.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/mldatasets.html#definition', type='problem'), op_class=), 'ml-dataset-xy': Rule(meta=RuleMeta(name='ml-dataset-xy', version='1.0.0', description='Multi-level dataset levels should provide spatial resolutions decreasing by powers of two.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/mldatasets.html#definition', type='problem'), op_class=), 'no-chunked-coords': Rule(meta=RuleMeta(name='no-chunked-coords', version='1.0.0', description='Coordinate variables should not be chunked. Can be used to identify performance issues, where chunked coordinates can cause slow opening if datasets due to the many chunk-fetching requests made to (remote) filesystems with low bandwidth. You can use the `limit` parameter to specify an acceptable number of chunks. Its default is 5.', schema={'type': 'object', 'properties': {'limit': {'type': 'integer', 'default': 5, 'minimum': 0, 'title': 'Acceptable number of chunks'}}}, ref=None, docs_url=None, type='problem'), op_class=), 'single-grid-mapping': Rule(meta=RuleMeta(name='single-grid-mapping', version='1.0.0', description='A single grid mapping shall be used for all spatial data variables of a datacube.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#spatial-reference', type='problem'), op_class=), 'time-naming': Rule(meta=RuleMeta(name='time-naming', version='1.0.0', description=\"Time coordinate and dimension should be called 'time'.\", schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#temporal-reference', type='problem'), op_class=)}, processors={'multi-level-dataset': Processor(meta=ProcessorMeta(name='multi-level-dataset', version='0.0.0', description='Implements the processor operations.', schema=None, ref=None), op_class=)}, configs={'recommended': [Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'xcube': ...}, rules=None, settings=None), Config(name=None, files=['**/*.levels'], ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules=None, settings=None), Config(name=None, files=['**/*.levels'], ignores=None, linter_options=None, opener_options=None, processor='xcube/multi-level-dataset', plugins=None, rules=None, settings=None), Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'xcube/any-spatial-data-var': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/cube-dims-order': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/data-var-colors': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/grid-mapping-naming': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/increasing-time': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/lat-lon-naming': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-meta': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-time': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/ml-dataset-xy': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/no-chunked-coords': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/single-grid-mapping': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/time-naming': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)], 'all': [Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'xcube': ...}, rules=None, settings=None), Config(name=None, files=['**/*.levels'], ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules=None, settings=None), Config(name=None, files=['**/*.levels'], ignores=None, linter_options=None, opener_options=None, processor='xcube/multi-level-dataset', plugins=None, rules=None, settings=None), Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'xcube/any-spatial-data-var': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/cube-dims-order': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/data-var-colors': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/grid-mapping-naming': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/increasing-time': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/lat-lon-naming': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-meta': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-time': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-xy': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/no-chunked-coords': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/single-grid-mapping': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/time-naming': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)]})}, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=[], kwargs={}), 'flags': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=[], kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=[], kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/any-spatial-data-var': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/cube-dims-order': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/data-var-colors': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/grid-mapping-naming': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/increasing-time': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/lat-lon-naming': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-meta': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-time': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/ml-dataset-xy': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/no-chunked-coords': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/single-grid-mapping': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/time-naming': RuleConfig(severity=2, args=(), kwargs={})}, settings=None), file_path='', messages=[Message(message=\"Missing 'title' attribute in dataset.\", node_path='dataset', rule_id='dataset-title-attr', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'standard_name' should be 'latitude', was None.\", node_path=\"dataset.coords['y']\", rule_id='lat-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'axis' should be 'Y', was 'y'.\", node_path=\"dataset.coords['y']\", rule_id='lat-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'units' should be 'degrees_east', was 'degrees'.\", node_path=\"dataset.coords['x']\", rule_id='lon-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'axis' should be 'X', was 'x'.\", node_path=\"dataset.coords['x']\", rule_id='lon-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message='Missing metadata, attributes are empty.', node_path='dataset.attrs', rule_id='no-empty-attrs', severity=1, fatal=None, fix=None, suggestions=[Suggestion(desc='Make sure to add appropriate metadata attributes to dataset elements.', data=None, fix=None)]), Message(message=\"Invalid 'units' attribute: 'days since 2020-01-01 UTC'.\", node_path=\"dataset.coords['time']\", rule_id='time-coordinate', severity=2, fatal=None, fix=None, suggestions=[Suggestion(desc=\"Specify 'units' attribute using the UDUNITS format, e.g., 'seconds since 2010-10-8 15:15:42.5 -6:00' or 'days since 2000-01-01 +0:00'.\", data=None, fix=None)]), Message(message=\"Invalid 'units' attribute in variable 'sst'.\", node_path=\"dataset.data_vars['sst']\", rule_id='var-units-attr', severity=2, fatal=None, fix=None, suggestions=None), Message(message='Order of dimensions should be y,x, but found x,y.', node_path=\"dataset.data_vars['sst_avg']\", rule_id='xcube/cube-dims-order', severity=2, fatal=None, fix=None, suggestions=[Suggestion(desc='Use xarray.transpose(...) to reorder dimensions.', data=None, fix=None)]), Message(message=\"Missing attribute 'color_bar_name'.\", node_path=\"dataset.data_vars['sst']\", rule_id='xcube/data-var-colors', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'color_bar_name'.\", node_path=\"dataset.data_vars['sst_anomaly']\", rule_id='xcube/data-var-colors', severity=1, fatal=None, fix=None, suggestions=None)], fixable_error_count=0, fixable_warning_count=0, error_count=7, fatal_error_count=0, warning_count=4)" + "Result(config=Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'__core__': Plugin(meta=PluginMeta(name='__core__', version='0.4.1', ref='xrlint.plugins.core:export_plugin'), rules={'content-desc': Rule(meta=RuleMeta(name='content-desc', version='1.0.0', description=\"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:\\n\\n- `globals`: list of names of required global attributes. Defaults to `['title', 'history']`.\\n- `commons`: list of names of required variable attributes that can also be defined globally. Defaults to `['institution', 'source', 'references', 'comment']`.\\n- `no_vars`: do not check variables at all. Defaults to `False`.\\n- `ignored_vars`: list of ignored variables (regex patterns). Defaults to `['crs', 'spatial_ref']`.\\n\", schema={'type': 'object', 'properties': {'globals': {'type': 'array', 'default': ['title', 'history'], 'items': {'type': 'string'}, 'title': 'Global attribute names'}, 'commons': {'type': 'array', 'default': ['institution', 'source', 'references', 'comment'], 'items': {'type': 'string'}, 'title': 'Common attribute names'}, 'skip_vars': {'type': 'boolean', 'default': False, 'title': 'Do not check variables'}, 'ignored_vars': {'type': 'array', 'default': ['crs', 'spatial_ref'], 'items': {'type': 'string'}, 'title': 'Ignored variables (regex name patterns)'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#description-of-file-contents', type='suggestion'), op_class=), 'conventions': Rule(meta=RuleMeta(name='conventions', version='1.0.0', description='Datasets should identify the applicable conventions using the `Conventions` attribute.\\n The rule has an optional configuration parameter `match` which is a regex pattern that the value of the `Conventions` attribute must match, if any. If not provided, the rule just verifies that the attribute exists and whether it is a character string.', schema={'type': 'object', 'properties': {'match': {'type': 'string', 'title': 'Regex pattern'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#identification-of-conventions', type='suggestion'), op_class=), 'coords-for-dims': Rule(meta=RuleMeta(name='coords-for-dims', version='1.0.0', description='Dimensions of data variables should have corresponding coordinates.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'dataset-title-attr': Rule(meta=RuleMeta(name='dataset-title-attr', version='1.0.0', description='Datasets should be given a non-empty title.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'grid-mappings': Rule(meta=RuleMeta(name='grid-mappings', version='1.0.0', description='Grid mappings, if any, shall have valid grid mapping coordinate variables.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'lat-coordinate': Rule(meta=RuleMeta(name='lat-coordinate', version='1.0.0', description='Latitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate', type='problem'), op_class=), 'lon-coordinate': Rule(meta=RuleMeta(name='lon-coordinate', version='1.0.0', description='Longitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate', type='problem'), op_class=), 'no-empty-attrs': Rule(meta=RuleMeta(name='no-empty-attrs', version='1.0.0', description='Every dataset element should have metadata that describes it.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'no-empty-chunks': Rule(meta=RuleMeta(name='no-empty-chunks', version='1.0.0', description='Empty chunks should not be encoded and written. The rule currently applies to Zarr format only.', schema=None, ref=None, docs_url='https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html#xarray-dataset-to-zarr', type='suggestion'), op_class=), 'time-coordinate': Rule(meta=RuleMeta(name='time-coordinate', version='1.0.0', description='Time coordinates should have valid and unambiguous time units encoding.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate', type='problem'), op_class=), 'var-desc': Rule(meta=RuleMeta(name='var-desc', version='1.0.0', description=\"Check that each data variable provides an identification and description of the content. The rule can be configured by parameter `attrs` which is a list of names of attributes that provides descriptive information. It defaults to `['standard_name', 'long_name']`.\", schema={'type': 'object', 'properties': {'attrs': {'type': 'array', 'default': ['standard_name', 'long_name'], 'items': {'type': 'string'}, 'title': 'Attribute names to check'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#standard-name', type='suggestion'), op_class=), 'var-flags': Rule(meta=RuleMeta(name='var-flags', version='1.0.0', description=\"Validate attributes 'flag_values', 'flag_masks' and 'flag_meanings' that make variables that contain flag values self describing. \", schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#flags', type='suggestion'), op_class=), 'var-units': Rule(meta=RuleMeta(name='var-units', version='1.0.0', description='Every variable should provide a description of its units.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#units', type='suggestion'), op_class=)}, processors={}, configs={'recommended': [Config(name='recommended', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'content-desc': RuleConfig(severity=1, args=(), kwargs={}), 'conventions': RuleConfig(severity=1, args=(), kwargs={}), 'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-desc': RuleConfig(severity=1, args=(), kwargs={}), 'var-flags': RuleConfig(severity=2, args=(), kwargs={}), 'var-units': RuleConfig(severity=1, args=(), kwargs={})}, settings=None)], 'all': [Config(name='all', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'content-desc': RuleConfig(severity=2, args=(), kwargs={}), 'conventions': RuleConfig(severity=2, args=(), kwargs={}), 'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=2, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-desc': RuleConfig(severity=2, args=(), kwargs={}), 'var-flags': RuleConfig(severity=2, args=(), kwargs={}), 'var-units': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)]}), 'xcube': Plugin(meta=PluginMeta(name='xcube', version='0.4.1', ref='xrlint.plugins.xcube:export_plugin'), rules={'any-spatial-data-var': Rule(meta=RuleMeta(name='any-spatial-data-var', version='1.0.0', description='A datacube should have spatial data variables.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#data-model-and-format', type='problem'), op_class=), 'cube-dims-order': Rule(meta=RuleMeta(name='cube-dims-order', version='1.0.0', description='Order of dimensions in spatio-temporal datacube variables should be [time, ..., y, x].', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#data-model-and-format', type='problem'), op_class=), 'data-var-colors': Rule(meta=RuleMeta(name='data-var-colors', version='1.0.0', description='Spatial data variables should encode xcube color mappings in their metadata.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#encoding-of-colors', type='suggestion'), op_class=), 'grid-mapping-naming': Rule(meta=RuleMeta(name='grid-mapping-naming', version='1.0.0', description=\"Grid mapping variables should be called 'spatial_ref' or 'crs' for compatibility with rioxarray and other packages.\", schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#spatial-reference', type='suggestion'), op_class=), 'increasing-time': Rule(meta=RuleMeta(name='increasing-time', version='1.0.0', description='Time coordinate labels should be monotonically increasing.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#temporal-reference', type='problem'), op_class=), 'lat-lon-naming': Rule(meta=RuleMeta(name='lat-lon-naming', version='1.0.0', description=\"Latitude and longitude coordinates and dimensions should be called 'lat' and 'lon'.\", schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#spatial-reference', type='problem'), op_class=), 'ml-dataset-meta': Rule(meta=RuleMeta(name='ml-dataset-meta', version='1.0.0', description=\"Multi-level datasets should provide a '.zlevels' meta-info file, and if so, it should be consistent. Without the meta-info file the multi-level dataset cannot be reliably extended by new time slices as the aggregation method used for each variable must be specified.\", schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/mldatasets.html#the-xcube-levels-format', type='suggestion'), op_class=), 'ml-dataset-time': Rule(meta=RuleMeta(name='ml-dataset-time', version='1.0.0', description='The `time` dimension of multi-level datasets should use a chunk size of 1. This allows for faster image tile generation for visualisation.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/mldatasets.html#definition', type='problem'), op_class=), 'ml-dataset-xy': Rule(meta=RuleMeta(name='ml-dataset-xy', version='1.0.0', description='Multi-level dataset levels should provide spatial resolutions decreasing by powers of two.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/mldatasets.html#definition', type='problem'), op_class=), 'no-chunked-coords': Rule(meta=RuleMeta(name='no-chunked-coords', version='1.0.0', description='Coordinate variables should not be chunked. Can be used to identify performance issues, where chunked coordinates can cause slow opening if datasets due to the many chunk-fetching requests made to (remote) filesystems with low bandwidth. You can use the `limit` parameter to specify an acceptable number of chunks. Its default is 5.', schema={'type': 'object', 'properties': {'limit': {'type': 'integer', 'default': 5, 'minimum': 0, 'title': 'Acceptable number of chunks'}}}, ref=None, docs_url=None, type='problem'), op_class=), 'single-grid-mapping': Rule(meta=RuleMeta(name='single-grid-mapping', version='1.0.0', description='A single grid mapping shall be used for all spatial data variables of a datacube.', schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#spatial-reference', type='problem'), op_class=), 'time-naming': Rule(meta=RuleMeta(name='time-naming', version='1.0.0', description=\"Time coordinate and dimension should be called 'time'.\", schema=None, ref=None, docs_url='https://xcube.readthedocs.io/en/latest/cubespec.html#temporal-reference', type='problem'), op_class=)}, processors={'multi-level-dataset': Processor(meta=ProcessorMeta(name='multi-level-dataset', version='0.0.0', description='Implements the processor operations.', schema=None, ref=None), op_class=)}, configs={'recommended': [Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'xcube': ...}, rules=None, settings=None), Config(name=None, files=['**/*.levels'], ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules=None, settings=None), Config(name=None, files=['**/*.levels'], ignores=None, linter_options=None, opener_options=None, processor='xcube/multi-level-dataset', plugins=None, rules=None, settings=None), Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'xcube/any-spatial-data-var': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/cube-dims-order': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/data-var-colors': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/grid-mapping-naming': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/increasing-time': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/lat-lon-naming': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-meta': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-time': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/ml-dataset-xy': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/no-chunked-coords': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/single-grid-mapping': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/time-naming': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)], 'all': [Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'xcube': ...}, rules=None, settings=None), Config(name=None, files=['**/*.levels'], ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules=None, settings=None), Config(name=None, files=['**/*.levels'], ignores=None, linter_options=None, opener_options=None, processor='xcube/multi-level-dataset', plugins=None, rules=None, settings=None), Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'xcube/any-spatial-data-var': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/cube-dims-order': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/data-var-colors': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/grid-mapping-naming': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/increasing-time': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/lat-lon-naming': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-meta': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-time': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-xy': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/no-chunked-coords': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/single-grid-mapping': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/time-naming': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)]})}, rules={'content-desc': RuleConfig(severity=0, args=(), kwargs={}), 'conventions': RuleConfig(severity=1, args=(), kwargs={}), 'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=[], kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=[], kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=[], kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-desc': RuleConfig(severity=0, args=(), kwargs={}), 'var-flags': RuleConfig(severity=2, args=(), kwargs={}), 'var-units': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/any-spatial-data-var': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/cube-dims-order': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/data-var-colors': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/grid-mapping-naming': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/increasing-time': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/lat-lon-naming': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-meta': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/ml-dataset-time': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/ml-dataset-xy': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/no-chunked-coords': RuleConfig(severity=1, args=(), kwargs={}), 'xcube/single-grid-mapping': RuleConfig(severity=2, args=(), kwargs={}), 'xcube/time-naming': RuleConfig(severity=2, args=(), kwargs={})}, settings=None), file_path='', messages=[Message(message=\"Missing attribute 'Conventions'.\", node_path='dataset', rule_id='conventions', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing 'title' attribute in dataset.\", node_path='dataset', rule_id='dataset-title-attr', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'standard_name' should be 'latitude', was None.\", node_path=\"dataset.coords['y']\", rule_id='lat-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'axis' should be 'Y', was 'y'.\", node_path=\"dataset.coords['y']\", rule_id='lat-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'units' should be 'degrees_east', was 'degrees'.\", node_path=\"dataset.coords['x']\", rule_id='lon-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Attribute 'axis' should be 'X', was 'x'.\", node_path=\"dataset.coords['x']\", rule_id='lon-coordinate', severity=2, fatal=None, fix=None, suggestions=None), Message(message='Missing metadata, attributes are empty.', node_path='dataset.attrs', rule_id='no-empty-attrs', severity=1, fatal=None, fix=None, suggestions=[Suggestion(desc='Make sure to add appropriate metadata attributes to dataset elements.', data=None, fix=None)]), Message(message=\"Invalid 'units' attribute: 'days since 2020-01-01 UTC'.\", node_path=\"dataset.coords['time']\", rule_id='time-coordinate', severity=2, fatal=None, fix=None, suggestions=[Suggestion(desc=\"Specify 'units' attribute using the UDUNITS format, e.g., 'seconds since 2010-10-8 15:15:42.5 -6:00' or 'days since 2000-01-01 +0:00'.\", data=None, fix=None)]), Message(message=\"Invalid 'units' attribute in variable 'sst'.\", node_path=\"dataset.data_vars['sst']\", rule_id='var-units', severity=2, fatal=None, fix=None, suggestions=None), Message(message='Order of dimensions should be y,x, but found x,y.', node_path=\"dataset.data_vars['sst_avg']\", rule_id='xcube/cube-dims-order', severity=2, fatal=None, fix=None, suggestions=[Suggestion(desc='Use xarray.transpose(...) to reorder dimensions.', data=None, fix=None)]), Message(message=\"Missing attribute 'color_bar_name'.\", node_path=\"dataset.data_vars['sst']\", rule_id='xcube/data-var-colors', severity=1, fatal=None, fix=None, suggestions=None), Message(message=\"Missing attribute 'color_bar_name'.\", node_path=\"dataset.data_vars['sst_anomaly']\", rule_id='xcube/data-var-colors', severity=1, fatal=None, fix=None, suggestions=None)], fixable_error_count=0, fixable_warning_count=0, error_count=7, fatal_error_count=0, warning_count=5)" ] }, "execution_count": 9, @@ -1143,14 +1546,14 @@ "

<dataset>:

\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "
dataset.attrs warn Missing metadata, attributes are empty. humpty-dumpty/no-empty-attrs
dataset errorMissing 'title' attribute in dataset. humpty-dumpty/dataset-title-attr
dataset.data_vars['sst']warn Invalid 'units' attribute in variable 'sst'.humpty-dumpty/var-units-attr
dataset.attrswarn Missing metadata, attributes are empty. humpty-dumpty/no-empty-attrs
dataset errorMissing 'title' attribute in dataset. humpty-dumpty/dataset-title-attr
dataset errorunknown rule 'humpty-dumpty/var-units-attr'humpty-dumpty/var-units-attr

3 problems (one error and 2 warnings)

\n" + "

3 problems (2 errors and one warning)

\n" ], "text/plain": [ - "Result(config=Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'humpty-dumpty': Plugin(meta=PluginMeta(name='__core__', version='0.4.0.dev0', ref='xrlint.plugins.core:export_plugin'), rules={'coords-for-dims': Rule(meta=RuleMeta(name='coords-for-dims', version='1.0.0', description='Dimensions of data variables should have corresponding coordinates.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'dataset-title-attr': Rule(meta=RuleMeta(name='dataset-title-attr', version='1.0.0', description='Datasets should be given a non-empty title.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'flags': Rule(meta=RuleMeta(name='flags', version='1.0.0', description=\"Validate attributes 'flag_values', 'flag_masks' and 'flag_meanings' that make variables that contain flag values self describing. \", schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#flags', type='suggestion'), op_class=), 'grid-mappings': Rule(meta=RuleMeta(name='grid-mappings', version='1.0.0', description='Grid mappings, if any, shall have valid grid mapping coordinate variables.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'lat-coordinate': Rule(meta=RuleMeta(name='lat-coordinate', version='1.0.0', description='Latitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate', type='problem'), op_class=), 'lon-coordinate': Rule(meta=RuleMeta(name='lon-coordinate', version='1.0.0', description='Longitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate', type='problem'), op_class=), 'no-empty-attrs': Rule(meta=RuleMeta(name='no-empty-attrs', version='1.0.0', description='Every dataset element should have metadata that describes it.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'no-empty-chunks': Rule(meta=RuleMeta(name='no-empty-chunks', version='1.0.0', description='Empty chunks should not be encoded and written. The rule currently applies to Zarr format only.', schema=None, ref=None, docs_url='https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html#xarray-dataset-to-zarr', type='suggestion'), op_class=), 'time-coordinate': Rule(meta=RuleMeta(name='time-coordinate', version='1.0.0', description='Time coordinates should have valid and unambiguous time units encoding.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate', type='problem'), op_class=), 'var-units-attr': Rule(meta=RuleMeta(name='var-units-attr', version='1.0.0', description=\"Every variable should have a valid 'units' attribute.\", schema=None, ref=None, docs_url=None, type='suggestion'), op_class=)}, processors={}, configs={'recommended': [Config(name='recommended', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'flags': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None)], 'all': [Config(name='all', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'flags': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=2, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)]})}, rules={'humpty-dumpty/no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'humpty-dumpty/dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'humpty-dumpty/var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), file_path='', messages=[Message(message='Missing metadata, attributes are empty.', node_path='dataset.attrs', rule_id='humpty-dumpty/no-empty-attrs', severity=1, fatal=None, fix=None, suggestions=[Suggestion(desc='Make sure to add appropriate metadata attributes to dataset elements.', data=None, fix=None)]), Message(message=\"Missing 'title' attribute in dataset.\", node_path='dataset', rule_id='humpty-dumpty/dataset-title-attr', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"Invalid 'units' attribute in variable 'sst'.\", node_path=\"dataset.data_vars['sst']\", rule_id='humpty-dumpty/var-units-attr', severity=1, fatal=None, fix=None, suggestions=None)], fixable_error_count=0, fixable_warning_count=0, error_count=1, fatal_error_count=0, warning_count=2)" + "Result(config=Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'humpty-dumpty': Plugin(meta=PluginMeta(name='__core__', version='0.4.1', ref='xrlint.plugins.core:export_plugin'), rules={'content-desc': Rule(meta=RuleMeta(name='content-desc', version='1.0.0', description=\"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:\\n\\n- `globals`: list of names of required global attributes. Defaults to `['title', 'history']`.\\n- `commons`: list of names of required variable attributes that can also be defined globally. Defaults to `['institution', 'source', 'references', 'comment']`.\\n- `no_vars`: do not check variables at all. Defaults to `False`.\\n- `ignored_vars`: list of ignored variables (regex patterns). Defaults to `['crs', 'spatial_ref']`.\\n\", schema={'type': 'object', 'properties': {'globals': {'type': 'array', 'default': ['title', 'history'], 'items': {'type': 'string'}, 'title': 'Global attribute names'}, 'commons': {'type': 'array', 'default': ['institution', 'source', 'references', 'comment'], 'items': {'type': 'string'}, 'title': 'Common attribute names'}, 'skip_vars': {'type': 'boolean', 'default': False, 'title': 'Do not check variables'}, 'ignored_vars': {'type': 'array', 'default': ['crs', 'spatial_ref'], 'items': {'type': 'string'}, 'title': 'Ignored variables (regex name patterns)'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#description-of-file-contents', type='suggestion'), op_class=), 'conventions': Rule(meta=RuleMeta(name='conventions', version='1.0.0', description='Datasets should identify the applicable conventions using the `Conventions` attribute.\\n The rule has an optional configuration parameter `match` which is a regex pattern that the value of the `Conventions` attribute must match, if any. If not provided, the rule just verifies that the attribute exists and whether it is a character string.', schema={'type': 'object', 'properties': {'match': {'type': 'string', 'title': 'Regex pattern'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#identification-of-conventions', type='suggestion'), op_class=), 'coords-for-dims': Rule(meta=RuleMeta(name='coords-for-dims', version='1.0.0', description='Dimensions of data variables should have corresponding coordinates.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'dataset-title-attr': Rule(meta=RuleMeta(name='dataset-title-attr', version='1.0.0', description='Datasets should be given a non-empty title.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'grid-mappings': Rule(meta=RuleMeta(name='grid-mappings', version='1.0.0', description='Grid mappings, if any, shall have valid grid mapping coordinate variables.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'lat-coordinate': Rule(meta=RuleMeta(name='lat-coordinate', version='1.0.0', description='Latitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate', type='problem'), op_class=), 'lon-coordinate': Rule(meta=RuleMeta(name='lon-coordinate', version='1.0.0', description='Longitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate', type='problem'), op_class=), 'no-empty-attrs': Rule(meta=RuleMeta(name='no-empty-attrs', version='1.0.0', description='Every dataset element should have metadata that describes it.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'no-empty-chunks': Rule(meta=RuleMeta(name='no-empty-chunks', version='1.0.0', description='Empty chunks should not be encoded and written. The rule currently applies to Zarr format only.', schema=None, ref=None, docs_url='https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html#xarray-dataset-to-zarr', type='suggestion'), op_class=), 'time-coordinate': Rule(meta=RuleMeta(name='time-coordinate', version='1.0.0', description='Time coordinates should have valid and unambiguous time units encoding.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate', type='problem'), op_class=), 'var-desc': Rule(meta=RuleMeta(name='var-desc', version='1.0.0', description=\"Check that each data variable provides an identification and description of the content. The rule can be configured by parameter `attrs` which is a list of names of attributes that provides descriptive information. It defaults to `['standard_name', 'long_name']`.\", schema={'type': 'object', 'properties': {'attrs': {'type': 'array', 'default': ['standard_name', 'long_name'], 'items': {'type': 'string'}, 'title': 'Attribute names to check'}}}, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#standard-name', type='suggestion'), op_class=), 'var-flags': Rule(meta=RuleMeta(name='var-flags', version='1.0.0', description=\"Validate attributes 'flag_values', 'flag_masks' and 'flag_meanings' that make variables that contain flag values self describing. \", schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#flags', type='suggestion'), op_class=), 'var-units': Rule(meta=RuleMeta(name='var-units', version='1.0.0', description='Every variable should provide a description of its units.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#units', type='suggestion'), op_class=)}, processors={}, configs={'recommended': [Config(name='recommended', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'content-desc': RuleConfig(severity=1, args=(), kwargs={}), 'conventions': RuleConfig(severity=1, args=(), kwargs={}), 'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-desc': RuleConfig(severity=1, args=(), kwargs={}), 'var-flags': RuleConfig(severity=2, args=(), kwargs={}), 'var-units': RuleConfig(severity=1, args=(), kwargs={})}, settings=None)], 'all': [Config(name='all', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'content-desc': RuleConfig(severity=2, args=(), kwargs={}), 'conventions': RuleConfig(severity=2, args=(), kwargs={}), 'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-chunks': RuleConfig(severity=2, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-desc': RuleConfig(severity=2, args=(), kwargs={}), 'var-flags': RuleConfig(severity=2, args=(), kwargs={}), 'var-units': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)]})}, rules={'humpty-dumpty/no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'humpty-dumpty/dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'humpty-dumpty/var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), file_path='', messages=[Message(message='Missing metadata, attributes are empty.', node_path='dataset.attrs', rule_id='humpty-dumpty/no-empty-attrs', severity=1, fatal=None, fix=None, suggestions=[Suggestion(desc='Make sure to add appropriate metadata attributes to dataset elements.', data=None, fix=None)]), Message(message=\"Missing 'title' attribute in dataset.\", node_path='dataset', rule_id='humpty-dumpty/dataset-title-attr', severity=2, fatal=None, fix=None, suggestions=None), Message(message=\"unknown rule 'humpty-dumpty/var-units-attr'\", node_path='dataset', rule_id='humpty-dumpty/var-units-attr', severity=2, fatal=True, fix=None, suggestions=None)], fixable_error_count=0, fixable_warning_count=0, error_count=2, fatal_error_count=1, warning_count=1)" ] }, "execution_count": 11, @@ -1269,16 +1672,19 @@ "text/plain": [ "{'configs': [{'plugins': {'__core__': 'xrlint.plugins.core:export_plugin'}},\n", " {'name': 'recommended',\n", - " 'rules': {'coords-for-dims': 2,\n", + " 'rules': {'content-desc': 1,\n", + " 'conventions': 1,\n", + " 'coords-for-dims': 2,\n", " 'dataset-title-attr': 1,\n", - " 'flags': 2,\n", " 'grid-mappings': 2,\n", " 'lat-coordinate': 2,\n", " 'lon-coordinate': 2,\n", " 'no-empty-attrs': 1,\n", " 'no-empty-chunks': 1,\n", " 'time-coordinate': 2,\n", - " 'var-units-attr': 1}}]}" + " 'var-desc': 1,\n", + " 'var-flags': 2,\n", + " 'var-units': 1}}]}" ] }, "execution_count": 17, From 446ec2691f7042ddbb5eb23307495b43f7568443 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Thu, 30 Jan 2025 18:28:41 +0100 Subject: [PATCH 03/12] Trying to detect write_empty_chunks --- notebooks/xrlint-cli.ipynb | 230 ++++++++++++------- xrlint/plugins/core/rules/no_empty_chunks.py | 14 +- 2 files changed, 161 insertions(+), 83 deletions(-) diff --git a/notebooks/xrlint-cli.ipynb b/notebooks/xrlint-cli.ipynb index 7f9a2ce..06f526b 100644 --- a/notebooks/xrlint-cli.ipynb +++ b/notebooks/xrlint-cli.ipynb @@ -108,7 +108,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -121,7 +121,7 @@ "\n", "make_dataset().to_zarr(\"valid.zarr\", mode=\"w\", encoding=dict(\n", " sst=dict(write_empty_chunks=False), \n", - " sst_anomaly=dict(write_empty_chunks=False)\n", + " sst_anomaly=dict(write_empty_chunks=False),\n", "))\n", "make_dataset_with_issues().to_zarr(\"invalid.zarr\", mode=\"w\")" ] @@ -137,58 +137,134 @@ "text": [ "\n", "valid.zarr:\n", - "dataset.coords['x'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", - "dataset.coords['y'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", - "dataset.data_vars['sst'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", - "dataset.data_vars['sst_anomaly'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", - "dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units\n", + "dataset.data_vars['sst'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks\n", + "dataset.data_vars['sst_anomaly'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks\n", + "dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units\n", "\n", "invalid.zarr:\n", - "dataset warn Missing attribute 'title'. content-desc\n", - "dataset warn Missing attribute 'history'. content-desc\n", - "dataset.data_vars['sst'] warn Missing attribute 'institution'. content-desc\n", - "dataset.data_vars['sst'] warn Missing attribute 'source'. content-desc\n", - "dataset.data_vars['sst'] warn Missing attribute 'references'. content-desc\n", - "dataset.data_vars['sst'] warn Missing attribute 'comment'. content-desc\n", - "dataset.data_vars['sst_anomaly'] warn Missing attribute 'institution'. content-desc\n", - "dataset.data_vars['sst_anomaly'] warn Missing attribute 'source'. content-desc\n", - "dataset.data_vars['sst_anomaly'] warn Missing attribute 'references'. content-desc\n", - "dataset.data_vars['sst_anomaly'] warn Missing attribute 'comment'. content-desc\n", - "dataset.data_vars['sst_avg'] warn Missing attribute 'institution'. content-desc\n", - "dataset.data_vars['sst_avg'] warn Missing attribute 'source'. content-desc\n", - "dataset.data_vars['sst_avg'] warn Missing attribute 'references'. content-desc\n", - "dataset.data_vars['sst_avg'] warn Missing attribute 'comment'. content-desc\n", - "dataset warn Missing attribute 'Conventions'. conventions\n", - "dataset warn Missing 'title' attribute in dataset. dataset-title-attr\n", - "dataset.coords['y'] error Attribute 'standard_name' should be 'latitude', was None. lat-coordinate\n", - "dataset.coords['y'] error Attribute 'axis' should be 'Y', was 'y'. lat-coordinate\n", - "dataset.coords['x'] error Attribute 'units' should be 'degrees_east', was 'degrees'. lon-coordinate\n", - "dataset.coords['x'] error Attribute 'axis' should be 'X', was 'x'. lon-coordinate\n", - "dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs\n", - "dataset.coords['x'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", - "dataset.coords['y'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", - "dataset.data_vars['sst'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", - "dataset.data_vars['sst_anomaly'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", - "dataset.data_vars['sst_avg'] warn Consider writing the dataset using 'write_empty_chunks=True'. no-empty-chunks\n", - "dataset.coords['time'] error Invalid 'units' attribute: 'days since 2020-01-01 UTC'. time-coordinate\n", - "dataset.data_vars['sst_avg'] warn Missing attribute 'standard_name'. var-desc\n", - "dataset.data_vars['sst_avg'] warn Missing attribute 'long_name'. var-desc\n", - "dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units\n", - "dataset.data_vars['sst'] warn Invalid 'units' attribute in variable 'sst'. var-units\n", + "dataset warn Missing attribute 'title'. content-desc\n", + "dataset warn Missing attribute 'history'. content-desc\n", + "dataset.data_vars['sst'] warn Missing attribute 'institution'. content-desc\n", + "dataset.data_vars['sst'] warn Missing attribute 'source'. content-desc\n", + "dataset.data_vars['sst'] warn Missing attribute 'references'. content-desc\n", + "dataset.data_vars['sst'] warn Missing attribute 'comment'. content-desc\n", + "dataset.data_vars['sst_anomaly'] warn Missing attribute 'institution'. content-desc\n", + "dataset.data_vars['sst_anomaly'] warn Missing attribute 'source'. content-desc\n", + "dataset.data_vars['sst_anomaly'] warn Missing attribute 'references'. content-desc\n", + "dataset.data_vars['sst_anomaly'] warn Missing attribute 'comment'. content-desc\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'institution'. content-desc\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'source'. content-desc\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'references'. content-desc\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'comment'. content-desc\n", + "dataset warn Missing attribute 'Conventions'. conventions\n", + "dataset warn Missing 'title' attribute in dataset. dataset-title-attr\n", + "dataset.coords['y'] error Attribute 'standard_name' should be 'latitude', was None. lat-coordinate\n", + "dataset.coords['y'] error Attribute 'axis' should be 'Y', was 'y'. lat-coordinate\n", + "dataset.coords['x'] error Attribute 'units' should be 'degrees_east', was 'degrees'. lon-coordinate\n", + "dataset.coords['x'] error Attribute 'axis' should be 'X', was 'x'. lon-coordinate\n", + "dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs\n", + "dataset.data_vars['sst'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks\n", + "dataset.data_vars['sst_anomaly'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks\n", + "dataset.data_vars['sst_avg'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks\n", + "dataset.coords['time'] error Invalid 'units' attribute: 'days since 2020-01-01 UTC'. time-coordinate\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'standard_name'. var-desc\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'long_name'. var-desc\n", + "dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units\n", + "dataset.data_vars['sst'] warn Invalid 'units' attribute in variable 'sst'. var-units\n", "\n", - "36 problems (5 errors and 31 warnings)\n", + "32 problems (5 errors and 27 warnings)\n", "\n" ] } ], "source": [ - "!xrlint --no-color valid.zarr invalid.zarr" + "!xrlint --no-color valid.zarr invalid.zarr " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "valid.zarr:\n", + "dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units\n", + "\n", + "invalid.zarr:\n", + "dataset warn Missing attribute 'Conventions'. conventions\n", + "dataset warn Missing 'title' attribute in dataset. dataset-title-attr\n", + "dataset.coords['y'] error Attribute 'standard_name' should be 'latitude', was None. lat-coordinate\n", + "dataset.coords['y'] error Attribute 'axis' should be 'Y', was 'y'. lat-coordinate\n", + "dataset.coords['x'] error Attribute 'units' should be 'degrees_east', was 'degrees'. lon-coordinate\n", + "dataset.coords['x'] error Attribute 'axis' should be 'X', was 'x'. lon-coordinate\n", + "dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs\n", + "dataset.coords['time'] error Invalid 'units' attribute: 'days since 2020-01-01 UTC'. time-coordinate\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'standard_name'. var-desc\n", + "dataset.data_vars['sst_avg'] warn Missing attribute 'long_name'. var-desc\n", + "dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units\n", + "dataset.data_vars['sst'] warn Invalid 'units' attribute in variable 'sst'. var-units\n", + "\n", + "13 problems (5 errors and 8 warnings)\n", + "\n" + ] + } + ], + "source": [ + "!xrlint --no-color --rule \"content-desc: off\" --rule \"no-empty-chunks: off\" valid.zarr invalid.zarr " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "valid.zarr:\n", + "dataset.data_vars['sst'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks\n", + "dataset.data_vars['sst_anomaly'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks\n", + "dataset.data_vars['sst'] error Missing attribute 'description'. var-desc\n", + "dataset.data_vars['sst_anomaly'] error Missing attribute 'description'. var-desc\n", + "dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units\n", + "\n", + "invalid.zarr:\n", + "dataset warn Missing attribute 'Conventions'. conventions\n", + "dataset warn Missing 'title' attribute in dataset. dataset-title-attr\n", + "dataset.coords['y'] error Attribute 'standard_name' should be 'latitude', was None. lat-coordinate\n", + "dataset.coords['y'] error Attribute 'axis' should be 'Y', was 'y'. lat-coordinate\n", + "dataset.coords['x'] error Attribute 'units' should be 'degrees_east', was 'degrees'. lon-coordinate\n", + "dataset.coords['x'] error Attribute 'axis' should be 'X', was 'x'. lon-coordinate\n", + "dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs\n", + "dataset.data_vars['sst'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks\n", + "dataset.data_vars['sst_anomaly'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks\n", + "dataset.data_vars['sst_avg'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks\n", + "dataset.coords['time'] error Invalid 'units' attribute: 'days since 2020-01-01 UTC'. time-coordinate\n", + "dataset.data_vars['sst'] error Missing attribute 'description'. var-desc\n", + "dataset.data_vars['sst_anomaly'] error Missing attribute 'description'. var-desc\n", + "dataset.data_vars['sst_avg'] error Missing attribute 'description'. var-desc\n", + "dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units\n", + "dataset.data_vars['sst'] warn Invalid 'units' attribute in variable 'sst'. var-units\n", + "\n", + "21 problems (10 errors and 11 warnings)\n", + "\n" + ] + } + ], + "source": [ + "!xrlint --no-color --rule \"content-desc: off\" --rule \"var-desc: [error, {'attrs': ['description']}]\" valid.zarr invalid.zarr " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -200,52 +276,48 @@ "

valid.zarr:

\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "
dataset.coords['x'] warnConsider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.coords['y'] warnConsider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.data_vars['sst'] warnConsider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.data_vars['sst_anomaly']warnConsider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.coords['time'] warnMissing 'units' attribute in variable 'time'. var-units
dataset.data_vars['sst'] warnConsider writing with 'write_empty_chunks=False'.no-empty-chunks
dataset.data_vars['sst_anomaly']warnConsider writing with 'write_empty_chunks=False'.no-empty-chunks
dataset.coords['time'] warnMissing 'units' attribute in variable 'time'. var-units

5 warnings

\n", + "

3 warnings

\n", "\n", "
\n", "
\n", "

invalid.zarr:

\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "
dataset warn Missing attribute 'title'. content-desc
dataset warn Missing attribute 'history'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'source'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'references'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'comment'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'source'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'references'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'comment'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'source'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'references'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'comment'. content-desc
dataset warn Missing attribute 'Conventions'. conventions
dataset warn Missing 'title' attribute in dataset. dataset-title-attr
dataset.coords['y'] errorAttribute 'standard_name' should be 'latitude', was None. lat-coordinate
dataset.coords['y'] errorAttribute 'axis' should be 'Y', was 'y'. lat-coordinate
dataset.coords['x'] errorAttribute 'units' should be 'degrees_east', was 'degrees'. lon-coordinate
dataset.coords['x'] errorAttribute 'axis' should be 'X', was 'x'. lon-coordinate
dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs
dataset.coords['x'] warn Consider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.coords['y'] warn Consider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.data_vars['sst'] warn Consider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.data_vars['sst_anomaly']warn Consider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.data_vars['sst_avg'] warn Consider writing the dataset using 'write_empty_chunks=True'.no-empty-chunks
dataset.coords['time'] errorInvalid 'units' attribute: 'days since 2020-01-01 UTC'. time-coordinate
dataset.data_vars['sst_avg'] warn Missing attribute 'standard_name'. var-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'long_name'. var-desc
dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units
dataset.data_vars['sst'] warn Invalid 'units' attribute in variable 'sst'. var-units
dataset warn Missing attribute 'title'. content-desc
dataset warn Missing attribute 'history'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'source'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'references'. content-desc
dataset.data_vars['sst'] warn Missing attribute 'comment'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'source'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'references'. content-desc
dataset.data_vars['sst_anomaly']warn Missing attribute 'comment'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'institution'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'source'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'references'. content-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'comment'. content-desc
dataset warn Missing attribute 'Conventions'. conventions
dataset warn Missing 'title' attribute in dataset. dataset-title-attr
dataset.coords['y'] errorAttribute 'standard_name' should be 'latitude', was None. lat-coordinate
dataset.coords['y'] errorAttribute 'axis' should be 'Y', was 'y'. lat-coordinate
dataset.coords['x'] errorAttribute 'units' should be 'degrees_east', was 'degrees'.lon-coordinate
dataset.coords['x'] errorAttribute 'axis' should be 'X', was 'x'. lon-coordinate
dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs
dataset.data_vars['sst'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks
dataset.data_vars['sst_anomaly']warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks
dataset.data_vars['sst_avg'] warn Consider writing with 'write_empty_chunks=False'. no-empty-chunks
dataset.coords['time'] errorInvalid 'units' attribute: 'days since 2020-01-01 UTC'. time-coordinate
dataset.data_vars['sst_avg'] warn Missing attribute 'standard_name'. var-desc
dataset.data_vars['sst_avg'] warn Missing attribute 'long_name'. var-desc
dataset.coords['time'] warn Missing 'units' attribute in variable 'time'. var-units
dataset.data_vars['sst'] warn Invalid 'units' attribute in variable 'sst'. var-units

31 problems (5 errors and 26 warnings)

\n", + "

29 problems (5 errors and 24 warnings)

\n", "
\n", "\n", "\n" @@ -258,7 +330,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "metadata": {}, "outputs": [ { diff --git a/xrlint/plugins/core/rules/no_empty_chunks.py b/xrlint/plugins/core/rules/no_empty_chunks.py index f243d8f..2ae38b3 100644 --- a/xrlint/plugins/core/rules/no_empty_chunks.py +++ b/xrlint/plugins/core/rules/no_empty_chunks.py @@ -25,9 +25,15 @@ def dataset(self, ctx: RuleContext, node: DataArrayNode): raise RuleExit def data_array(self, ctx: RuleContext, node: DataArrayNode): + if node.name in ctx.dataset.coords: + return + + array = node.data_array + encoding = array.encoding if ( - "write_empty_chunks" not in node.data_array.encoding - and "chunks" in node.data_array.encoding - and "_FillValue" in node.data_array.encoding + "write_empty_chunks" not in encoding + and "_FillValue" in encoding + and "chunks" in encoding + and tuple(encoding.get("chunks")) != tuple(array.shape) ): - ctx.report("Consider writing the dataset using 'write_empty_chunks=True'.") + ctx.report("Consider writing with 'write_empty_chunks=False'.") From 5cb37b92df7eb42fae9911348e77ef35be1a7f34 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Fri, 31 Jan 2025 19:21:55 +0100 Subject: [PATCH 04/12] changed rule "np-empty-chunks" --- CHANGES.md | 5 +++ xrlint/plugins/core/__init__.py | 2 +- xrlint/plugins/core/rules/no_empty_chunks.py | 35 +++++++++----------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a1c95a1..bca6e65 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,11 @@ ## Version 0.4.2 (in development) +- Rule `no-empty-chunks` has taken off the `"recommended"` settings + as there is no easy/efficient way to tell whether a dataset has + been written using `write_emtpy_chunks` option or not. + The rule message itself has been fixed. (#45) + ## Version 0.4.1 (from 2025-01-31) ### Changes diff --git a/xrlint/plugins/core/__init__.py b/xrlint/plugins/core/__init__.py index 916dec7..f931321 100644 --- a/xrlint/plugins/core/__init__.py +++ b/xrlint/plugins/core/__init__.py @@ -20,7 +20,7 @@ def export_plugin() -> Plugin: "lat-coordinate": "error", "lon-coordinate": "error", "no-empty-attrs": "warn", - "no-empty-chunks": "warn", + "no-empty-chunks": "off", "time-coordinate": "error", "var-desc": "warn", "var-flags": "error", diff --git a/xrlint/plugins/core/rules/no_empty_chunks.py b/xrlint/plugins/core/rules/no_empty_chunks.py index 2ae38b3..19d742c 100644 --- a/xrlint/plugins/core/rules/no_empty_chunks.py +++ b/xrlint/plugins/core/rules/no_empty_chunks.py @@ -1,4 +1,4 @@ -from xrlint.node import DataArrayNode +from xrlint.node import DatasetNode from xrlint.plugins.core.plugin import plugin from xrlint.rule import RuleContext, RuleExit, RuleOp @@ -17,23 +17,18 @@ ), ) class NoEmptyChunks(RuleOp): - def dataset(self, ctx: RuleContext, node: DataArrayNode): - source = ctx.dataset.encoding.get("source") + def dataset(self, ctx: RuleContext, node: DatasetNode): + source = node.dataset.encoding.get("source") is_zarr = isinstance(source, str) and source.endswith(".zarr") - if not is_zarr: - # if not a Zarr, no need to check further - raise RuleExit - - def data_array(self, ctx: RuleContext, node: DataArrayNode): - if node.name in ctx.dataset.coords: - return - - array = node.data_array - encoding = array.encoding - if ( - "write_empty_chunks" not in encoding - and "_FillValue" in encoding - and "chunks" in encoding - and tuple(encoding.get("chunks")) != tuple(array.shape) - ): - ctx.report("Consider writing with 'write_empty_chunks=False'.") + if is_zarr: + for var in node.dataset.data_vars.values(): + is_chunked_in_storage = ( + "_FillValue" in var.encoding + and "chunks" in var.encoding + and tuple(var.encoding.get("chunks")) != tuple(var.shape) + ) + if is_chunked_in_storage: + ctx.report("Consider writing with `write_empty_chunks=False`.") + break + # no need to traverse further + raise RuleExit From e647c66ad48a39dbd994911f31c95fe110a68932 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Fri, 31 Jan 2025 20:06:38 +0100 Subject: [PATCH 05/12] fix tests --- notebooks/mkdataset.py | 3 ++- tests/plugins/core/rules/test_no_empty_chunks.py | 13 +++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/notebooks/mkdataset.py b/notebooks/mkdataset.py index 0b0aa6c..ebc9998 100644 --- a/notebooks/mkdataset.py +++ b/notebooks/mkdataset.py @@ -9,6 +9,7 @@ ncy = 180 nct = 1 + def make_dataset() -> xr.Dataset: """Create a dataset that passes xrlint core rules.""" @@ -77,7 +78,7 @@ def make_dataset() -> xr.Dataset: attrs={ "standard_name": "sea_surface_temperature_anomaly", "long_name": "sea surface temperature anomaly", - "units": "kelvin", + "units": "kelvin", "grid_mapping": "spatial_ref", }, ), diff --git a/tests/plugins/core/rules/test_no_empty_chunks.py b/tests/plugins/core/rules/test_no_empty_chunks.py index b54378e..9f0db7c 100644 --- a/tests/plugins/core/rules/test_no_empty_chunks.py +++ b/tests/plugins/core/rules/test_no_empty_chunks.py @@ -3,28 +3,22 @@ from xrlint.plugins.core.rules.no_empty_chunks import NoEmptyChunks from xrlint.testing import RuleTest, RuleTester -# valid, because it does not write empty chunks +# valid, because it is not chunked valid_dataset_0 = xr.Dataset(attrs=dict(title="OC-Climatology")) valid_dataset_0.encoding["source"] = "test.zarr" valid_dataset_0["sst"] = xr.DataArray([273, 274, 272], dims="time") valid_dataset_0["sst"].encoding["_FillValue"] = 0 -valid_dataset_0["sst"].encoding["chunks"] = [ - 1, -] -valid_dataset_0["sst"].encoding["write_empty_chunks"] = False +valid_dataset_0["sst"].encoding["chunks"] = [3] # valid, because it does not apply valid_dataset_1 = valid_dataset_0.copy() del valid_dataset_1.encoding["source"] # valid, because it does not apply valid_dataset_2 = valid_dataset_0.copy() valid_dataset_2.encoding["source"] = "test.nc" -# valid, because it does not apply -valid_dataset_3 = valid_dataset_0.copy() -valid_dataset_3.sst.encoding["write_empty_chunks"] = True # valid, because it does not apply invalid_dataset_0 = valid_dataset_0.copy() -del invalid_dataset_0.sst.encoding["write_empty_chunks"] +invalid_dataset_0["sst"].encoding["chunks"] = [1] NoEmptyChunksTest = RuleTester.define_test( "no-empty-chunks", @@ -33,7 +27,6 @@ RuleTest(dataset=valid_dataset_0), RuleTest(dataset=valid_dataset_1), RuleTest(dataset=valid_dataset_2), - RuleTest(dataset=valid_dataset_3), ], invalid=[ RuleTest(dataset=invalid_dataset_0, expected=1), From 5088d2ebc80b3632561a3f477cd32c26b3e9f6b0 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Fri, 31 Jan 2025 20:20:51 +0100 Subject: [PATCH 06/12] add docs --- docs/rule-ref.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rule-ref.md b/docs/rule-ref.md index 4b9295b..3301e05 100644 --- a/docs/rule-ref.md +++ b/docs/rule-ref.md @@ -69,7 +69,7 @@ Contained in: `all`-:material-lightning-bolt: `recommended`-:material-alert: Empty chunks should not be encoded and written. The rule currently applies to Zarr format only. [:material-information-variant:](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_zarr.html#xarray-dataset-to-zarr) -Contained in: `all`-:material-lightning-bolt: `recommended`-:material-alert: +Contained in: `all`-:material-lightning-bolt: `recommended`-:material-circle-off-outline: ### :material-bug: `time-coordinate` From be12d3f324e897845c19bd26c3535bde9d3bd4e9 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Sat, 1 Feb 2025 12:06:19 +0100 Subject: [PATCH 07/12] releasing 0.4.2 --- CHANGES.md | 4 +++- xrlint/plugins/core/rules/var_units.py | 22 +++++++++++++++------- xrlint/version.py | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bca6e65..0e6de74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,14 @@ # XRLint Change History -## Version 0.4.2 (in development) +## Version 0.4.2 (from 2025-02-01) - Rule `no-empty-chunks` has taken off the `"recommended"` settings as there is no easy/efficient way to tell whether a dataset has been written using `write_emtpy_chunks` option or not. The rule message itself has been fixed. (#45) +- Adjusted messages of rule `var-units` to better match other rules. + ## Version 0.4.1 (from 2025-01-31) ### Changes diff --git a/xrlint/plugins/core/rules/var_units.py b/xrlint/plugins/core/rules/var_units.py index 0a4350d..269fa6c 100644 --- a/xrlint/plugins/core/rules/var_units.py +++ b/xrlint/plugins/core/rules/var_units.py @@ -12,12 +12,20 @@ ) class VarUnits(RuleOp): def data_array(self, ctx: RuleContext, node: DataArrayNode): - data_array = node.data_array - units = data_array.attrs.get("units") - if units is None: - if "grid_mapping_name" not in data_array.attrs: - ctx.report(f"Missing 'units' attribute in variable {node.name!r}.") + array = node.data_array + attrs = array.attrs + + if "grid_mapping_name" in attrs: + # likely grid mapping variable --> rule "gid-mappings" + return + if "units" in array.encoding: + # likely time coordinate --> rule "time-coordinate" + return + + units = attrs.get("units") + if "units" not in attrs: + ctx.report("Missing attribute 'units'.") elif not isinstance(units, str): - ctx.report(f"Invalid 'units' attribute in variable {node.name!r}.") + ctx.report(f"Invalid attribute 'units': {units!r}") elif not units: - ctx.report(f"Empty 'units' attribute in variable {node.name!r}.") + ctx.report("Empty attribute 'units'.") diff --git a/xrlint/version.py b/xrlint/version.py index 8a96ab0..3c4d76a 100644 --- a/xrlint/version.py +++ b/xrlint/version.py @@ -1 +1 @@ -version = "0.4.2.dev0" +version = "0.4.2" From 7f5c6070d898b187fdd3c9ccee03074fc050e2ee Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Sat, 1 Feb 2025 12:08:47 +0100 Subject: [PATCH 08/12] updated various rules --- .../rules/test_dataset_title_attr.py | 8 ++-- xrlint/plugins/core/rules/time_coordinate.py | 41 ++++++++++++------- .../rules/dataset_title.py} | 11 ++--- 3 files changed, 36 insertions(+), 24 deletions(-) rename tests/plugins/{core => xcube}/rules/test_dataset_title_attr.py (75%) rename xrlint/plugins/{core/rules/dataset_title_attr.py => xcube/rules/dataset_title.py} (56%) diff --git a/tests/plugins/core/rules/test_dataset_title_attr.py b/tests/plugins/xcube/rules/test_dataset_title_attr.py similarity index 75% rename from tests/plugins/core/rules/test_dataset_title_attr.py rename to tests/plugins/xcube/rules/test_dataset_title_attr.py index 8fb9c55..d11987b 100644 --- a/tests/plugins/core/rules/test_dataset_title_attr.py +++ b/tests/plugins/xcube/rules/test_dataset_title_attr.py @@ -1,6 +1,6 @@ import xarray as xr -from xrlint.plugins.core.rules.dataset_title_attr import DatasetTitleAttr +from xrlint.plugins.xcube.rules.dataset_title import DatasetTitle from xrlint.testing import RuleTest, RuleTester valid_dataset_1 = xr.Dataset(attrs=dict(title="OC-Climatology")) @@ -9,9 +9,9 @@ invalid_dataset_2 = xr.Dataset(attrs=dict(title="")) -DatasetTitleAttrTest = RuleTester.define_test( - "dataset-title-attr", - DatasetTitleAttr, +DatasetTitleTest = RuleTester.define_test( + "dataset-title", + DatasetTitle, valid=[ RuleTest(dataset=valid_dataset_1), RuleTest(dataset=valid_dataset_2), diff --git a/xrlint/plugins/core/rules/time_coordinate.py b/xrlint/plugins/core/rules/time_coordinate.py index 8efc75c..dfa65ad 100644 --- a/xrlint/plugins/core/rules/time_coordinate.py +++ b/xrlint/plugins/core/rules/time_coordinate.py @@ -54,19 +54,27 @@ class TimeCoordinate(RuleOp): def data_array(self, ctx: RuleContext, node: DataArrayNode): array = node.data_array - attrs = array.attrs encoding = array.encoding + attrs = array.attrs + + units: str | None = None + source: str | None = None + if "units" in encoding: + units = encoding["units"] + source = "encoding" + elif "units" in attrs: + units = attrs["units"] + source = "attribute" - units: str | None = encoding.get("units", attrs.get("units")) - if units is None: + if source is None: if _is_time_by_name(attrs): - ctx.report("Missing 'units' attribute for time coordinate.") + ctx.report("Missing encoding/attribute 'units'.") # No more to check w.o. time units return elif not isinstance(units, str): if _is_time_by_name(attrs): ctx.report( - f"Invalid 'units' attribute for time coordinate," + f"Invalid {source} 'units'," f" expected type str, got {type(units).__name__}." ) # No more to check w.o. time units @@ -83,11 +91,14 @@ def data_array(self, ctx: RuleContext, node: DataArrayNode): units_ok = False else: # We have time units - - if not encoding.get("calendar", attrs.get("calendar")): - ctx.report( - "Attribute/encoding 'calendar' should be specified.", - ) + calendar: str | None = None + if source == "encoding" and "calendar" in encoding: + calendar = encoding["calendar"] + elif source == "attribute" and "calendar" in attrs: + calendar = attrs["calendar"] + # Note, we should also check here for valid calendar names + if calendar is None: + ctx.report(f"Missing {source} 'calendar'." ) uot_part = units_parts[0] date_part = units_parts[2] @@ -97,7 +108,7 @@ def data_array(self, ctx: RuleContext, node: DataArrayNode): if uot_part not in _ALL_UNITS_OF_TIME: ctx.report( f"Unrecognized units of measure for time" - f" in 'units' attribute: {units!r}.", + f" in {source} 'units': {units!r}.", suggestions=[ _units_format_suggestion(), _units_of_time_suggestion(), @@ -106,7 +117,7 @@ def data_array(self, ctx: RuleContext, node: DataArrayNode): elif uot_part in _AMBIGUOUS_UNITS_OF_TIME: ctx.report( f"Ambiguous units of measure for time in" - f" 'units' attribute: {units!r}.", + f" {source} 'units': {units!r}.", suggestions=[ _units_format_suggestion(), _units_of_time_suggestion(), @@ -138,7 +149,7 @@ def data_array(self, ctx: RuleContext, node: DataArrayNode): if not tz_part: ctx.report( - f"Missing timezone in 'units' attribute: {units!r}.", + f"Missing timezone in {source} 'units': {units!r}.", suggestions=[ _units_format_suggestion(), f"Append timezone specification, e.g., use" @@ -148,14 +159,14 @@ def data_array(self, ctx: RuleContext, node: DataArrayNode): if not units_ok: ctx.report( - f"Invalid 'units' attribute: {units!r}.", + f"Invalid {source} 'units': {units!r}.", suggestions=[_units_format_suggestion()], ) def _units_format_suggestion(): use_units_format_msg = ( - f"Specify 'units' attribute using the UDUNITS format," + f"Specify units using the UDUNITS format," f" e.g., {_EXAMPLE_UNIT_1!r} or {_EXAMPLE_UNIT_2!r}." ) return use_units_format_msg diff --git a/xrlint/plugins/core/rules/dataset_title_attr.py b/xrlint/plugins/xcube/rules/dataset_title.py similarity index 56% rename from xrlint/plugins/core/rules/dataset_title_attr.py rename to xrlint/plugins/xcube/rules/dataset_title.py index 4abb5e2..c8281f1 100644 --- a/xrlint/plugins/core/rules/dataset_title_attr.py +++ b/xrlint/plugins/xcube/rules/dataset_title.py @@ -4,13 +4,14 @@ @plugin.define_rule( - "dataset-title-attr", + "dataset-title", version="1.0.0", type="suggestion", description="Datasets should be given a non-empty title.", + docs_url="https://xcube.readthedocs.io/en/latest/cubespec.html#metadata" ) -class DatasetTitleAttr(RuleOp): +class DatasetTitle(RuleOp): def dataset(self, ctx: RuleContext, node: DatasetNode): - title = node.dataset.attrs.get("title") - if not title: - ctx.report("Missing 'title' attribute in dataset.") + attrs = node.dataset.attrs + if "title" not in attrs: + ctx.report("Missing attribute 'title'.") From 24968dc7e9e33acad90f9d051194c43995873f84 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Sat, 1 Feb 2025 12:28:56 +0100 Subject: [PATCH 09/12] back to full coverage --- tests/plugins/core/rules/test_var_units.py | 36 ++++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/tests/plugins/core/rules/test_var_units.py b/tests/plugins/core/rules/test_var_units.py index e5bd9d5..1e12c76 100644 --- a/tests/plugins/core/rules/test_var_units.py +++ b/tests/plugins/core/rules/test_var_units.py @@ -3,32 +3,42 @@ from xrlint.plugins.core.rules.var_units import VarUnits from xrlint.testing import RuleTest, RuleTester -valid_dataset_1 = xr.Dataset() -valid_dataset_2 = xr.Dataset( +valid_dataset_0 = xr.Dataset() +valid_dataset_1 = xr.Dataset( attrs=dict(title="v-data"), - coords={"x": xr.DataArray([0, 0.1, 0.2], dims="x", attrs={"units": "s"})}, - data_vars={"v": xr.DataArray([10, 20, 30], dims="x", attrs={"units": "m/s"})}, + coords={"t": xr.DataArray([0, 1, 2], dims="t", attrs={"units": "seconds"})}, + data_vars={"v": xr.DataArray([10, 20, 30], dims="t", attrs={"units": "m/s"})}, ) +valid_dataset_2 = valid_dataset_1.copy() +valid_dataset_2.t.encoding["units"] = "seconds since 2025-02-01 12:15:00" +del valid_dataset_2.t.attrs["units"] -invalid_dataset_1 = valid_dataset_2.copy() -invalid_dataset_2 = valid_dataset_2.copy() -invalid_dataset_3 = valid_dataset_2.copy() +valid_dataset_3 = valid_dataset_1.copy() +valid_dataset_3.t.attrs["grid_mapping_name"] = "latitude_longitude" + +invalid_dataset_0 = valid_dataset_1.copy() +invalid_dataset_0.t.attrs = {} + +invalid_dataset_1 = valid_dataset_1.copy() +invalid_dataset_1.t.attrs = {"units": 1} + +invalid_dataset_2 = valid_dataset_1.copy() +invalid_dataset_2.t.attrs = {"units": ""} -invalid_dataset_1.x.attrs = {} -invalid_dataset_2.v.attrs = {"units": ""} -invalid_dataset_3.v.attrs = {"units": 1} VarUnitsTest = RuleTester.define_test( "var-units", VarUnits, valid=[ + RuleTest(dataset=valid_dataset_0), RuleTest(dataset=valid_dataset_1), RuleTest(dataset=valid_dataset_2), + RuleTest(dataset=valid_dataset_3), ], invalid=[ - RuleTest(dataset=invalid_dataset_1, expected=1), - RuleTest(dataset=invalid_dataset_2, expected=1), - RuleTest(dataset=invalid_dataset_3, expected=1), + RuleTest(dataset=invalid_dataset_0, expected=["Missing attribute 'units'."]), + RuleTest(dataset=invalid_dataset_1, expected=["Invalid attribute 'units': 1"]), + RuleTest(dataset=invalid_dataset_2, expected=["Empty attribute 'units'."]), ], ) From c2776cd98dba9db2bc61efa2b9a4add87224f9eb Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Sat, 1 Feb 2025 15:14:23 +0100 Subject: [PATCH 10/12] now at 100% coverage --- CHANGES.md | 28 +++++--- tests/cli/configs/recommended.json | 2 +- tests/cli/configs/recommended.py | 2 +- tests/cli/test_main.py | 67 ++++++++++--------- tests/plugins/core/rules/test_var_units.py | 1 - tests/plugins/core/test_plugin.py | 1 - .../plugins/xcube/rules/test_dataset_title.py | 28 ++++++++ .../xcube/rules/test_dataset_title_attr.py | 23 ------- tests/plugins/xcube/test_plugin.py | 1 + tests/test_linter.py | 11 +-- tests/util/test_constructible.py | 2 +- xrlint/_linter/validate.py | 3 - xrlint/cli/engine.py | 27 ++++---- xrlint/cli/main.py | 5 +- xrlint/linter.py | 2 +- xrlint/plugins/core/__init__.py | 1 - xrlint/plugins/core/rules/time_coordinate.py | 2 +- xrlint/plugins/xcube/__init__.py | 1 + xrlint/plugins/xcube/rules/dataset_title.py | 6 +- 19 files changed, 118 insertions(+), 95 deletions(-) create mode 100644 tests/plugins/xcube/rules/test_dataset_title.py delete mode 100644 tests/plugins/xcube/rules/test_dataset_title_attr.py diff --git a/CHANGES.md b/CHANGES.md index 850fa7f..6cf50b4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,29 +1,33 @@ # XRLint Change History -## Version 0.4.2 (from 2025-02-01) +## Version 0.5.0 (in development) + +### Changes - Rule `no-empty-chunks` has taken off the `"recommended"` settings as there is no easy/efficient way to tell whether a dataset has been written using `write_emtpy_chunks` option or not. The rule message itself has been fixed. (#45) -- Adjusted messages of rule `var-units` to better match other rules. +- Adjusted messages of rules `var-units` and `time-coordinate` + to be consistent with messages of other rules. -## Version 0.5.0 (in development) - -### Incompatible API changes: +- Core rule `dataset-title-attr` has been moved into `xcube` plugin + and renamed to `xcube/dataset-title` because the core rule `var-descr` + covers checking for dataset titles. -- Renamed nodes and node properties for consistency and clarity: - - renamed `DataArrayNode` into `VariableNode` - - renamed `DataArrayNode.data_array` into `VariableNode.array` +### Incompatible API changes - Changed general use of term _verify_ into _validate_: - prefixed `RuleOp` methods by `validate_` for clarity. - renamed `XRLint.verify_datasets()` into `validate_files()` - renamed `Lint.verify_dataset()` into `validate()` -- Various changes for improved clarity and consistency - regarding configuration management: +- Renamed nodes and node properties for clarity and consistency: + - renamed `DataArrayNode` into `VariableNode` + - renamed `DataArrayNode.data_array` into `VariableNode.array` + +- Various changes for improved clarity regarding configuration management: - introduced type aliases `ConfigLike` and `ConfigObjectLike`. - renamed `Config` into `ConfigObject` - renamed `ConfigList.configs` into `config_objects` @@ -34,6 +38,10 @@ - added class method `from_config()` to `ConfigList`. - removed function `xrlint.config.merge_configs` as it was no longer used. +### Other changes + +- Added more tests so we finally reached 100% coverage. + ## Version 0.4.1 (from 2025-01-31) ### Changes diff --git a/tests/cli/configs/recommended.json b/tests/cli/configs/recommended.json index 6da59fa..cfcd0af 100644 --- a/tests/cli/configs/recommended.json +++ b/tests/cli/configs/recommended.json @@ -8,7 +8,7 @@ "xcube/recommended", { "rules": { - "dataset-title-attr": "error", + "xcube/dataset-title": "error", "xcube/single-grid-mapping": "off" } } diff --git a/tests/cli/configs/recommended.py b/tests/cli/configs/recommended.py index f998c12..dc5615a 100644 --- a/tests/cli/configs/recommended.py +++ b/tests/cli/configs/recommended.py @@ -14,7 +14,7 @@ def export_config(): *xcube.configs["recommended"], { "rules": { - "dataset-title-attr": "error", + "xcube/dataset-title": "error", "xcube/single-grid-mapping": "off", } }, diff --git a/tests/cli/test_main.py b/tests/cli/test_main.py index 1d9871b..2bd192a 100644 --- a/tests/cli/test_main.py +++ b/tests/cli/test_main.py @@ -24,13 +24,16 @@ class CliMainTest(TestCase): files = ["dataset1.zarr", "dataset1.nc", "dataset2.zarr", "dataset2.nc"] - ok_config_yaml = "- rules:\n dataset-title-attr: error\n" - fail_config_yaml = "- rules:\n no-empty-attrs: error\n" + ok_config_yaml = "- rules:\n var-units: error\n" + fail_config_yaml = "- rules:\n conventions: error\n" + # noinspection SpellCheckingInspection + invalid_config_yaml = "- recommentet\n" datasets = dict( dataset1=xr.Dataset(attrs={"title": "Test 1"}), dataset2=xr.Dataset( - attrs={"title": "Test 2"}, data_vars={"v": xr.DataArray([1, 2, 3])} + attrs={"title": "Test 2"}, + data_vars={"v": xr.DataArray([1, 2, 3], attrs={"units": "m/s"})}, ), ) @@ -80,20 +83,8 @@ def test_files_no_config(self): def test_files_no_config_lookup(self): with text_file(DEFAULT_CONFIG_FILE_YAML, self.ok_config_yaml): - result = self.xrlint("--no-config-lookup", "--no-color", *self.files) - self.assertEqual( - "\n" - "dataset1.zarr:\n" - "dataset error No rules configured or applicable.\n\n" - "dataset1.nc:\n" - "dataset error No rules configured or applicable.\n\n" - "dataset2.zarr:\n" - "dataset error No rules configured or applicable.\n\n" - "dataset2.nc:\n" - "dataset error No rules configured or applicable.\n\n" - "4 errors\n\n", - result.output, - ) + result = self.xrlint("--no-config-lookup", *self.files) + self.assertEqual("Error: no rules configured\n", result.output) self.assertEqual(1, result.exit_code) def test_files_one_rule(self): @@ -112,8 +103,7 @@ def test_files_one_rule(self): with text_file(DEFAULT_CONFIG_FILE_YAML, self.fail_config_yaml): result = self.xrlint(*self.files) - self.assertIn("Missing metadata, attributes are empty.", result.output) - self.assertIn("no-empty-attrs", result.output) + self.assertIn("Missing attribute 'Conventions'.", result.output) self.assertEqual(1, result.exit_code) def test_dir_one_rule(self): @@ -129,8 +119,7 @@ def test_dir_one_rule(self): with text_file(DEFAULT_CONFIG_FILE_YAML, self.fail_config_yaml): result = self.xrlint(*self.files) - self.assertIn("Missing metadata, attributes are empty.", result.output) - self.assertIn("no-empty-attrs", result.output) + self.assertIn("Missing attribute 'Conventions'.", result.output) self.assertEqual(1, result.exit_code) def test_color_no_color(self): @@ -161,10 +150,25 @@ def test_color_no_color(self): ) self.assertEqual(0, result.exit_code) + def test_files_with_invalid_config(self): + with text_file(DEFAULT_CONFIG_FILE_YAML, self.invalid_config_yaml): + result = self.xrlint("--no-color", *self.files) + self.assertEqual( + "Error: xrlint_config.yaml: configuration 'recommentet' not found\n", + result.output, + ) + self.assertEqual(1, result.exit_code) + def test_files_with_rule_option(self): - result = self.xrlint("--rule", "no-empty-attrs: error", *self.files) - self.assertIn("Missing metadata, attributes are empty.", result.output) - self.assertIn("no-empty-attrs", result.output) + result = self.xrlint("--rule", "conventions: error", *self.files) + self.assertIn("Missing attribute 'Conventions'.", result.output) + self.assertEqual(1, result.exit_code) + + def test_files_with_max_warnings(self): + result = self.xrlint( + "--rule", "conventions: warn", "--max-warnings", "0", *self.files + ) + self.assertIn("Maximum number of warnings exceeded.", result.output) self.assertEqual(1, result.exit_code) def test_files_with_plugin_and_rule_options(self): @@ -213,7 +217,7 @@ def test_print_config_option(self): ' "__core__": "xrlint.plugins.core:export_plugin"\n' " },\n" ' "rules": {\n' - ' "dataset-title-attr": 2\n' + ' "var-units": 2\n' " }\n" "}\n" ), @@ -222,11 +226,13 @@ def test_print_config_option(self): self.assertEqual(0, result.exit_code) def test_files_with_invalid_format_option(self): - result = self.xrlint("-f", "foo", *self.files) - self.assertIn( - "Error: unknown format 'foo'. The available formats are '", result.output - ) - self.assertEqual(1, result.exit_code) + with text_file(DEFAULT_CONFIG_FILE_YAML, self.ok_config_yaml): + result = self.xrlint("-f", "foo", *self.files) + self.assertIn( + "Error: unknown format 'foo'. The available formats are '", + result.output, + ) + self.assertEqual(1, result.exit_code) def test_init(self): config_file = DEFAULT_CONFIG_FILE_YAML @@ -256,6 +262,7 @@ def test_init_exists(self): self.assertEqual(result.exit_code, 1) +# noinspection PyTypeChecker class CliMainMetaTest(TestCase): def test_help(self): runner = CliRunner() diff --git a/tests/plugins/core/rules/test_var_units.py b/tests/plugins/core/rules/test_var_units.py index 1e12c76..9323ba8 100644 --- a/tests/plugins/core/rules/test_var_units.py +++ b/tests/plugins/core/rules/test_var_units.py @@ -26,7 +26,6 @@ invalid_dataset_2.t.attrs = {"units": ""} - VarUnitsTest = RuleTester.define_test( "var-units", VarUnits, diff --git a/tests/plugins/core/test_plugin.py b/tests/plugins/core/test_plugin.py index c6d69b6..e930cf4 100644 --- a/tests/plugins/core/test_plugin.py +++ b/tests/plugins/core/test_plugin.py @@ -11,7 +11,6 @@ def test_rules_complete(self): "content-desc", "conventions", "coords-for-dims", - "dataset-title-attr", "grid-mappings", "lat-coordinate", "lon-coordinate", diff --git a/tests/plugins/xcube/rules/test_dataset_title.py b/tests/plugins/xcube/rules/test_dataset_title.py new file mode 100644 index 0000000..3f2f68d --- /dev/null +++ b/tests/plugins/xcube/rules/test_dataset_title.py @@ -0,0 +1,28 @@ +# Copyright © 2025 Brockmann Consult GmbH. +# This software is distributed under the terms and conditions of the +# MIT license (https://mit-license.org/). + +import xarray as xr + +from xrlint.plugins.xcube.rules.dataset_title import DatasetTitle +from xrlint.testing import RuleTest, RuleTester + +valid_dataset_0 = xr.Dataset(attrs=dict(title="OC-Climatology")) +valid_dataset_1 = xr.Dataset(attrs=dict(title="SST-Climatology")) + +invalid_dataset_0 = xr.Dataset() +invalid_dataset_1 = xr.Dataset(attrs=dict(title="")) + + +DatasetTitleTest = RuleTester.define_test( + "dataset-title", + DatasetTitle, + valid=[ + RuleTest(dataset=valid_dataset_0), + RuleTest(dataset=valid_dataset_1), + ], + invalid=[ + RuleTest(dataset=invalid_dataset_0, expected=1), + RuleTest(dataset=invalid_dataset_1, expected=1), + ], +) diff --git a/tests/plugins/xcube/rules/test_dataset_title_attr.py b/tests/plugins/xcube/rules/test_dataset_title_attr.py deleted file mode 100644 index d11987b..0000000 --- a/tests/plugins/xcube/rules/test_dataset_title_attr.py +++ /dev/null @@ -1,23 +0,0 @@ -import xarray as xr - -from xrlint.plugins.xcube.rules.dataset_title import DatasetTitle -from xrlint.testing import RuleTest, RuleTester - -valid_dataset_1 = xr.Dataset(attrs=dict(title="OC-Climatology")) -valid_dataset_2 = xr.Dataset(attrs=dict(title="SST-Climatology")) -invalid_dataset_1 = xr.Dataset() -invalid_dataset_2 = xr.Dataset(attrs=dict(title="")) - - -DatasetTitleTest = RuleTester.define_test( - "dataset-title", - DatasetTitle, - valid=[ - RuleTest(dataset=valid_dataset_1), - RuleTest(dataset=valid_dataset_2), - ], - invalid=[ - RuleTest(dataset=invalid_dataset_1, expected=1), - RuleTest(dataset=invalid_dataset_2, expected=1), - ], -) diff --git a/tests/plugins/xcube/test_plugin.py b/tests/plugins/xcube/test_plugin.py index 12a8e42..0c33a7b 100644 --- a/tests/plugins/xcube/test_plugin.py +++ b/tests/plugins/xcube/test_plugin.py @@ -11,6 +11,7 @@ def test_rules_complete(self): "any-spatial-data-var", "cube-dims-order", "data-var-colors", + "dataset-title", "grid-mapping-naming", "increasing-time", "lat-lon-naming", diff --git a/tests/test_linter.py b/tests/test_linter.py index f95f8b2..c08b717 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -75,18 +75,21 @@ def test_config_with_config_obj(self): ) self.assert_result_ok(result, "Missing metadata, attributes are empty.") + def test_config_with_file_not_found(self): + linter = new_linter({"rules": {"no-empty-attrs": 2}}) + result = linter.validate("cube.zarr") + self.assert_result_ok(result, "No such file or directory:") + def test_no_config(self): linter = Linter() - result = linter.validate( - xr.Dataset(), - ) + result = linter.validate(xr.Dataset()) self.assert_result_ok(result, "No configuration given or matches ''.") def assert_result_ok(self, result: Result, expected_message: str): self.assertIsInstance(result, Result) self.assertEqual(1, len(result.messages)) self.assertEqual(2, result.messages[0].severity) - self.assertEqual(expected_message, result.messages[0].message) + self.assertIn(expected_message, result.messages[0].message) class LinterValidateTest(TestCase): diff --git a/tests/util/test_constructible.py b/tests/util/test_constructible.py index f5dac61..daa5648 100644 --- a/tests/util/test_constructible.py +++ b/tests/util/test_constructible.py @@ -36,7 +36,7 @@ class RequiredPropsContainer(MappingConstructible): class NoTypesContainer(MappingConstructible): - def __init__(self, u, v, w): + def __init__(self, u, v, w, /): # positional only! self.u = u self.v = v self.w = w diff --git a/xrlint/_linter/validate.py b/xrlint/_linter/validate.py index dcf254a..352ab43 100644 --- a/xrlint/_linter/validate.py +++ b/xrlint/_linter/validate.py @@ -31,9 +31,6 @@ def _validate_dataset( assert isinstance(dataset, xr.Dataset) assert isinstance(file_path, str) - if not config_obj.rules: - return [new_fatal_message("No rules configured or applicable.")] - context = RuleContextImpl(config_obj, dataset, file_path, file_index) for rule_id, rule_config in config_obj.rules.items(): with context.use_state(rule_id=rule_id): diff --git a/xrlint/cli/engine.py b/xrlint/cli/engine.py index 80b7d96..942a04f 100644 --- a/xrlint/cli/engine.py +++ b/xrlint/cli/engine.py @@ -70,7 +70,7 @@ def result_stats(self) -> ResultStats: """Get current result statistics.""" return self._result_stats - def init_config(self, *configs: ConfigLike) -> None: + def init_config(self, *extra_configs: ConfigLike) -> None: """Initialize configuration. The function will load the configuration list from a specified configuration file, if any. @@ -78,9 +78,11 @@ def init_config(self, *configs: ConfigLike) -> None: in the current working directory. Args: - *configs: Variable number of configuration-like arguments. + *extra_configs: Variable number of configuration-like arguments. For more information see the [ConfigLike][xrlint.config.ConfigLike] type alias. + If provided, `extra_configs` will be appended to configuration + read from configration files and passed as command line options. """ plugins = {} for plugin_spec in self.plugin_specs: @@ -92,36 +94,37 @@ def init_config(self, *configs: ConfigLike) -> None: rule = yaml.load(rule_spec, Loader=yaml.SafeLoader) rules.update(rule) - config = None + file_config = None if self.config_path: try: - config = read_config(self.config_path) + file_config = read_config(self.config_path) except (FileNotFoundError, ConfigError) as e: raise click.ClickException(f"{e}") from e elif not self.no_config_lookup: for config_path in DEFAULT_CONFIG_FILES: try: - config = read_config(config_path) + file_config = read_config(config_path) break except FileNotFoundError: pass except ConfigError as e: raise click.ClickException(f"{e}") from e - if config is None: + if file_config is None: click.echo("Warning: no configuration file found.") core_config_obj = get_core_config_object() core_config_obj.plugins.update(plugins) - base_configs = [core_config_obj] - if config is not None: - base_configs += config.objects + + base_configs = [] + if file_config is not None: + base_configs += file_config.objects if rules: base_configs += [{"rules": rules}] - self.config = Config.from_config(*base_configs, *configs) - if not self.config.objects: - raise click.ClickException("no configuration provided") + self.config = Config.from_config(core_config_obj, *base_configs, *extra_configs) + if not any(co.rules for co in self.config.objects): + raise click.ClickException("no rules configured") def compute_config_for_file(self, file_path: str) -> ConfigObject | None: """Compute the configuration object for the given file. diff --git a/xrlint/cli/main.py b/xrlint/cli/main.py index e8d3f7c..702d22a 100644 --- a/xrlint/cli/main.py +++ b/xrlint/cli/main.py @@ -156,9 +156,8 @@ def main( report = cli_engine.format_results(results) cli_engine.write_report(report) - result_stats = cli_engine.result_stats - error_status = result_stats.error_count > 0 - max_warn_status = result_stats.warning_count > max_warnings + error_status = cli_engine.result_stats.error_count > 0 + max_warn_status = cli_engine.max_warnings_exceeded if max_warn_status and not error_status: click.echo("Maximum number of warnings exceeded.") if max_warn_status or error_status: diff --git a/xrlint/linter.py b/xrlint/linter.py index f7757ce..b265e31 100644 --- a/xrlint/linter.py +++ b/xrlint/linter.py @@ -89,7 +89,7 @@ def validate( config = Config.from_config(self._config, config, config_props) config_obj = config.compute_config_object(file_path) - if config_obj is None: + if config_obj is None or not config_obj.rules: return Result.new( config_object=None, file_path=file_path, diff --git a/xrlint/plugins/core/__init__.py b/xrlint/plugins/core/__init__.py index f931321..9cd55dc 100644 --- a/xrlint/plugins/core/__init__.py +++ b/xrlint/plugins/core/__init__.py @@ -15,7 +15,6 @@ def export_plugin() -> Plugin: "content-desc": "warn", "conventions": "warn", "coords-for-dims": "error", - "dataset-title-attr": "warn", "grid-mappings": "error", "lat-coordinate": "error", "lon-coordinate": "error", diff --git a/xrlint/plugins/core/rules/time_coordinate.py b/xrlint/plugins/core/rules/time_coordinate.py index c7e6d3a..e48ab6b 100644 --- a/xrlint/plugins/core/rules/time_coordinate.py +++ b/xrlint/plugins/core/rules/time_coordinate.py @@ -99,7 +99,7 @@ def validate_variable(self, ctx: RuleContext, node: VariableNode): calendar = attrs["calendar"] # Note, we should also check here for valid calendar names if calendar is None: - ctx.report(f"Missing {source} 'calendar'." ) + ctx.report(f"Missing {source} 'calendar'.") uot_part = units_parts[0] date_part = units_parts[2] diff --git a/xrlint/plugins/xcube/__init__.py b/xrlint/plugins/xcube/__init__.py index 10c974e..899e544 100644 --- a/xrlint/plugins/xcube/__init__.py +++ b/xrlint/plugins/xcube/__init__.py @@ -35,6 +35,7 @@ def export_plugin() -> Plugin: "xcube/any-spatial-data-var": "error", "xcube/cube-dims-order": "error", "xcube/data-var-colors": "warn", + "xcube/dataset-title": "error", "xcube/grid-mapping-naming": "warn", "xcube/increasing-time": "error", "xcube/lat-lon-naming": "error", diff --git a/xrlint/plugins/xcube/rules/dataset_title.py b/xrlint/plugins/xcube/rules/dataset_title.py index ce2b0cd..338e1f7 100644 --- a/xrlint/plugins/xcube/rules/dataset_title.py +++ b/xrlint/plugins/xcube/rules/dataset_title.py @@ -1,5 +1,5 @@ from xrlint.node import DatasetNode -from xrlint.plugins.core.plugin import plugin +from xrlint.plugins.xcube.plugin import plugin from xrlint.rule import RuleContext, RuleOp @@ -8,10 +8,12 @@ version="1.0.0", type="suggestion", description="Datasets should be given a non-empty title.", - docs_url="https://xcube.readthedocs.io/en/latest/cubespec.html#metadata" + docs_url="https://xcube.readthedocs.io/en/latest/cubespec.html#metadata", ) class DatasetTitle(RuleOp): def validate_dataset(self, ctx: RuleContext, node: DatasetNode): attrs = node.dataset.attrs if "title" not in attrs: ctx.report("Missing attribute 'title'.") + elif not attrs["title"]: + ctx.report(f"Invalid attribute 'title': {attrs['title']!r}") From fb63954e1abcd88319c1a3c898e2b8236d6d8602 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Sat, 1 Feb 2025 15:14:31 +0100 Subject: [PATCH 11/12] now at 100% coverage --- docs/todo.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/todo.md b/docs/todo.md index 38eeca3..9e5da37 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -24,7 +24,6 @@ ## Nice to have -- add some more tests so we reach 100% coverage - support `autofix` feature - support `md` (markdown) output format - support formatter op args/kwargs and apply validation schema From 810dfa508bd321fa65778f03ad52b93c65636fdf Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Sat, 1 Feb 2025 15:18:53 +0100 Subject: [PATCH 12/12] upd doc --- docs/rule-ref.md | 13 +++++++------ xrlint/plugins/xcube/rules/dataset_title.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/rule-ref.md b/docs/rule-ref.md index 3301e05..1211cb0 100644 --- a/docs/rule-ref.md +++ b/docs/rule-ref.md @@ -32,12 +32,6 @@ Dimensions of data variables should have corresponding coordinates. Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning-bolt: -### :material-lightbulb: `dataset-title-attr` - -Datasets should be given a non-empty title. - -Contained in: `all`-:material-lightning-bolt: `recommended`-:material-alert: - ### :material-bug: `grid-mappings` Grid mappings, if any, shall have valid grid mapping coordinate variables. @@ -122,6 +116,13 @@ Spatial data variables should encode xcube color mappings in their metadata. Contained in: `all`-:material-lightning-bolt: `recommended`-:material-alert: +### :material-bug: `dataset-title` + +Datasets should be given a non-empty title. +[:material-information-variant:](https://xcube.readthedocs.io/en/latest/cubespec.html#metadata) + +Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning-bolt: + ### :material-lightbulb: `grid-mapping-naming` Grid mapping variables should be called 'spatial_ref' or 'crs' for compatibility with rioxarray and other packages. diff --git a/xrlint/plugins/xcube/rules/dataset_title.py b/xrlint/plugins/xcube/rules/dataset_title.py index 338e1f7..4fad707 100644 --- a/xrlint/plugins/xcube/rules/dataset_title.py +++ b/xrlint/plugins/xcube/rules/dataset_title.py @@ -6,7 +6,7 @@ @plugin.define_rule( "dataset-title", version="1.0.0", - type="suggestion", + type="problem", description="Datasets should be given a non-empty title.", docs_url="https://xcube.readthedocs.io/en/latest/cubespec.html#metadata", )