|
4 | 4 | import re
|
5 | 5 | import warnings
|
6 | 6 | from collections import ChainMap
|
| 7 | +from datetime import datetime |
7 | 8 | from typing import (
|
8 | 9 | Any,
|
9 | 10 | Callable,
|
|
27 | 28 | from .criteria import coordinate_criteria, regex
|
28 | 29 | from .helpers import bounds_to_vertices
|
29 | 30 | from .utils import (
|
| 31 | + _get_version, |
30 | 32 | _is_datetime_like,
|
31 | 33 | always_iterable,
|
32 | 34 | invert_mappings,
|
33 | 35 | parse_cell_methods_attr,
|
| 36 | + parse_cf_standard_name_table, |
34 | 37 | )
|
35 | 38 |
|
36 | 39 | #: Classes wrapped by cf_xarray.
|
|
62 | 65 | ATTRS["time"] = ATTRS["T"]
|
63 | 66 | ATTRS["vertical"] = ATTRS["Z"]
|
64 | 67 |
|
65 |
| - |
66 | 68 | # Type for Mapper functions
|
67 | 69 | Mapper = Callable[[Union[DataArray, Dataset], str], List[str]]
|
68 | 70 |
|
@@ -1521,6 +1523,106 @@ def differentiate(
|
1521 | 1523 | result *= -1
|
1522 | 1524 | return result
|
1523 | 1525 |
|
| 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 | + |
1524 | 1626 |
|
1525 | 1627 | @xr.register_dataset_accessor("cf")
|
1526 | 1628 | class CFDatasetAccessor(CFAccessor):
|
|
0 commit comments