Source code for xclim.core.cfchecks
"""
CF-Convention Checking
======================
Utilities designed to verify the compliance of metadata with the CF-Convention.
"""
from __future__ import annotations
import fnmatch
import re
from collections.abc import Sequence
import xarray as xr
from xclim.core._exceptions import ValidationError
from xclim.core._types import VARIABLES
from xclim.core.options import cfcheck
[docs]
@cfcheck
def check_valid(var: xr.DataArray, key: str, expected: str | Sequence[str]):
r"""
Check that a variable's attribute has one of the expected values and raise a ValidationError if otherwise.
Parameters
----------
var : xr.DataArray
The variable to check.
key : str
The attribute to check.
expected : str or sequence of str
The expected value(s).
Raises
------
ValidationError
If the attribute is not present or does not match the expected value(s).
"""
att = getattr(var, key, None)
if att is None:
raise ValidationError(f"Variable does not have a `{key}` attribute.")
if isinstance(expected, str):
expected = [expected]
for exp in expected:
if fnmatch.fnmatch(att, exp):
break
else:
raise ValidationError(
f"Variable has a non-conforming {key}: Got `{att}`, expected `{expected}`",
)
[docs]
def cfcheck_from_name(varname: str, vardata: xr.DataArray, attrs: list[str] | None = None):
"""
Perform cfchecks on a DataArray using specifications from xclim's default variables.
Parameters
----------
varname : str
The name of the variable to check.
vardata : xr.DataArray
The variable to check.
attrs : list of str, optional
Attributes to check. Default is ["cell_methods", "standard_name"].
Raises
------
ValidationError
If the variable does not meet the expected CF-Convention.
"""
if attrs is None:
attrs = ["cell_methods", "standard_name"]
data = VARIABLES[varname]
if "cell_methods" in data and "cell_methods" in attrs:
_check_cell_methods(getattr(vardata, "cell_methods", None), data["cell_methods"])
if "standard_name" in data and "standard_name" in attrs:
check_valid(vardata, "standard_name", data["standard_name"])
[docs]
@cfcheck
def _check_cell_methods(data_cell_methods: str, expected_method: str) -> None:
if data_cell_methods is None:
raise ValidationError("Variable does not have a `cell_methods` attribute.")
EXTRACT_CELL_METHOD_REGEX = r"(\s*\S+\s*:(\s+[\w()-]+)+)(?!\S*:)"
for m in re.compile(EXTRACT_CELL_METHOD_REGEX).findall(data_cell_methods):
if expected_method in m[0]:
return None
raise ValidationError(
f"Variable has a non-conforming cell_methods: "
f"Got `{data_cell_methods}`, which do not include the expected "
f"`{expected_method}`."
)