Source code for iris.fileformats.cf._group

# Copyright Iris contributors
#
# This file is part of Iris and is released under the BSD license.
# See LICENSE in the root of the repository for full licensing details.
"""Collect classified CF-netCDF variables into a single addressable group.

:class:`CFGroup` is the output of classification and the input to rule-based
loading. It is a :class:`~collections.abc.MutableMapping` from CF-netCDF
variable name to the :class:`~iris.fileformats.cf.CFVariable` instance that
:mod:`iris.fileformats.cf._variables` classified it as, so a group holds every
variable in a file exactly once, whatever its type.

On top of that mapping sit one read-only property per variable type --
:attr:`CFGroup.bounds`, :attr:`CFGroup.grid_mappings` and so on -- each of
which filters the mapping by ``isinstance``.

.. maintainer-notes::

   Those properties are computed on every access rather than cached, so a
   group stays correct when :class:`~iris.fileformats.cf.CFReader` mutates it
   during its second pass. A variable therefore appears in every property
   whose type it satisfies, and :attr:`CFGroup.formula_terms` is the one
   exception to the ``isinstance`` rule: it filters on ``has_formula_terms()``
   instead, because participating in a formula term is a property of the
   variable rather than of its class.

   Two attributes sit alongside the variables and are populated by the reader,
   not by this module. ``global_attributes`` holds the file's netCDF global
   attributes. ``promoted`` records the variables that the reader
   re-classified as :class:`~iris.fileformats.cf.CFDataVariable`, which it
   does for two distinct reasons: a formula term that names a reference
   surface is a data pay-load in its own right, per [CF] Appendix D; and a
   variable that was referenced but whose dimensions are not a subset of the
   referrer's is dropped from that referrer with a warning, then promoted so
   that it is not lost altogether. Promotion is how Iris avoids silently
   discarding either.

"""

from collections.abc import MutableMapping

from ._variables import (
    CFAncillaryDataVariable,
    CFAuxiliaryCoordinateVariable,
    CFBoundaryVariable,
    CFClimatologyVariable,
    CFCoordinateVariable,
    CFDataVariable,
    CFGridMappingVariable,
    CFLabelVariable,
    CFMeasureVariable,
    CFUGridAuxiliaryCoordinateVariable,
    CFUGridConnectivityVariable,
    CFUGridMeshVariable,
    CFVariable,
)


################################################################################
[docs] class CFGroup(MutableMapping): """Collection of 'NetCDF CF Metadata Conventions variables and netCDF global attributes. Represents a collection of 'NetCDF Climate and Forecast (CF) Metadata Conventions' variables and netCDF global attributes. """ def __init__(self): #: Collection of CF-netCDF variables self._cf_variables = {} #: Collection of netCDF global attributes self.global_attributes = {} #: Collection of CF-netCDF variables promoted to a CFDataVariable. self.promoted = {} def _cf_getter(self, cls): # Generate dictionary with dictionary comprehension. return { cf_name: cf_var for cf_name, cf_var in self._cf_variables.items() if isinstance(cf_var, cls) } @property def ancillary_variables(self): """Collection of CF-netCDF ancillary variables.""" return self._cf_getter(CFAncillaryDataVariable) @property def auxiliary_coordinates(self): """Collection of CF-netCDF auxiliary coordinate variables.""" return self._cf_getter(CFAuxiliaryCoordinateVariable) @property def bounds(self): """Collection of CF-netCDF boundary variables.""" return self._cf_getter(CFBoundaryVariable) @property def climatology(self): """Collection of CF-netCDF climatology variables.""" return self._cf_getter(CFClimatologyVariable) @property def coordinates(self): """Collection of CF-netCDF coordinate variables.""" return self._cf_getter(CFCoordinateVariable) @property def data_variables(self): """Collection of CF-netCDF data pay-load variables.""" return self._cf_getter(CFDataVariable) @property def formula_terms(self): """Collection of CF-netCDF variables that participate in a CF-netCDF formula term.""" return { cf_name: cf_var for cf_name, cf_var in self._cf_variables.items() if cf_var.has_formula_terms() } @property def grid_mappings(self): """Collection of CF-netCDF grid mapping variables.""" return self._cf_getter(CFGridMappingVariable) @property def labels(self): """Collection of CF-netCDF label variables.""" return self._cf_getter(CFLabelVariable) @property def cell_measures(self): """Collection of CF-netCDF measure variables.""" return self._cf_getter(CFMeasureVariable) @property def non_data_variable_names(self): """:class:`set` names of the CF-netCDF variables that are not the data pay-load.""" non_data_variables = ( self.ancillary_variables, self.auxiliary_coordinates, self.bounds, self.climatology, self.coordinates, self.grid_mappings, self.labels, self.cell_measures, self.connectivities, self.ugrid_coords, self.meshes, ) result = set() for variable in non_data_variables: result |= set(variable) return result @property def connectivities(self): """Collection of CF-UGRID connectivity variables.""" return self._cf_getter(CFUGridConnectivityVariable) @property def ugrid_coords(self): """Collection of CF-UGRID-relevant auxiliary coordinate variables.""" return self._cf_getter(CFUGridAuxiliaryCoordinateVariable) @property def meshes(self): """Collection of CF-UGRID mesh variables.""" return self._cf_getter(CFUGridMeshVariable)
[docs] def keys(self): """Return the names of all the CF-netCDF variables in the group.""" return self._cf_variables.keys()
def __len__(self): return len(self._cf_variables) def __iter__(self): for item in self._cf_variables: yield item def __setitem__(self, name, variable): if not isinstance(variable, CFVariable): raise TypeError( "Attempted to add an invalid CF-netCDF variable to the %s" % self.__class__.__name__ ) if name != variable.cf_name: raise ValueError( "Mismatch between key name %r and CF-netCDF variable name %r" % (str(name), variable.cf_name) ) self._cf_variables[name] = variable def __getitem__(self, name): if name not in self._cf_variables: raise KeyError("Cannot get unknown CF-netCDF variable name %r" % str(name)) return self._cf_variables[name] def __delitem__(self, name): if name not in self._cf_variables: raise KeyError( "Cannot delete unknown CF-netcdf variable name %r" % str(name) ) del self._cf_variables[name] def __repr__(self): result = [] result.append("variables:%d" % len(self._cf_variables)) result.append("global_attributes:%d" % len(self.global_attributes)) result.append("promoted:%d" % len(self.promoted)) return "<%s of %s>" % (self.__class__.__name__, ", ".join(result))