Skip to content

Commit f1c35fc

Browse files
authored
Implement .cf.add_canonical_attributes (#232)
1 parent a898641 commit f1c35fc

File tree

10 files changed

+33719
-33
lines changed

10 files changed

+33719
-33
lines changed

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
graft cf_xarray/data

cf_xarray/__init__.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
from pkg_resources import DistributionNotFound, get_distribution
2-
31
from .accessor import CFAccessor # noqa
42
from .helpers import bounds_to_vertices, vertices_to_bounds # noqa
3+
from .utils import _get_version
54

6-
try:
7-
__version__ = get_distribution("cf_xarray").version
8-
except DistributionNotFound:
9-
# package is not installed
10-
__version__ = "unknown"
5+
__version__ = _get_version()

cf_xarray/accessor.py

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import re
55
import warnings
66
from collections import ChainMap
7+
from datetime import datetime
78
from typing import (
89
Any,
910
Callable,
@@ -27,10 +28,12 @@
2728
from .criteria import coordinate_criteria, regex
2829
from .helpers import bounds_to_vertices
2930
from .utils import (
31+
_get_version,
3032
_is_datetime_like,
3133
always_iterable,
3234
invert_mappings,
3335
parse_cell_methods_attr,
36+
parse_cf_standard_name_table,
3437
)
3538

3639
#: Classes wrapped by cf_xarray.
@@ -62,7 +65,6 @@
6265
ATTRS["time"] = ATTRS["T"]
6366
ATTRS["vertical"] = ATTRS["Z"]
6467

65-
6668
# Type for Mapper functions
6769
Mapper = Callable[[Union[DataArray, Dataset], str], List[str]]
6870

@@ -1521,6 +1523,106 @@ def differentiate(
15211523
result *= -1
15221524
return result
15231525

1526+
def add_canonical_attributes(
1527+
self,
1528+
override: bool = False,
1529+
skip: Union[str, List[str]] = None,
1530+
verbose: bool = False,
1531+
source=None,
1532+
) -> Union[Dataset, DataArray]:
1533+
"""
1534+
Add canonical CF attributes to variables with standard names.
1535+
Attributes are parsed from the official CF standard name table.
1536+
This function adds an entry to the "history" attribute.
1537+
1538+
Parameters
1539+
----------
1540+
override: bool
1541+
Override existing attributes.
1542+
skip: str, iterable, optional
1543+
Attribute(s) to skip: ``{"units", "grib", "amip", "description"}``.
1544+
verbose: bool
1545+
Print added attributes to screen.
1546+
source: optional
1547+
Path of `cf-standard-name-table.xml` or file object containing XML data.
1548+
If ``None``, use the default version associated with ``cf-xarray``.
1549+
1550+
Returns
1551+
-------
1552+
DataArray or Dataset with attributes added.
1553+
1554+
Notes
1555+
-----
1556+
The ``"units"`` attribute is never added to datetime-like variables.
1557+
"""
1558+
1559+
# Arguments to add to history
1560+
args = ", ".join([f"{k!s}={v!r}" for k, v in locals().items() if k != "self"])
1561+
1562+
# Defaults
1563+
skip = [skip] if isinstance(skip, str) else (skip or [])
1564+
1565+
# Parse table
1566+
info, table, aliases = parse_cf_standard_name_table(source)
1567+
1568+
# Loop over standard names
1569+
ds = self._maybe_to_dataset().copy()
1570+
attrs_to_print: dict = {}
1571+
for std_name, var_names in ds.cf.standard_names.items():
1572+
1573+
# Loop over variable names
1574+
for var_name in var_names:
1575+
old_attrs = ds[var_name].attrs
1576+
std_name = aliases.get(std_name, std_name)
1577+
new_attrs = table.get(std_name, {})
1578+
1579+
# Loop over attributes
1580+
for key, value in new_attrs.items():
1581+
if value and key not in skip and (override or key not in old_attrs):
1582+
1583+
# Don't add units to time variables (e.g., datetime64, ...)
1584+
if key == "units" and _is_datetime_like(ds[var_name]):
1585+
continue
1586+
1587+
# Add attribute
1588+
ds[var_name].attrs[key] = value
1589+
1590+
# Build verbose dictionary
1591+
if verbose:
1592+
attrs_to_print.setdefault(var_name, {})
1593+
attrs_to_print[var_name][key] = value
1594+
1595+
if verbose:
1596+
# Info
1597+
strings = ["CF Standard Name Table info:"]
1598+
for key, value in info.items():
1599+
strings.append(f"- {key}: {value}")
1600+
1601+
# Attributes added
1602+
strings.append("\nAttributes added:")
1603+
for varname, attrs in attrs_to_print.items():
1604+
strings.append(f"- {varname}:")
1605+
for key, value in attrs.items():
1606+
strings.append(f" * {key}: {value}")
1607+
strings.append("")
1608+
1609+
print("\n".join(strings))
1610+
1611+
# Prepend history
1612+
now = datetime.now().ctime()
1613+
method_name = inspect.stack()[0][3]
1614+
version = _get_version()
1615+
table_version = info["version_number"]
1616+
history = (
1617+
f"{now}:"
1618+
f" cf.{method_name}({args})"
1619+
f" [cf-xarray {version}, cf-standard-name-table {table_version}]\n"
1620+
)
1621+
obj = self._maybe_to_dataarray(ds)
1622+
obj.attrs["history"] = history + obj.attrs.get("history", "")
1623+
1624+
return obj
1625+
15241626

15251627
@xr.register_dataset_accessor("cf")
15261628
class CFDatasetAccessor(CFAccessor):

0 commit comments

Comments
 (0)