Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5dc661c
Implement split cube attributes.
pp-mo Oct 31, 2022
9bbaf55
Test fixes.
pp-mo Oct 31, 2022
c72c238
Modify examples for simpler metadata printouts.
pp-mo Nov 1, 2022
839f559
Added tests, small behaviour fixes.
pp-mo Nov 3, 2022
21a8dcf
Simplify copy.
pp-mo Nov 3, 2022
e7681c6
Fix doctests.
pp-mo Nov 3, 2022
cc8e880
Skip doctests with non-replicable outputs (from use of sets).
pp-mo Nov 3, 2022
7db42ce
Tidy test comments, and add extra test.
pp-mo Nov 4, 2022
7456558
Tiny typo.
pp-mo Jul 17, 2023
2687e4f
Remove redundant redefinition of Cube.attributes.
pp-mo Jul 17, 2023
b055cbe
Add CubeAttrsDict in module __all__ + improve docs coverage.
pp-mo Jul 18, 2023
6e07c8a
Review changes - small test changes.
pp-mo Jul 18, 2023
53b05d3
More review changes.
pp-mo Jul 18, 2023
7454e22
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 18, 2023
ade712e
Fix CubeAttrsDict example docstrings.
pp-mo Jul 19, 2023
6ebce70
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 19, 2023
3dc857a
Merge branch 'FEATURE_split_attrs' into splitattrs_cubeattrs
pp-mo Jul 19, 2023
f143de0
Odd small fixes.
pp-mo Jul 19, 2023
e572967
Improved docstrings and comments; fix doctests.
pp-mo Jul 19, 2023
0a5c7e5
Don't sidestep netcdf4 thread-safety.
pp-mo Jul 19, 2023
16594a2
Publicise LimitedAttributeDict, so CubeAttrsDict can refer to it.
pp-mo Jul 19, 2023
8b070d6
Fix various internal + external links.
pp-mo Jul 19, 2023
56c5071
Merge branch 'FEATURE_split_attrs' into splitattrs_cubeattrs
pp-mo Jul 20, 2023
bab365e
Update lib/iris/cube.py
pp-mo Jul 20, 2023
4f22eba
Update lib/iris/cube.py
pp-mo Jul 20, 2023
0ab17f4
Update lib/iris/cube.py
pp-mo Jul 20, 2023
fc109e9
Update lib/iris/cube.py
pp-mo Jul 20, 2023
96792e3
Streamline docs.
pp-mo Jul 20, 2023
9efe4f6
Review changes.
pp-mo Jul 20, 2023
05c4e67
Distinguish local+global attributes in netcdf loads.
pp-mo Nov 4, 2022
2c60b02
Small test fixes.
pp-mo Nov 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions docs/src/further_topics/metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ We can easily get all of the associated metadata of the :class:`~iris.cube.Cube`
using the ``metadata`` property:

>>> cube.metadata
CubeMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'Conventions': 'CF-1.5', 'STASH': STASH(model=1, section=3, item=236), 'Model scenario': 'A1B', 'source': 'Data from Met Office Unified Model 6.05'}, cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),))
CubeMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes=CubeAttrsDict(globals={}, locals={'Conventions': 'CF-1.5', 'STASH': STASH(model=1, section=3, item=236), 'Model scenario': 'A1B', 'source': 'Data from Met Office Unified Model 6.05'}), cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),))

We can also inspect the ``metadata`` of the ``longitude``
:class:`~iris.coords.DimCoord` attached to the :class:`~iris.cube.Cube` in the same way:
Expand Down Expand Up @@ -675,8 +675,8 @@ For example, consider the following :class:`~iris.common.metadata.CubeMetadata`,

.. doctest:: metadata-combine

>>> cube.metadata # doctest: +SKIP
CubeMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'Conventions': 'CF-1.5', 'STASH': STASH(model=1, section=3, item=236), 'Model scenario': 'A1B', 'source': 'Data from Met Office Unified Model 6.05'}, cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),))
>>> cube.metadata
CubeMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes=CubeAttrsDict(globals={}, locals={'Conventions': 'CF-1.5', 'STASH': STASH(model=1, section=3, item=236), 'Model scenario': 'A1B', 'source': 'Data from Met Office Unified Model 6.05'}), cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),))

We can perform the **identity function** by comparing the metadata with itself,

Expand All @@ -701,7 +701,7 @@ which is replaced with a **different value**,
>>> metadata != cube.metadata
True
>>> metadata.combine(cube.metadata) # doctest: +SKIP
CubeMetadata(standard_name=None, long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'STASH': STASH(model=1, section=3, item=236), 'source': 'Data from Met Office Unified Model 6.05', 'Model scenario': 'A1B', 'Conventions': 'CF-1.5'}, cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),))
CubeMetadata(standard_name=None, long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'Conventions': 'CF-1.5', 'Model scenario': 'A1B', 'STASH': STASH(model=1, section=3, item=236), 'source': 'Data from Met Office Unified Model 6.05'}, cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),))

The ``combine`` method combines metadata by performing a **strict** comparison
between each of the associated metadata member values,
Expand Down Expand Up @@ -810,16 +810,17 @@ the ``from_metadata`` class method. For example, given the following

.. doctest:: metadata-convert

>>> cube.metadata # doctest: +SKIP
CubeMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'Conventions': 'CF-1.5', 'STASH': STASH(model=1, section=3, item=236), 'Model scenario': 'A1B', 'source': 'Data from Met Office Unified Model 6.05'}, cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),))
>>> cube.metadata
CubeMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes=CubeAttrsDict(globals={}, locals={'Conventions': 'CF-1.5', 'STASH': STASH(model=1, section=3, item=236), 'Model scenario': 'A1B', 'source': 'Data from Met Office Unified Model 6.05'}), cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),))

We can easily convert it to a :class:`~iris.common.metadata.DimCoordMetadata` instance
using ``from_metadata``,

.. doctest:: metadata-convert

>>> DimCoordMetadata.from_metadata(cube.metadata) # doctest: +SKIP
DimCoordMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'Conventions': 'CF-1.5', 'STASH': STASH(model=1, section=3, item=236), 'Model scenario': 'A1B', 'source': 'Data from Met Office Unified Model 6.05'}, coord_system=None, climatological=None, circular=None)
>>> newmeta = DimCoordMetadata.from_metadata(cube.metadata)
>>> print(newmeta)
DimCoordMetadata(standard_name=air_temperature, var_name=air_temperature, units=K, attributes={'Conventions': 'CF-1.5', 'STASH': STASH(model=1, section=3, item=236), 'Model scenario': 'A1B', 'source': 'Data from Met Office Unified Model 6.05'})

By examining :numref:`metadata members table`, we can see that the
:class:`~iris.cube.Cube` and :class:`~iris.coords.DimCoord` container
Expand Down Expand Up @@ -849,9 +850,9 @@ class instance,

.. doctest:: metadata-convert

>>> longitude.metadata.from_metadata(cube.metadata)
DimCoordMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'Conventions': 'CF-1.5', 'STASH': STASH(model=1, section=3, item=236), 'Model scenario': 'A1B', 'source': 'Data from Met Office Unified Model 6.05'}, coord_system=None, climatological=None, circular=None)

>>> newmeta = longitude.metadata.from_metadata(cube.metadata)
>>> print(newmeta)
DimCoordMetadata(standard_name=air_temperature, var_name=air_temperature, units=K, attributes={'Conventions': 'CF-1.5', 'STASH': STASH(model=1, section=3, item=236), 'Model scenario': 'A1B', 'source': 'Data from Met Office Unified Model 6.05'})

.. _metadata assignment:

Expand Down Expand Up @@ -978,7 +979,7 @@ Indeed, it's also possible to assign to the ``metadata`` property with a
>>> longitude.metadata
DimCoordMetadata(standard_name='longitude', long_name=None, var_name='longitude', units=Unit('degrees'), attributes={}, coord_system=GeogCS(6371229.0), climatological=False, circular=False)
>>> longitude.metadata = cube.metadata
>>> longitude.metadata # doctest: +SKIP
>>> longitude.metadata
DimCoordMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'Conventions': 'CF-1.5', 'STASH': STASH(model=1, section=3, item=236), 'Model scenario': 'A1B', 'source': 'Data from Met Office Unified Model 6.05'}, coord_system=GeogCS(6371229.0), climatological=False, circular=False)

Note that, only **common** metadata members will be assigned new associated
Expand Down
6 changes: 5 additions & 1 deletion lib/iris/common/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,11 @@ def __str__(self):
field_strings = []
for field in self._fields:
value = getattr(self, field)
if value is None or isinstance(value, (str, dict)) and not value:
if (
value is None
or isinstance(value, (str, Mapping))
and not value
):
continue
field_strings.append(f"{field}={value}")

Expand Down
33 changes: 28 additions & 5 deletions lib/iris/common/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from .metadata import BaseMetadata

__all__ = ["CFVariableMixin"]
__all__ = ["CFVariableMixin", "LimitedAttributeDict"]


def _get_valid_standard_name(name):
Expand Down Expand Up @@ -53,7 +53,29 @@ def _get_valid_standard_name(name):


class LimitedAttributeDict(dict):
_forbidden_keys = (
"""
A specialised 'dict' subclass, which forbids (errors) certain attribute names.

Used for the attribute dictionaries of all Iris data objects (that is,
:class:`CFVariableMixin` and its subclasses).

The "excluded" attributes are those which either :mod:`netCDF4` or Iris intpret and
control with special meaning, which therefore should *not* be defined as custom
'user' attributes on Iris data objects such as cubes.

For example : "coordinates", "grid_mapping", "scale_factor".

The 'forbidden' attributes are those listed in
:data:`iris.common.mixin.LimitedAttributeDict.CF_ATTRS_FORBIDDEN` .

All the forbidden attributes are amongst those listed in
`Appendix A of the CF Conventions: <http://cfconventions.org/Data/cf-conventions/cf-conventions-1.10/cf-conventions.html#attribute-appendix>`_
-- however, not *all* of them, since not all are interpreted by Iris.

"""

#: Attributes with special CF meaning, forbidden in Iris attribute dictionaries.
CF_ATTRS_FORBIDDEN = (
"standard_name",
"long_name",
"units",
Expand All @@ -78,7 +100,7 @@ def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
# Check validity of keys
for key in self.keys():
if key in self._forbidden_keys:
if key in self.CF_ATTRS_FORBIDDEN:
raise ValueError(f"{key!r} is not a permitted attribute")

def __eq__(self, other):
Expand All @@ -99,11 +121,12 @@ def __ne__(self, other):
return not self == other

def __setitem__(self, key, value):
if key in self._forbidden_keys:
if key in self.CF_ATTRS_FORBIDDEN:
raise ValueError(f"{key!r} is not a permitted attribute")
dict.__setitem__(self, key, value)

def update(self, other, **kwargs):
"""Standard ``dict.update()`` operation."""
# Gather incoming keys
keys = []
if hasattr(other, "keys"):
Expand All @@ -115,7 +138,7 @@ def update(self, other, **kwargs):

# Check validity of keys
for key in keys:
if key in self._forbidden_keys:
if key in self.CF_ATTRS_FORBIDDEN:
raise ValueError(f"{key!r} is not a permitted attribute")

dict.update(self, other, **kwargs)
Expand Down
Loading