From 219e0d0dee53dc23a84d25e83f58cc864839f4a8 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Tue, 1 Mar 2022 19:44:31 +0000 Subject: [PATCH 1/8] dev --- cfdm/field.py | 133 ++++++++++- cfdm/mixin/fielddomain.py | 491 ++++++++++++++++++++++++++++++++------ 2 files changed, 552 insertions(+), 72 deletions(-) diff --git a/cfdm/field.py b/cfdm/field.py index 72689ce79e..06f05e9b32 100644 --- a/cfdm/field.py +++ b/cfdm/field.py @@ -409,9 +409,68 @@ def _test_docstring_substitution_Field(self, inplace=False, verbose=None): """ print("_test_docstring_substitution_Field") - # ---------------------------------------------------------------- - # Attributes - # ---------------------------------------------------------------- + def field_ancillary( + self, + *identity, + default=ValueError(), + key=False, + item=False, + **filter_kwargs, + ): + """Select a field ancillary construct. + + {{unique construct}} + + .. versionadded:: (cfdm) 1.9.1.0 + + .. seealso:: `construct`, `field_ancillaries` + + :Parameters: + + identity: optional + Select field ancillary constructs that have an + identity, defined by their `!identities` methods, that + matches any of the given values. + + Additionally, the values are matched against construct + identifiers, with or without the ``'key%'`` prefix. + + If no values are provided then all field ancillary + constructs are selected. + + {{value match}} + + {{displayed identity}} + + {{key: `bool`, optional}} + + {{item: `bool`, optional}} + + default: optional + Return the value of the *default* parameter if there + is no unique construct. + + {{default Exception}} + + {{filter_kwargs: optional}} + + :Returns: + + {{Returns construct}} + + **Examples** + + """ + return self._construct( + "field_ancillary", + "field_ancillaries", + identity, + key=key, + item=item, + default=default, + **filter_kwargs, + ) + def field_ancillaries(self, *identities, **filter_kwargs): """Return field ancillary constructs. @@ -466,6 +525,74 @@ def field_ancillaries(self, *identities, **filter_kwargs): **filter_kwargs, ) + def cell_method( + self, + *identity, + default=ValueError(), + key=False, + item=False, + **filter_kwargs, + ): + """Select a cell method construct. + + {{unique construct}} + + .. versionadded:: (cfdm) 1.9.1.0 + + .. seealso:: `construct`, `cell_methods` + + :Parameters: + + identity: optional + Select cell method constructs that have an identity, + defined by their `!identities` methods, that matches + any of the given values. + + Additionally, the values are matched against construct + identifiers, with or without the ``'key%'`` prefix. + + Additionally, if for a given value + ``f.domain_axes(value)`` returns a unique domain axis + construct then any cell method constructs that span + exactly that axis are selected. See `domain_axes` for + details. + + If no values are provided then all cell method + constructs are selected. + + {{value match}} + + {{displayed identity}} + + {{key: `bool`, optional}} + + {{item: `bool`, optional}} + + default: optional + Return the value of the *default* parameter if there + is no unique construct. + + {{default Exception}} + + {{filter_kwargs: optional}} + + :Returns: + + {{Returns construct}} + + **Examples** + + """ + return self._construct( + "cell_method", + "cell_methods", + identity, + key=key, + item=item, + default=default, + **filter_kwargs, + ) + def cell_methods(self, *identities, **filter_kwargs): """Return cell method constructs. diff --git a/cfdm/mixin/fielddomain.py b/cfdm/mixin/fielddomain.py index c6680d73b9..9391c96915 100644 --- a/cfdm/mixin/fielddomain.py +++ b/cfdm/mixin/fielddomain.py @@ -13,9 +13,6 @@ class FieldDomain: """ - # ---------------------------------------------------------------- - # Private methods - # ---------------------------------------------------------------- def _apply_masking_constructs(self): """Apply masking to metadata constructs in-place. @@ -34,6 +31,65 @@ def _apply_masking_constructs(self): for c in self.constructs.filter_by_data(todict=True).values(): c.apply_masking(inplace=True) + def _construct( + self, + _method, + _constructs_method, + identities, + key=False, + item=False, + default=ValueError(), + cached=None, + **filter_kwargs, + ): + """An interface to `Constructs.filter`. + + {{unique construct}} + + .. versionadded:: (cfdm) 1.9.1.0 + + :Parameters: + + _method: `str` + The name of the calling method. + + _constructs_method: `str` + The name of the corresponding method that can return + any number of constructs. + + identities: sequence + As for the *identities* parameter of the calling + method. + + {{key: `bool`, optional}} + + {{item: `bool`, optional}} + + default: optional + Return the value of the *default* parameter if there + is no unique construct. + + {{default Exception}} + + {{cached: optional}} + + {{filter_kwargs: optional}} + + :Returns: + + {{Returns construct}} + + """ + if cached is not None: + return cached + + filter_kwargs["todict"] = True + + c = getattr(self, _constructs_method)(*identities, **filter_kwargs) + + # Return construct, or key, or both, or default + return self._filter_return_construct(c, key, item, default, _method) + def _get_data_compression_variables(self, component): """TODO.""" out = [] @@ -157,7 +213,7 @@ def _filter_interface( construct=False, key=False, item=False, - default=None, + default=ValueError(), _identity_config={}, **filter_kwargs, ): @@ -180,10 +236,22 @@ def _filter_interface( {{value match}} - {{todict: `bool`, optional}} - {{cached: optional}} + {{key: `bool`, optional}} + + .. versionadded:: (cfdm) 1.9.1.0 + + {{item: `bool`, optional}} + + .. versionadded:: (cfdm) 1.9.1.0 + + default: optional + If *construct* is True then return the value of the + *default* parameter if there is no unique construct. + + {{default Exception}} + {{filter_kwargs: optional}} :Returns: @@ -200,8 +268,8 @@ def _filter_interface( if not _ctypes: kwargs = filter_kwargs else: - # Ensure that filter_by_types is the first filter - # applied, as it's the cheapest + # Ensure that filter_by_types is the first filter applied, + # as it's the cheapest. if not (identities or filter_kwargs): # This a very common pattern for which calling # filter_by_type directly is faster @@ -220,25 +288,6 @@ def _filter_interface( return self._filter_return_construct( c, key, item, default, _method ) - # n = len(c) - # if n == 1: - # k, construct = c.popitem() - # if key: - # return k - # - # if item: - # return k, construct - # - # return construct - # - # if default is None: - # return default - # - # return self._default( - # default, - # f"{self.__class__.__name__}.{_method}() can't return {n} " - # "constructs", - # ) kwargs = {"filter_by_type": _ctypes} @@ -251,8 +300,8 @@ def _filter_interface( kwargs.update(filter_kwargs) - # Ensure that filter_by_identity is the one of the last - # filters applied, as it's expensive. + # Ensure that filter_by_identity is the last filter applied, + # as it's expensive. if filter_kwargs: if identities: if "filter_by_identity" in filter_kwargs: @@ -285,25 +334,6 @@ def _filter_interface( # Return construct, or key, or both, or default return self._filter_return_construct(c, key, item, default, _method) - # if len(c) == 1: - # k, construct = c.popitem() - # if key: - # return k - # - # if item: - # return k, construct - # - # return construct - # - # if default is None: - # return default - # - # return self._default( - # default, - # f"{self.__class__.__name__}.{_method}() can't return {len(c)} " - # "constructs", - # ) - def _unique_construct_names(self): """Return unique metadata construct names. @@ -379,6 +409,68 @@ def _unique_domain_axis_identities(self): return key_to_name + def auxiliary_coordinate( + self, + *identity, + default=ValueError(), + key=False, + item=False, + **filter_kwargs, + ): + """Select an auxiliary coordinate construct. + + {{unique construct}} + + .. versionadded:: (cfdm) 1.9.1.0 + + .. seealso:: `construct`, `auxiliary_coordinates` + + :Parameters: + + identity: optional + Select auxiliary coordinate constructs that have an + identity, defined by their `!identities` methods, that + matches any of the given values. + + Additionally, the values are matched against construct + identifiers, with or without the ``'key%'`` prefix. + + If no values are provided then all auxiliary + coordinate constructs are selected. + + {{value match}} + + {{displayed identity}} + + {{key: `bool`, optional}} + + {{item: `bool`, optional}} + + default: optional + Return the value of the *default* parameter if there + is no unique construct. + + {{default Exception}} + + {{filter_kwargs: optional}} + + :Returns: + + {{Returns construct}} + + **Examples** + + """ + return self._construct( + "auxiliary_coordinate", + "auxiliary_coordinates", + identity, + key=key, + item=item, + default=default, + **filter_kwargs, + ) + def auxiliary_coordinates(self, *identities, **filter_kwargs): """Return auxiliary coordinate constructs. @@ -628,6 +720,68 @@ def del_construct(self, *identity, default=ValueError(), **filter_kwargs): return self._default(default, "Can't find unique construct to remove") + def dimension_coordinate( + self, + *identity, + key=False, + default=ValueError(), + item=False, + **filter_kwargs, + ): + """Select a dimension coordinate construct. + + {{unique construct}} + + .. versionadded:: (cfdm) 1.9.1.0 + + .. seealso:: `construct`, `dimension_coordinates` + + :Parameters: + + identity: optional + Select dimension coordinate constructs that have an + identity, defined by their `!identities` methods, that + matches any of the given values. + + Additionally, the values are matched against construct + identifiers, with or without the ``'key%'`` prefix. + + If no values are provided then all dimension + coordinate constructs are selected. + + {{value match}} + + {{displayed identity}} + + {{key: `bool`, optional}} + + {{item: `bool`, optional}} + + default: optional + Return the value of the *default* parameter if there + is no unique construct. + + {{default Exception}} + + {{filter_kwargs: optional}} + + :Returns: + + {{Returns construct}} + + **Examples** + + """ + return self._construct( + "dimension_coordinate", + "dimension_coordinates", + identity, + key=key, + item=item, + default=default, + **filter_kwargs, + ) + def dimension_coordinates(self, *identities, **filter_kwargs): """Return dimension coordinate constructs. @@ -683,6 +837,79 @@ def dimension_coordinates(self, *identities, **filter_kwargs): **filter_kwargs, ) + def domain_axis( + self, + *identity, + key=False, + default=ValueError(), + item=False, + **filter_kwargs, + ): + """Select a domain axis construct. + + {{unique construct}} + + .. versionadded:: (cfdm) 1.9.1.0 + + .. seealso:: `construct`, `domain_axes` + + :Parameters: + + identities: `tuple`, optional + Select domain axis constructs that have an identity, + defined by their `!identities` methods, that matches + any of the given values. + + Additionally, the values are matched against construct + identifiers, with or without the ``'key%'`` prefix. + + Additionally, if for a given `value``, + ``f.coordinates(value, filter_by_naxes=(1,))`` returns + 1-d coordinate constructs that all span the same + domain axis construct then that domain axis construct + is selected. See `coordinates` for details. + + Additionally, if there is a `Field` data array and a + value matches the integer position of an array + dimension, then the corresponding domain axis + construct is selected. + + If no values are provided then all domain axis + constructs are selected. + + {{value match}} + + {{displayed identity}} + + {{key: `bool`, optional}} + + {{item: `bool`, optional}} + + default: optional + Return the value of the *default* parameter if there + is no unique construct. + + {{default Exception}} + + {{filter_kwargs: optional}} + + :Returns: + + {{Returns construct}} + + **Examples** + + """ + return self._construct( + "domain_axis", + "domain_axes", + identity, + key=key, + item=item, + default=default, + **filter_kwargs, + ) + def domain_axes(self, *identities, **filter_kwargs): """Return domain axis constructs. @@ -731,6 +958,68 @@ def domain_axes(self, *identities, **filter_kwargs): """ return self.constructs.domain_axes(*identities, **filter_kwargs) + def domain_ancillary( + self, + *identity, + default=ValueError(), + key=False, + item=False, + **filter_kwargs, + ): + """Select a domain ancillary construct. + + {{unique construct}} + + .. versionadded:: (cfdm) 1.9.1.0 + + .. seealso:: `construct`, `domain_ancillaries` + + :Parameters: + + identity: optional + Select domain ancillary constructs that have an + identity, defined by their `!identities` methods, that + matches any of the given values. + + Additionally, the values are matched against construct + identifiers, with or without the ``'key%'`` prefix. + + If no values are provided then all domain ancillary + constructs are selected. + + {{value match}} + + {{displayed identity}} + + {{key: `bool`, optional}} + + {{item: `bool`, optional}} + + default: optional + Return the value of the *default* parameter if there + is no unique construct. + + {{default Exception}} + + {{filter_kwargs: optional}} + + :Returns: + + {{Returns construct}} + + **Examples** + + """ + return self._construct( + "domain_ancillary", + "domain_ancillaries", + identity, + key=key, + item=item, + default=default, + **filter_kwargs, + ) + def domain_ancillaries(self, *identities, **filter_kwargs): """Return domain ancillary constructs. @@ -782,6 +1071,68 @@ def domain_ancillaries(self, *identities, **filter_kwargs): **filter_kwargs, ) + def cell_measure( + self, + *identity, + default=ValueError(), + key=False, + item=False, + **filter_kwargs, + ): + """Select a cell measure construct. + + {{unique construct}} + + .. versionadded:: (cfdm) 1.9.1.0 + + .. seealso:: `construct`, `cell_measures` + + :Parameters: + + identity: optional + Select dimension coordinate constructs that have an + identity, defined by their `!identities` methods, that + matches any of the given values. + + Additionally, the values are matched against construct + identifiers, with or without the ``'key%'`` prefix. + + If no values are provided then all dimension + coordinate constructs are selected. + + {{value match}} + + {{displayed identity}} + + {{key: `bool`, optional}} + + {{item: `bool`, optional}} + + default: optional + Return the value of the *default* parameter if there + is no unique construct. + + {{default Exception}} + + {{filter_kwargs: optional}} + + :Returns: + + {{Returns construct}} + + **Examples** + + """ + return self._construct( + "cell_measure", + "cell_measures", + identity, + key=key, + item=item, + default=default, + **filter_kwargs, + ) + def cell_measures(self, *identities, **filter_kwargs): """Return cell measure constructs. @@ -831,14 +1182,23 @@ def cell_measures(self, *identities, **filter_kwargs): ("cell_measure",), "cell_measures", identities, **filter_kwargs ) - def construct(self, *identity, default=ValueError(), **filter_kwargs): + def construct( + self, + *identity, + default=ValueError(), + key=False, + item=False, + **filter_kwargs, + ): """Return a metadata construct. {{unique construct}} .. versionadded:: (cfdm) 1.7.0 - .. seealso:: `constructs`, `construct_item`, `construct_key` + .. seealso:: `constructs`, `del_construct`, `get_construct`, + `has_construct`, `set_construct` + `construct_item`, `construct_key` :Parameters: @@ -863,15 +1223,23 @@ def construct(self, *identity, default=ValueError(), **filter_kwargs): {{default Exception}} + {{key: `bool`, optional}} + + .. versionadded:: (cfdm) 1.9.1.0 + + {{item: `bool`, optional}} + + .. versionadded:: (cfdm) 1.9.1.0 + {{filter_kwargs: optional}} .. versionadded:: (cfdm) 1.8.9.0 :Returns: - The selected construct. + {{Returns construct}} - **Examples:** + **Examples** >>> f = {{package}}.example_{{class_lower}}(0) @@ -903,8 +1271,8 @@ def construct(self, *identity, default=ValueError(), **filter_kwargs): "construct", identity, construct=True, - key=False, - item=False, + key=key, + item=item, default=default, **filter_kwargs, ) @@ -1295,21 +1663,6 @@ def equals( f"{self.__class__.__name__}: Different metadata constructs" ) return False - # if not self._equals( - # self.constructs, - # other.constructs, - # rtol=rtol, - # atol=atol, - # verbose=verbose, - # ignore_data_type=ignore_data_type, - # ignore_fill_value=ignore_fill_value, - # ignore_compression=ignore_compression, - # _ignore_type=False, - # ): - # logger.info( - # f"{self.__class__.__name__}: Different metadata constructs" - # ) - # return False return True From 49da791ec7e4e889e2f3ad684677c4dbe8d5f4b0 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Wed, 2 Mar 2022 10:49:27 +0000 Subject: [PATCH 2/8] backport of construct access --- cfdm/docstring/docstring.py | 21 ++++- cfdm/domain.py | 4 +- cfdm/field.py | 84 +++++++++--------- cfdm/mixin/fielddomain.py | 135 +++++++++++++++++++++++++++- cfdm/test/test_Field.py | 140 ++++++++++++++++++++++++++++++ docs/source/class/cfdm.Domain.rst | 7 ++ docs/source/class/cfdm.Field.rst | 10 +++ docs/source/tutorial.py | 53 +++++------ docs/source/tutorial.rst | 101 +++++++++++++-------- 9 files changed, 443 insertions(+), 112 deletions(-) diff --git a/cfdm/docstring/docstring.py b/cfdm/docstring/docstring.py index 487ba6e0b7..b12ca1b658 100644 --- a/cfdm/docstring/docstring.py +++ b/cfdm/docstring/docstring.py @@ -266,10 +266,17 @@ Keyword arguments as accepted by `Constructs.filter` that define additional construct selection criteria.""", - # Returns constructs - "{{Returns constructs}}": """ - The selected constructs in a new `Constructs` object, - unless modified by any *filter_kwargs* parameters.""", + # key: `bool`, optional + "{{key: `bool`, optional}}": """key: `bool`, optional + If True then return the selected construct + identifier. By default the construct itself is + returned.""", + # item: `bool`, optional + "{{item: `bool`, optional}}": """item: `bool`, optional + If True then return the selected construct identifier + and the construct itself. By default the construct + itself is returned. If *key* is True then *item* is + ignored.""", # ---------------------------------------------------------------- # Method description susbstitutions (4 levels of indentataion) # ---------------------------------------------------------------- @@ -281,4 +288,10 @@ "{{displayed identity}}": """Note that in the output of a `dump` method or `print` call, a construct is always described by an identity that will select it.""", + # Returns construct + "{{Returns construct}}": """The selected construct, or its identifier if *key* is + True, or a `tuple` of both if *item* is True.""", + # Returns constructs + "{{Returns constructs}}": """The selected constructs in a new `Constructs` object, + unless modified by any *filter_kwargs* parameters.""", } diff --git a/cfdm/domain.py b/cfdm/domain.py index 5745f1cb08..72b845ba42 100644 --- a/cfdm/domain.py +++ b/cfdm/domain.py @@ -391,9 +391,9 @@ def climatological_time_axes(self): The keys of the domain axis constructs that are climatological time axes. - **Examples:** + **Examples** - >>> d = cfdm.example_field(0) + >>> d = cfdm.example_field(0).domain >>> d.climatological_time_axes() set() diff --git a/cfdm/field.py b/cfdm/field.py index 06f05e9b32..4641a91502 100644 --- a/cfdm/field.py +++ b/cfdm/field.py @@ -791,7 +791,7 @@ def climatological_time_axes(self): The axes on the field which are climatological time axes. If there are none, this will be an empty set. - **Examples:** + **Examples** >>> f <{{repr}}Field: air_temperature(time(12), latitude(145), longitude(192)) K> @@ -828,35 +828,10 @@ def climatological_time_axes(self): continue # Still here? Then this axis is a climatological time axis - # out.append((axis,)) out.add(axis) return out - # out = [] - # - # domain_axes = None - # - # for key, cm in self.cell_methods(todict=True).items(): - # qualifiers = cm.qualifiers() - # if not ("within" in qualifiers or "over" in qualifiers): - # continue - # - # axes = cm.get_axes(default=()) - # if len(axes) != 1: - # continue - # - # domain_axes = self.domain_axes(cached=domain_axes, todict=True) - # - # axis = axes[0] - # if axis not in domain_axes: - # continue - # - # # Still here? Then this axis is a climatological time axis - # out.append((axis,)) - # - # return out - @_inplace_enabled(default=False) def compress( self, @@ -1746,7 +1721,7 @@ def dump(self, display=True, _level=0, _title=None): return "\n".join(string) - def get_data_axes(self, key=None, default=ValueError()): + def get_data_axes(self, *identity, default=ValueError(), **filter_kwargs): """Gets the keys of the axes spanned by the construct data. Specifically, returns the keys of the domain axis constructs @@ -1758,12 +1733,16 @@ def get_data_axes(self, key=None, default=ValueError()): :Parameters: - key: `str`, optional - Specify a metadata construct, instead of the field - construct. + identity, filter_kwargs: optional + Select the unique construct returned by + ``f.construct(*identity, **filter_kwargs)``. See + `construct` for details. - *Parameter example:* - ``key='auxiliarycoordinate0'`` + If neither *identity* nor *filter_kwargs* are set then + the domain of the field constructs's data are + returned. + + .. versionadded:: (cfdm) 1.9.1.0 default: optional Return the value of the *default* parameter if the data @@ -1771,18 +1750,24 @@ def get_data_axes(self, key=None, default=ValueError()): {{default Exception}} + {{filter_kwargs: optional}} + + .. versionadded:: (cfdm) 1.9.1.0 + :Returns: `tuple` The keys of the domain axis constructs spanned by the data. - **Examples:** + **Examples** >>> f = {{package}}.example_field(0) >>> f.get_data_axes() ('domainaxis0', 'domainaxis1') - >>> f.get_data_axes(key='dimensioncoordinate2') + >>> f.get_data_axes('latitude') + ('domainaxis0',) + >>> f.get_data_axes('time') ('domainaxis2',) >>> f.has_data_axes() True @@ -1794,17 +1779,32 @@ def get_data_axes(self, key=None, default=ValueError()): 'no axes' """ - if key is not None: - return super().get_data_axes(key, default=default) + if not identity and not filter_kwargs: + # Get axes of the Field data array + return super().get_data_axes(default=default) + + key = self.construct( + *identity, key=True, default=None, **filter_kwargs + ) + if key is None: + if default is None: + return default - try: - return self._get_component("data_axes") - except ValueError: return self._default( - default, - "{!r} has no data axes".format(self.__class__.__name__), + default, "Can't get axes for non-existent construct" ) + axes = super().get_data_axes(key, default=None) + if axes is None: + if default is None: + return default + + return self._default( + default, "Can't get axes for non-existent construct" + ) + + return axes + def get_domain(self): """Return the domain. @@ -2062,7 +2062,7 @@ def convert(self, *identity, full_domain=True, **filter_kwargs): """ filter_kwargs.pop("item", None) - key, c = self.construct_item(*identity, **filter_kwargs) + key, c = self.construct(*identity, item=True, **filter_kwargs) if c is None: raise ValueError("Can't return zero constructs") diff --git a/cfdm/mixin/fielddomain.py b/cfdm/mixin/fielddomain.py index 9391c96915..f209b7fefc 100644 --- a/cfdm/mixin/fielddomain.py +++ b/cfdm/mixin/fielddomain.py @@ -525,6 +525,68 @@ def auxiliary_coordinates(self, *identities, **filter_kwargs): **filter_kwargs, ) + def coordinate( + self, + *identity, + default=ValueError(), + key=False, + item=False, + **filter_kwargs, + ): + """Select a dimension or auxiliary coordinate construct. + + {{unique construct}} + + .. versionadded:: (cfdm) 1.9.1.0 + + .. seealso:: `construct`, `coordinates` + + :Parameters: + + identity: optional + Select dimension or auxiliary coordinate constructs + that have an identity, defined by their `!identities` + methods, that matches any of the given values. + + Additionally, the values are matched against construct + identifiers, with or without the ``'key%'`` prefix. + + If no values are provided then all dimension or + auxiliary coordinate constructs are selected. + + {{value match}} + + {{displayed identity}} + + {{key: `bool`, optional}} + + {{item: `bool`, optional}} + + default: optional + Return the value of the *default* parameter if there + is no unique construct. + + {{default Exception}} + + {{filter_kwargs: optional}} + + :Returns: + + {{Returns construct}} + + **Examples** + + """ + return self._construct( + "coordinate", + "coordinates", + identity, + key=key, + item=item, + default=default, + **filter_kwargs, + ) + def coordinates(self, *identities, **filter_kwargs): """Return dimension and auxiliary coordinate constructs. @@ -588,6 +650,66 @@ def coordinates(self, *identities, **filter_kwargs): **filter_kwargs, ) + def coordinate_reference( + self, + *identity, + default=ValueError(), + key=False, + item=False, + **filter_kwargs, + ): + """Return a coordinate reference construct, or its key. + + .. versionadded:: (cfdm) 1.9.1.0 + + .. seealso:: `construct`, `coordinate_references` + + :Parameters: + + identity: optional + Select coordinate reference constructs that have an + identity, defined by their `!identities` methods, that + matches any of the given values. + + Additionally, the values are matched against construct + identifiers, with or without the ``'key%'`` prefix. + + If no identities are provided then all coordinate + reference constructs are selected. + + {{value match}} + + {{displayed identity}} + + {{key: `bool`, optional}} + + {{item: `bool`, optional}} + + default: optional + Return the value of the *default* parameter if there + is no unique construct. + + {{default Exception}} + + {{filter_kwargs: optional}} + + :Returns: + + {{Returns construct}} + + **Examples** + + """ + return self._construct( + "coordinate_reference", + "coordinate_references", + identity, + key=key, + item=item, + default=default, + **filter_kwargs, + ) + def coordinate_references(self, *identities, **filter_kwargs): """Return coordinate reference constructs. @@ -1198,7 +1320,6 @@ def construct( .. seealso:: `constructs`, `del_construct`, `get_construct`, `has_construct`, `set_construct` - `construct_item`, `construct_key` :Parameters: @@ -1282,9 +1403,13 @@ def construct_item(self, *identity, default=ValueError(), **filter_kwargs): {{unique construct}} + ``f.construct_item(*args, **kwargs)`` is an alias for + ``f.construct(*args, item=True, **kwargs)``. See `construct` + for details. + .. versionadded:: (cfdm) 1.8.9.0 - .. seealso:: `constructs`, `construct`, `construct_key` + .. seealso:: `construct`, `construct_key` :Parameters: @@ -1361,9 +1486,13 @@ def construct_key(self, *identity, default=ValueError(), **filter_kwargs): {{unique construct}} + ``f.construct_key(*args, **kwargs)`` is an alias for + ``f.construct(*args, itekey=True, **kwargs)``. See `construct` + for details. + .. versionadded:: (cfdm) 1.7.0 - .. seealso:: `constructs`, `construct`, `construct_item` + .. seealso:: `construct`, `construct_item` :Parameters: diff --git a/cfdm/test/test_Field.py b/cfdm/test/test_Field.py index e0d1f1b906..7f090460eb 100644 --- a/cfdm/test/test_Field.py +++ b/cfdm/test/test_Field.py @@ -635,6 +635,146 @@ def test_Field_bounds(self): f = cfdm.example_field(0) self.assertFalse(f.has_bounds()) + def test_Field_auxiliary_coordinate(self): + """Test that Field.auxiliary_coordinate.""" + f = self.f1 + + for identity in ("auxiliarycoordinate1", "latitude"): + key, c = f.construct(identity, item=True) + self.assertTrue(f.auxiliary_coordinate(identity).equals(c)) + self.assertEqual(f.auxiliary_coordinate(identity, key=True), key) + + with self.assertRaises(ValueError): + f.auxiliary_coordinate("long_name:qwerty") + + def test_Field_coordinate(self): + """Test that Field.coordinate.""" + f = self.f1 + + for identity in ( + "latitude", + "grid_longitude", + "auxiliarycoordinate1", + "dimensioncoordinate1", + ): + key, c = f.construct(identity, item=True) + + with self.assertRaises(ValueError): + f.coordinate("long_name:qweRty") + + def test_Field_coordinate_reference(self): + """Test that Field.coordinate_reference.""" + f = self.f1 + + for identity in ( + "coordinatereference1", + "key%coordinatereference0", + "standard_name:atmosphere_hybrid_height_coordinate", + "grid_mapping_name:rotated_latitude_longitude", + ): + key, c = f.construct(identity, item=True) + self.assertTrue(f.coordinate_reference(identity).equals(c)) + self.assertEqual(f.coordinate_reference(identity, key=True), key) + + with self.assertRaises(ValueError): + f.coordinate_reference("qwerty") + + def test_Field_dimension_coordinate(self): + """Test that Field.dimension_coordinate.""" + f = self.f1 + + for identity in ("grid_latitude", "dimensioncoordinate1"): + if identity == "X": + key, c = f.construct("grid_longitude", item=True) + else: + key, c = f.construct(identity, item=True) + + self.assertTrue(f.dimension_coordinate(identity).equals(c)) + self.assertEqual(f.dimension_coordinate(identity, key=True), key) + + k, v = f.dimension_coordinate(identity, item=True) + self.assertEqual(k, key) + self.assertTrue(v.equals(c)) + + self.assertIsNone( + f.dimension_coordinate("long_name=qwerty:asd", default=None) + ) + self.assertEqual( + len(f.dimension_coordinates("long_name=qwerty:asd")), 0 + ) + + with self.assertRaises(ValueError): + f.dimension_coordinate("long_name:qwerty") + + def test_Field_cell_measure(self): + """Test that Field.cell_measure.""" + f = self.f1 + + for identity in ("measure:area", "cellmeasure0"): + key, c = f.construct(identity, item=True) + + self.assertTrue(f.cell_measure(identity).equals(c)) + self.assertEqual(f.cell_measure(identity, key=True), key) + + self.assertTrue(f.cell_measure(identity).equals(c)) + self.assertEqual(f.cell_measure(identity, key=True), key) + + self.assertEqual(len(f.cell_measures()), 1) + self.assertEqual(len(f.cell_measures("measure:area")), 1) + self.assertEqual(len(f.cell_measures(*["measure:area"])), 1) + + self.assertIsNone(f.cell_measure("long_name=qwerty:asd", default=None)) + self.assertEqual(len(f.cell_measures("long_name=qwerty:asd")), 0) + + with self.assertRaises(ValueError): + f.cell_measure("long_name:qwerty") + + def test_Field_cell_method(self): + """Test that Field.cell_method.""" + f = self.f1 + + for identity in ("method:mean", "cellmethod0"): + key, c = f.construct(identity, item=True) + self.assertTrue(f.cell_method(identity).equals(c)) + self.assertEqual(f.cell_method(identity, key=True), key) + + def test_Field_domain_ancillary(self): + """Test that Field.domain_ancillary.""" + f = self.f1 + + for identity in ("surface_altitude", "domainancillary0"): + key, c = f.construct(identity, item=True) + self.assertTrue(f.domain_ancillary(identity).equals(c)) + self.assertEqual(f.domain_ancillary(identity, key=True), key) + + with self.assertRaises(ValueError): + f.domain_ancillary("long_name:qwerty") + + def test_Field_field_ancillary(self): + """Test that Field.field_ancillary.""" + f = self.f1 + + for identity in ("air_temperature standard_error", "fieldancillary0"): + key, c = f.construct_item(identity) + self.assertTrue(f.field_ancillary(identity).equals(c)) + self.assertEqual(f.field_ancillary(identity, key=True), key) + + with self.assertRaises(ValueError): + f.field_ancillary("long_name:qwerty") + + def test_Field_domain_axis(self): + """Test that Field.domain_axis.""" + f = self.f1 + + f.domain_axis(1) + f.domain_axis("domainaxis2") + + with self.assertRaises(ValueError): + f.domain_axis(99) + + with self.assertRaises(ValueError): + f.domain_axis("qwerty") + if __name__ == "__main__": print("Run date:", datetime.datetime.now()) diff --git a/docs/source/class/cfdm.Domain.rst b/docs/source/class/cfdm.Domain.rst index 23bf515a57..c7b4aa8c83 100644 --- a/docs/source/class/cfdm.Domain.rst +++ b/docs/source/class/cfdm.Domain.rst @@ -59,12 +59,19 @@ Metadata constructs ~cfdm.Domain.has_data_axes ~cfdm.Domain.set_data_axes ~cfdm.Domain.domain_axis_key + ~cfdm.Domain.auxiliary_coordinate ~cfdm.Domain.auxiliary_coordinates + ~cfdm.Domain.cell_measure ~cfdm.Domain.cell_measures + ~cfdm.Domain.coordinate ~cfdm.Domain.coordinates + ~cfdm.Domain.coordinate_reference ~cfdm.Domain.coordinate_references + ~cfdm.Domain.dimension_coordinate ~cfdm.Domain.dimension_coordinates + ~cfdm.Domain.domain_ancillary ~cfdm.Domain.domain_ancillaries + ~cfdm.Domain.domain_axis ~cfdm.Domain.domain_axes ~cfdm.Domain.climatological_time_axes diff --git a/docs/source/class/cfdm.Field.rst b/docs/source/class/cfdm.Field.rst index 133f935205..c72af5b5d0 100644 --- a/docs/source/class/cfdm.Field.rst +++ b/docs/source/class/cfdm.Field.rst @@ -119,15 +119,25 @@ Metadata constructs ~cfdm.Field.has_data_axes ~cfdm.Field.set_data_axes ~cfdm.Field.domain_axis_key + ~cfdm.Field.auxiliary_coordinate ~cfdm.Field.auxiliary_coordinates + ~cfdm.Field.cell_measure ~cfdm.Field.cell_measures + ~cfdm.Field.cell_method ~cfdm.Field.cell_methods + ~cfdm.Field.coordinate ~cfdm.Field.coordinates + ~cfdm.Field.coordinate_reference ~cfdm.Field.coordinate_references + ~cfdm.Field.dimension_coordinate ~cfdm.Field.dimension_coordinates + ~cfdm.Field.domain_ancillary ~cfdm.Field.domain_ancillaries + ~cfdm.Field.domain_axis ~cfdm.Field.domain_axes + ~cfdm.Field.field_ancillary ~cfdm.Field.field_ancillaries + ~cfdm.Field.climatological_time_axes .. rubric:: Attributes diff --git a/docs/source/tutorial.py b/docs/source/tutorial.py index c445f1c55e..090061e99f 100644 --- a/docs/source/tutorial.py +++ b/docs/source/tutorial.py @@ -153,13 +153,17 @@ print(t.constructs.filter_by_type('cell_measure')) print(t.cell_measures()) t.construct('latitude') -key = t.construct_key('latitude') +key = t.construct('latitude', key=True) t.construct(key) -key, lat = t.construct_item('latitude') -key = t.construct_key('latitude') +key, lat = t.construct('latitude', item=True) +key = t.construct('latitude', key=True) t.constructs[key] -key = t.construct_key('latitude') +key = t.construct('latitude', key=True) c = t.constructs.get(key) +c = t.constructs('latitude').value() +t.auxiliary_coordinate('latitude') +t.auxiliary_coordinate('latitude', key=True) +t.auxiliary_coordinate('latitude', item=True) try: t.construct('measure:volume') # Raises Exception except: @@ -182,19 +186,18 @@ lon lon.set_property('long_name', 'Longitude') lon.properties() -area = t.constructs.filter_by_property(units='km2').value() +area = t.construct('units=km2') area area.identity() area.identities() -lon = q.constructs('longitude').value() +t.construct('measure:area') +lon = q.construct('longitude') lon lon.data lon.data[2] lon.data[2] = 133.33 print(lon.data.array) -key = t.construct_key('latitude') -key -t.get_data_axes(key=key) +t.get_data_axes('latitude') t.constructs.data_axes() time = q.construct('time') time @@ -206,8 +209,8 @@ domain print(domain) description = domain.dump(display=False) -domain_latitude = t.domain.constructs('latitude').value() -field_latitude = t.constructs('latitude').value() +domain_latitude = t.domain.construct('latitude') +field_latitude = t.construct('latitude') domain_latitude.set_property('test', 'set by domain') print(field_latitude.get_property('test')) field_latitude.set_property('test', 'set by field') @@ -219,7 +222,7 @@ d d.get_size() print(t.coordinates()) -lon = t.constructs('grid_longitude').value() +lon = t.construct('grid_longitude') bounds = lon.bounds bounds bounds.data @@ -238,7 +241,7 @@ bounds = a.bounds bounds print(bounds.data.array) -crs = t.constructs('standard_name:atmosphere_hybrid_height_coordinate').value() +crs = t.construct('standard_name:atmosphere_hybrid_height_coordinate') crs crs.dump() crs.coordinates() @@ -248,7 +251,7 @@ crs.coordinate_conversion.parameters() crs.coordinate_conversion.domain_ancillaries() print(t.cell_methods()) -cm = t.constructs('method:mean').value() +cm = t.construct('method:mean') cm cm.get_axes() cm.get_method() @@ -534,10 +537,9 @@ numpy_array = v[...] data_memory = cfdm.Data(numpy_array) data_disk.equals(data_memory) -key = tas.construct_key('surface_altitude') -orog = tas.convert(key) +orog = tas.convert('surface_altitude') print(orog) -orog1 = tas.convert(key, full_domain=False) +orog1 = tas.convert('surface_altitude', full_domain=False) print(orog1) cfdm.write(tas, 'tas.nc') f = cfdm.read('tas.nc') @@ -555,7 +557,7 @@ t.constructs('grid_latitude') import copy u = copy.deepcopy(t) -orog = t.constructs('surface_altitude').value().copy() +orog = t.construct('surface_altitude').copy() t.equals(t) t.equals(t.copy()) t.equals(t[...]) @@ -571,16 +573,16 @@ with cfdm.atol(1e-5): print(cfdm.atol()) print(cfdm.atol()) -orog = t.constructs('surface_altitude').value() +orog = t.construct('surface_altitude') orog.equals(orog.copy()) print(t.constructs.filter_by_ncvar('b')) -t.constructs('ncvar%x').value() -t.constructs('ncdim%x') +t.construct('ncvar%x') +t.construct('ncdim%x') q.nc_get_variable() q.nc_global_attributes() q.nc_set_variable('humidity') q.nc_get_variable() -q.constructs('latitude').value().nc_get_variable() +q.construct('latitude').nc_get_variable() print(q) cfdm.write(q, 'q_file.nc') x @@ -613,8 +615,7 @@ f_file.set_property('Conventions', 'UGRID1.0') cfdm.write(f, 'f_file.nc', Conventions='UGRID1.0') print(q) -key = q.construct_key('time') -axes = q.get_data_axes(key) +axes = q.get_data_axes('time') axes q2 = q.insert_dimension(axis=axes[0]) q2 @@ -637,7 +638,7 @@ f.equals(g) u = cfdm.read('parent.nc')[0] print(u) -area = u.constructs('measure:area').value() +area = u.construct('measure:area') area area.nc_get_external() area.nc_get_variable() @@ -645,7 +646,7 @@ area.has_data() g = cfdm.read('parent.nc', external='external.nc')[0] print(g) -area = g.constructs('measure:area').value() +area = g.construct('measure:area') area area.nc_get_external() area.nc_get_variable() diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index df0e82c35a..c4506cfe54 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -1490,23 +1490,19 @@ construct key, by any of the following techniques: >>> t.construct('latitude') -* with the `~Field.construct_key` method of a field construct: - .. code-block:: python :caption: *Get the "latitude" metadata construct key with its construct identity and use the key to get the construct itself* - >>> key = t.construct_key('latitude') + >>> key = t.construct('latitude', key=True) >>> t.construct(key) -* with the `~Field.construct_item` method of a field construct: - .. code-block:: python :caption: *Get the "latitude" metadata construct and its identifier via its construct identity.* - >>> key, lat = t.construct_item('latitude') + >>> key, lat = t.construct('latitude', item=True) ('auxiliarycoordinate0', ) * by indexing a `Constructs` instance with a construct key. @@ -1515,7 +1511,7 @@ construct key, by any of the following techniques: :caption: *Get the "latitude" metadata construct via its construct key and indexing* - >>> key = t.construct_key('latitude') + >>> key = t.construct('latitude', key=True) >>> t.constructs[key] @@ -1525,10 +1521,49 @@ construct key, by any of the following techniques: :caption: *Get the "latitude" metadata construct via its construct key and the 'get' method.* - >>> key = t.construct_key('latitude') + >>> key = t.construct('latitude', key=True) >>> c = t.constructs.get(key) +* with the `~Constructs.value` method of a `Constructs` instance, or + +.. code-block:: python + :caption: *Get the "latitude" metadata construct via its 'value' + method.* + + >>> c = t.constructs('latitude').value() + + +In addition, an individual metadata construct of a particular type can +be retrieved with the following methods of the field construct: + +============================= ==================== +Method Metadata construct +============================= ==================== +`~Field.domain_axis` Domain axis +`~Field.dimension_coordinate` Dimension coordinate +`~Field.auxiliary_coordinate` Auxiliary coordinate +`~Field.coordinate_reference` Coordinate reference +`~Field.domain_ancillary` Domain ancillary +`~Field.cell_measure` Cell measure +`~Field.field_ancillary` Field ancillary +`~Field.cell_method` Cell method +============================= ==================== + +These methods will only look for the given identity amongst constructs +of the chosen type. + +.. code-block:: python + :caption: *Get the "latitude" auxiliary coordinate construct via + its construct identity, and also its key.* + + >>> t.auxiliary_coordinate('latitude') + + >>> t.auxiliary_coordinate('latitude', key=True) + 'auxiliarycoordinate0' + >>> t.auxiliary_coordinate('latitude', item=True) + ('auxiliarycoordinate0', ) + The `~Field.construct` method of the field construct and the `~Constructs.value` method of the `Constructs` instance will raise an exception of there is not a unique metadata construct to return, but @@ -1587,17 +1622,18 @@ Metadata constructs share the :ref:`same API as the field construct .. code-block:: python :caption: *Get the metadata construct with units of "km2", find its canonical identity, and all of its valid identities, that - may be used for selection by the "filter_by_identity" - method* + may be used for its selection.* - >>> area = t.constructs.filter_by_property(units='km2').value() + >>> area = t.construct('units=km2') >>> area >>> area.identity() 'measure:area' >>> area.identities() ['measure:area', 'units=km2', 'ncvar%cell_measure'] - + >>> t.construct('measure:area') + + .. _Metadata-construct-data: Metadata construct data @@ -1611,7 +1647,7 @@ construct ` as the field construct for accessing their data: data, change the third element of the array, and get the data as a numpy array.* - >>> lon = q.constructs('longitude').value() + >>> lon = q.construct('longitude') >>> lon >>> lon.data @@ -1628,12 +1664,9 @@ of the field construct: .. code-block:: python :caption: *Find the construct keys of the domain axis constructs - spanned by the data of each metadata construct.* + spanned by the 'latitude' construct. - >>> key = t.construct_key('latitude') - >>> key - 'auxiliarycoordinate0' - >>> t.get_data_axes(key=key) + >>> t.get_data_axes('latitude') ('domainaxis1', 'domainaxis2') The domain axis constructs spanned by all the data of all metadata @@ -1745,8 +1778,8 @@ constructs contained in the field construct. and show that this change appears in the same metadata data construct of the parent field, and vice versa.* - >>> domain_latitude = t.domain.constructs('latitude').value() - >>> field_latitude = t.constructs('latitude').value() + >>> domain_latitude = t.domain.construct('latitude') + >>> field_latitude = t.construct('latitude') >>> domain_latitude.set_property('test', 'set by domain') >>> print(field_latitude.get_property('test')) set by domain @@ -1831,7 +1864,7 @@ construct ` for accessing its data. :caption: *Get the Bounds instance of a coordinate construct and inspect its data.* - >>> lon = t.constructs('grid_longitude').value() + >>> lon = t.construct('grid_longitude') >>> bounds = lon.bounds >>> bounds @@ -2031,7 +2064,7 @@ A coordinate reference construct contains :caption: *Select the vertical coordinate system construct and inspect its coordinate constructs.* - >>> crs = t.constructs('standard_name:atmosphere_hybrid_height_coordinate').value() + >>> crs = t.construct('standard_name:atmosphere_hybrid_height_coordinate') >>> crs >>> crs.dump() @@ -2108,7 +2141,7 @@ methods of the cell method construct. :caption: *Get the domain axes constructs to which the cell method construct applies, and the method and other properties.* - >>> cm = t.constructs('method:mean').value() + >>> cm = t.construct('method:mean') >>> cm ) >>> cm.get_axes() @@ -2772,8 +2805,7 @@ define its domain. :caption: *Create an independent field construct from the "surface altitude" metadata construct.* - >>> key = tas.construct_key('surface_altitude') - >>> orog = tas.convert(key) + >>> orog = tas.convert('surface_altitude') >>> print(orog) Field: surface_altitude ----------------------- @@ -2795,7 +2827,7 @@ constructs. altitude" metadata construct, but without a complete domain.* - >>> orog1 = tas.convert(key, full_domain=False) + >>> orog1 = tas.convert('surface_altitude', full_domain=False) >>> print(orog1) Field: surface_altitude ----------------------- @@ -2906,7 +2938,7 @@ Metadata constructs may be copied individually in the same manner: .. code-block:: python :caption: *Copy a metadata construct.* - >>> orog = t.constructs('surface_altitude').value().copy() + >>> orog = t.construct('surface_altitude').copy() Arrays within `Data` instances are copied with a `copy-on-write `_ technique. This means @@ -3023,7 +3055,7 @@ Metadata constructs may also be tested for equality: :caption: *Metadata constructs also have an equals method, that behaves in a similar manner.* - >>> orog = t.constructs('surface_altitude').value() + >>> orog = t.construct('surface_altitude') >>> orog.equals(orog.copy()) True @@ -3056,9 +3088,9 @@ filters to a `Constructs` instance: >>> print(t.constructs.filter_by_ncvar('b')) Constructs: {'domainancillary1': } - >>> t.constructs('ncvar%x').value() + >>> t.construct('ncvar%x') - >>> t.constructs('ncdim%x') + >>> t.construct('ncdim%x') Each construct has methods to access the netCDF elements which it @@ -3169,7 +3201,7 @@ Method Description >>> q.nc_set_variable('humidity') >>> q.nc_get_variable() 'humidity' - >>> q.constructs('latitude').value().nc_get_variable() + >>> q.construct('latitude').nc_get_variable() 'lat' The complete collection of netCDF interface methods is: @@ -3618,8 +3650,7 @@ of the field construct. : longitude(8) = [22.5, ..., 337.5] degrees_east : time(1) = [2019-01-01 00:00:00] - >>> key = q.construct_key('time') - >>> axes = q.get_data_axes(key) + >>> axes = q.get_data_axes('time') >>> axes ('domainaxis2',) >>> q2 = q.insert_dimension(axis=axes[0]) @@ -3968,7 +3999,7 @@ is still created, but one without any metadata or data: : longitude(9) = [0.0, ..., 8.0] degrees Cell measures : measure:area (external variable: ncvar%areacella) - >>> area = u.constructs('measure:area').value() + >>> area = u.construct('measure:area') >>> area >>> area.nc_get_external() @@ -4003,7 +4034,7 @@ variable had been present in the parent dataset: Dimension coords: latitude(10) = [0.0, ..., 9.0] degrees : longitude(9) = [0.0, ..., 8.0] degrees Cell measures : cell_area(longitude(9), latitude(10)) = [[100000.5, ..., 100089.5]] m2 - >>> area = g.constructs('measure:area').value() + >>> area = g.construct('measure:area') >>> area >>> area.nc_get_external() From 371751c8d4dd707fc51ead013c6431360f9b8677 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 21 Mar 2022 11:42:21 +0000 Subject: [PATCH 3/8] package --- cfdm/domain.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cfdm/domain.py b/cfdm/domain.py index bd80c00f5e..09e26519eb 100644 --- a/cfdm/domain.py +++ b/cfdm/domain.py @@ -353,12 +353,12 @@ def apply_masking(self, inplace=False): **Examples** - >>> d = cfdm.example_field(0).domain + >>> d = {{package}}.example_field(0).domain >>> x = d.construct('longitude') - >>> x.data[[0, -1]] = cfdm.masked + >>> x.data[[0, -1]] = {{package}}.masked >>> print(x.data.array) [-- 67.5 112.5 157.5 202.5 247.5 292.5 --] - >>> cfdm.write(d, 'masked.nc') + >>> {{package}}.write(d, 'masked.nc') >>> no_mask = {{package}}.read('masked.nc', domain=True, mask=False)[0] >>> no_mask_x = no_mask.construct('longitude') >>> print(no_mask_x.data.array) @@ -393,7 +393,7 @@ def climatological_time_axes(self): **Examples** - >>> d = cfdm.example_field(0).domain + >>> d = {{package}}.example_field(0).domain >>> d.climatological_time_axes() set() From 063fd3d28157ec793d692158e16734d27ad38b9a Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 21 Mar 2022 14:19:54 +0000 Subject: [PATCH 4/8] construct access API --- Changelog.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Changelog.rst b/Changelog.rst index bcb54f4f7e..cd3614ab23 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,3 +1,22 @@ +Version 1.9.1.0 +--------------- + +**2022-??-??** + +* New method: `cfdm.Field.auxiliary_coordinate` +* New method: `cfdm.Field.cell_measure` +* New method: `cfdm.Field.cell_method` +* New method: `cfdm.Field.coordinate` +* New method: `cfdm.Field.coordinate_reference` +* New method: `cfdm.Field.dimension_coordinate` +* New method: `cfdm.Field.domain_ancillary` +* New method: `cfdm.Field.domain_axis` +* New method: `cfdm.Field.field_ancillary` +* New construct retrieval API methods + (https://github.com/NCAS-CMS/cfdm/issues/179) + +---- + Version 1.9.0.3 --------------- From 7d6bdc8759e23fbc886fc98ffbed8887e533990a Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 21 Mar 2022 14:20:08 +0000 Subject: [PATCH 5/8] tidy --- cfdm/docstring/docstring.py | 11 ++++-- cfdm/mixin/fielddomain.py | 77 +++++++++++++++++++++++++++++-------- docs/source/tutorial.rst | 40 +++++++++---------- 3 files changed, 88 insertions(+), 40 deletions(-) diff --git a/cfdm/docstring/docstring.py b/cfdm/docstring/docstring.py index 617d4654d6..23407b9738 100644 --- a/cfdm/docstring/docstring.py +++ b/cfdm/docstring/docstring.py @@ -314,10 +314,6 @@ and the construct itself. By default the construct itself is returned. If *key* is True then *item* is ignored.""", - # Returns constructs - "{{Returns constructs}}": """ - The selected constructs in a new `Constructs` object, - unless modified by any *filter_kwargs* parameters.""", # chunks subarrays "{{subarrays chunks: ``-1`` or sequence, optional}}": """chunks: ``-1`` or sequence, optional Define the subarray shapes. @@ -352,6 +348,13 @@ # ---------------------------------------------------------------- # Method description susbstitutions (4 levels of indentataion) # ---------------------------------------------------------------- + # Returns constructs + "{{Returns constructs}}": """ + The selected constructs in a new `Constructs` object, + unless modified by any *filter_kwargs* parameters.""", + # Returns construct + "{{Returns construct}}": """The selected construct, or its identifier if *key* is + True, or a tuple of both if *item* is True.""", # string value match "{{value match}}": """A value may be any object that can match via the ``==`` operator, or a `re.Pattern` object that matches diff --git a/cfdm/mixin/fielddomain.py b/cfdm/mixin/fielddomain.py index c6c63546bc..5a8615142f 100644 --- a/cfdm/mixin/fielddomain.py +++ b/cfdm/mixin/fielddomain.py @@ -457,6 +457,14 @@ def auxiliary_coordinate( **Examples** + >>> f = {{package}}.example_{{class_lower}}(1) + >>> f.auxiliary_coordinate('latitude') + + >>> f.auxiliary_coordinate('latitude', key=True) + 'auxiliarycoordinate0' + >>> f.auxiliary_coordinate('latitude', item=True) + ('auxiliarycoordinate0', ) + """ return self._construct( "auxiliary_coordinate", @@ -573,6 +581,16 @@ def coordinate( **Examples** + >>> f = {{package}}.example_{{class_lower}}(1) + >>> f.coordinate('latitude') + + >>> f.coordinate('grid_latitude') + + >>> f.coordinate('grid_latitude', key=True) + 'dimensioncoordinate1' + >>> f.coordinate('grid_latitude', item=True) + ('dimensioncoordinate1', ) + """ return self._construct( "coordinate", @@ -693,6 +711,15 @@ def coordinate_reference( **Examples** + >>> f = {{package}}.example_{{class_lower}}(1) + >>> f.coordinate_reference('grid_mapping_name:rotated_latitude_longitude') + + >>> f.coordinate_reference('grid_mapping_name:rotated_latitude_longitude', key=True) + 'coordinatereference1' + >>> f.coordinate_reference('grid_mapping_name:rotated_latitude_longitude', item=True) + ('coordinatereference1', + ) + """ return self._construct( "coordinate_reference", @@ -887,6 +914,14 @@ def dimension_coordinate( **Examples** + >>> f = {{package}}.example_{{class_lower}}(1) + >>> f.dimension_coordinate('grid_latitude') + + >>> f.coordinate('grid_latitude', key=True) + 'dimensioncoordinate1' + >>> f.coordinate('grid_latitude', item=True) + ('dimensioncoordinate1', ) + """ return self._construct( "dimension_coordinate", @@ -1015,6 +1050,14 @@ def domain_axis( **Examples** + >>> f = {{package}}.example_{{class_lower}}(1) + >>> f.domain_axis('domainaxis0') + + >>> f.domain_axis('domainaxis0', key=True) + 'domainaxis0' + >>> f.domain_axis('domainaxis0', item=True) + ('domainaxis0', ) + """ return self._construct( "domain_axis", @@ -1125,6 +1168,14 @@ def domain_ancillary( **Examples** + >>> f = {{package}}.example_{{class_lower}}(1) + >>> f.domain_ancillary('surface_altitude') + + >>> f.domain_ancillary('surface_altitude', key=True) + 'domainancillary2' + >>> f.domain_ancillary('surface_altitude', item=True) + ('domainancillary2', ) + """ return self._construct( "domain_ancillary", @@ -1206,9 +1257,9 @@ def cell_measure( :Parameters: identity: optional - Select dimension coordinate constructs that have an - identity, defined by their `!identities` methods, that - matches any of the given values. + Select cell measure constructs that have an identity, + defined by their `!identities` methods, that matches + any of the given values. Additionally, the values are matched against construct identifiers, with or without the ``'key%'`` prefix. @@ -1238,6 +1289,14 @@ def cell_measure( **Examples** + >>> f = {{package}}.example_{{class_lower}}(1) + >>> f.cell_measure() + + >>> f.cell_measure('measure:area', key=True) + 'cellmeasure0' + >>> f.cell_measure('measure:area', item=True) + ('cellmeasure0', ) + """ return self._construct( "cell_measure", @@ -1869,15 +1928,3 @@ def has_geometry(self): return True return False - - # def set_construct(self, construct, key=None, axes=None, copy=True): - # """TODO - # - # .. versionadded:: (cfdm) 1.7.0 - # - # .. seealso:: `constructs`, `del_construct`, `get_construct`, - # `set_data_axes` - # - # """ - # key = super().set_construct(construct, key=key, axes=axes, copy=copy) - # construct = self.constructs[key] diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index c4506cfe54..619265c8c8 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -1564,38 +1564,36 @@ of the chosen type. >>> t.auxiliary_coordinate('latitude', item=True) ('auxiliarycoordinate0', ) -The `~Field.construct` method of the field construct and the -`~Constructs.value` method of the `Constructs` instance will raise an -exception of there is not a unique metadata construct to return, but -this may be replaced with returning a default value or raising a -customised exception: +All of these techniques will raise an exception of there is not a +unique metadata construct to return, but this may be replaced with +returning a default value or raising a customised exception: .. code-block:: python :caption: *By default an exception is raised if there is not a unique construct that meets the criteria. Alternatively, the value of the "default" parameter is returned.* - >>> t.construct('measure:volume') # Raises Exception + >>> t.cell_measure('measure:volume') # Raises Exception Traceback (most recent call last): ... ValueError: Can't return zero constructs - >>> t.construct('measure:volume', default=False) + >>> t.cell_measure('measure:volume', default=False) False - >>> t.construct('measure:volume', default=Exception("my error")) # Raises Exception + >>> t.cell_measure('measure:volume', default=Exception("my error")) # Raises Exception Traceback (most recent call last): ... Exception: my error - >>> c = t.constructs.filter_by_measure("volume") + >>> c = t.cell_measures.filter_by_measure("volume") >>> len(c) 0 >>> d = t.constructs("units=degrees") >>> len(d) 2 - >>> t.construct("units=degrees") # Raises Exception + >>> t.coordinate("units=degrees") # Raises Exception Traceback (most recent call last): ... ValueError: Field.construct() can't return 2 constructs - >>> print(t.construct("units=degrees", default=None)) + >>> print(t.coordinate("units=degrees", default=None)) None .. _Metadata-construct-properties: @@ -1778,8 +1776,8 @@ constructs contained in the field construct. and show that this change appears in the same metadata data construct of the parent field, and vice versa.* - >>> domain_latitude = t.domain.construct('latitude') - >>> field_latitude = t.construct('latitude') + >>> domain_latitude = t.domain.coordinate('latitude') + >>> field_latitude = t.coordinate('latitude') >>> domain_latitude.set_property('test', 'set by domain') >>> print(field_latitude.get_property('test')) set by domain @@ -1864,7 +1862,7 @@ construct ` for accessing its data. :caption: *Get the Bounds instance of a coordinate construct and inspect its data.* - >>> lon = t.construct('grid_longitude') + >>> lon = t.coordinate('grid_longitude') >>> bounds = lon.bounds >>> bounds @@ -1930,7 +1928,7 @@ This is illustrated with the file ``geometry.nc`` (found in the : altitude(cf_role=timeseries_id(2)) = [5000.0, 20.0] m : cf_role=timeseries_id(cf_role=timeseries_id(2)) = [b'x1', b'y2'] Coord references: grid_mapping_name:latitude_longitude - >>> lon = f.construct('longitude') + >>> lon = f.coordinate('longitude') >>> lon.dump() Auxiliary coordinate: longitude standard_name = 'longitude' @@ -2064,7 +2062,7 @@ A coordinate reference construct contains :caption: *Select the vertical coordinate system construct and inspect its coordinate constructs.* - >>> crs = t.construct('standard_name:atmosphere_hybrid_height_coordinate') + >>> crs = t.coordinate_reference('standard_name:atmosphere_hybrid_height_coordinate') >>> crs >>> crs.dump() @@ -2141,7 +2139,7 @@ methods of the cell method construct. :caption: *Get the domain axes constructs to which the cell method construct applies, and the method and other properties.* - >>> cm = t.construct('method:mean') + >>> cm = t.cell_method('method:mean') >>> cm ) >>> cm.get_axes() @@ -2938,7 +2936,7 @@ Metadata constructs may be copied individually in the same manner: .. code-block:: python :caption: *Copy a metadata construct.* - >>> orog = t.construct('surface_altitude').copy() + >>> orog = t.domain_ancillary('surface_altitude').copy() Arrays within `Data` instances are copied with a `copy-on-write `_ technique. This means @@ -3055,7 +3053,7 @@ Metadata constructs may also be tested for equality: :caption: *Metadata constructs also have an equals method, that behaves in a similar manner.* - >>> orog = t.construct('surface_altitude') + >>> orog = t.domain_ancillary('surface_altitude') >>> orog.equals(orog.copy()) True @@ -3999,7 +3997,7 @@ is still created, but one without any metadata or data: : longitude(9) = [0.0, ..., 8.0] degrees Cell measures : measure:area (external variable: ncvar%areacella) - >>> area = u.construct('measure:area') + >>> area = u.cell_measure('measure:area') >>> area >>> area.nc_get_external() @@ -4034,7 +4032,7 @@ variable had been present in the parent dataset: Dimension coords: latitude(10) = [0.0, ..., 9.0] degrees : longitude(9) = [0.0, ..., 8.0] degrees Cell measures : cell_area(longitude(9), latitude(10)) = [[100000.5, ..., 100089.5]] m2 - >>> area = g.construct('measure:area') + >>> area = g.cell_measure('measure:area') >>> area >>> area.nc_get_external() From a878415796422c964393532e3db57835d99f0429 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Wed, 6 Apr 2022 15:56:11 +0100 Subject: [PATCH 6/8] construct access --- cfdm/domain.py | 85 +++++++++++++++++++++++++++++++++++++--- cfdm/field.py | 2 +- cfdm/test/test_Domain.py | 11 ++++++ docs/source/tutorial.py | 32 +++++++-------- docs/source/tutorial.rst | 2 +- test_tutorial_code | 2 +- 6 files changed, 109 insertions(+), 25 deletions(-) diff --git a/cfdm/domain.py b/cfdm/domain.py index 09e26519eb..f0d9a7c3f6 100644 --- a/cfdm/domain.py +++ b/cfdm/domain.py @@ -353,7 +353,7 @@ def apply_masking(self, inplace=False): **Examples** - >>> d = {{package}}.example_field(0).domain + >>> d = {{package}}.example_domain(0) >>> x = d.construct('longitude') >>> x.data[[0, -1]] = {{package}}.masked >>> print(x.data.array) @@ -393,7 +393,7 @@ def climatological_time_axes(self): **Examples** - >>> d = {{package}}.example_field(0).domain + >>> d = {{package}}.example_domain(0) >>> d.climatological_time_axes() set() @@ -437,7 +437,7 @@ def creation_commands( .. seealso:: `set_construct`, `{{package}}.Data.creation_commands`, - `{{package}}.example_field` + `{{package}}.example_domain` :Parameters: @@ -457,8 +457,7 @@ def creation_commands( **Examples** - >>> f = {{package}}.example_field(0) - >>> d = f.domain + >>> f = {{package}}.example_domain(0) >>> print(d.creation_commands()) # # domain: @@ -804,6 +803,80 @@ def dump( return "\n".join(string) + def get_data_axes(self, *identity, default=ValueError(), **filter_kwargs): + """Gets the keys of the axes spanned by the construct data. + + Specifically, returns the keys of the domain axis constructs + spanned by the data of a metadata construct. + + .. versionadded:: (cfdm) 1.7.0 + + .. seealso:: `del_data_axes`, `set_data_axes` + + :Parameters: + + identity, filter_kwargs: optional + Select the unique construct returned by + ``d.construct(*identity, **filter_kwargs)``. See + `construct` for details. + + .. versionadded:: (cfdm) 1.9.1.0 + + default: optional + Return the value of the *default* parameter if the + data axes have not been set. + + {{default Exception}} + + {{filter_kwargs: optional}} + + .. versionadded:: (cfdm) 1.9.1.0 + + :Returns: + + `tuple` + The keys of the domain axis constructs spanned by the + data. + + **Examples** + + >>> d = {{package}}.example_domain(0) + >>> d.get_data_axes('latitude') + ('domainaxis0',) + >>> d.get_data_axes('time') + ('domainaxis2',) + >>> d.has_data_axes() + True + >>> d.del_data_axes() + ('domainaxis0', 'domainaxis1') + >>> d.has_data_axes() + False + >>> d.get_data_axes(default='no axes') + 'no axes' + + """ + key = self.construct( + *identity, key=True, default=None, **filter_kwargs + ) + if key is None: + if default is None: + return default + + return self._default( + default, "Can't get axes for non-existent construct" + ) + + axes = super().get_data_axes(key, default=None) + if axes is None: + if default is None: + return default + + return self._default( + default, f"Construct {key!r} has not had axes set" + ) + + return axes + def get_filenames(self): """Return the file names containing the metadata construct data. @@ -816,7 +889,7 @@ def get_filenames(self): **Examples** - >>> d = {{package}}.example_field(0).domain + >>> d = {{package}}.example_domain(0) >>> {{package}}.write(d, 'temp_file.nc') >>> e = {{package}}.read('temp_file.nc', domain=True)[0] >>> e.get_filenames() diff --git a/cfdm/field.py b/cfdm/field.py index 9546b6f190..adae514d77 100644 --- a/cfdm/field.py +++ b/cfdm/field.py @@ -1800,7 +1800,7 @@ def get_data_axes(self, *identity, default=ValueError(), **filter_kwargs): return default return self._default( - default, "Can't get axes for non-existent construct" + default, f"Construct {key!r} has not had axes set" ) return axes diff --git a/cfdm/test/test_Domain.py b/cfdm/test/test_Domain.py index 59b3ed1bc3..4aa9ea0005 100644 --- a/cfdm/test/test_Domain.py +++ b/cfdm/test/test_Domain.py @@ -167,6 +167,17 @@ def test_Domain_has_bounds(self): """Test that Domain instances do not have cell bounds.""" self.assertFalse(self.d.has_bounds()) + def test_Domain_get_data_axes(self): + """Test Domain.get_data_axes.""" + d = cfdm.example_domain(0) + self.assertEqual(d.get_data_axes("latitude"), ("domainaxis0",)) + + with self.assertRaises(ValueError): + d.get_data_axes(None) + + with self.assertRaises(ValueError): + d.get_data_axes("domainaxis0") + if __name__ == "__main__": print("Run date:", datetime.datetime.now()) diff --git a/docs/source/tutorial.py b/docs/source/tutorial.py index c74494c999..9b05b61b4f 100644 --- a/docs/source/tutorial.py +++ b/docs/source/tutorial.py @@ -165,23 +165,23 @@ t.auxiliary_coordinate('latitude', key=True) t.auxiliary_coordinate('latitude', item=True) try: - t.construct('measure:volume') # Raises Exception + t.cell_measure('measure:volume') # Raises Exception except: pass -t.construct('measure:volume', default=False) +t.cell_measure('measure:volume', default=False) try: - t.construct('measure:volume', default=Exception("my error")) # Raises Exception + t.cell_measure('measure:volume', default=Exception("my error")) # Raises Exception except: pass -c = t.constructs.filter_by_measure("volume") +c = t.cell_measures().filter_by_measure("volume") len(c) d = t.constructs("units=degrees") len(d) try: - t.construct("units=degrees") # Raises Exception + t.coordinate("units=degrees") # Raises Exception except: pass -print(t.construct("units=degrees", default=None)) +print(t.coordinate("units=degrees", default=None)) lon = q.construct('longitude') lon lon.set_property('long_name', 'Longitude') @@ -209,8 +209,8 @@ domain print(domain) description = domain.dump(display=False) -domain_latitude = t.domain.construct('latitude') -field_latitude = t.construct('latitude') +domain_latitude = t.domain.coordinate('latitude') +field_latitude = t.coordinate('latitude') domain_latitude.set_property('test', 'set by domain') print(field_latitude.get_property('test')) field_latitude.set_property('test', 'set by field') @@ -222,7 +222,7 @@ d d.get_size() print(t.coordinates()) -lon = t.construct('grid_longitude') +lon = t.coordinate('grid_longitude') bounds = lon.bounds bounds bounds.data @@ -231,7 +231,7 @@ bounds.properties() f = cfdm.read('geometry.nc')[0] print(f) -lon = f.construct('longitude') +lon = f.coordinate('longitude') lon.dump() lon.get_geometry() print(lon.bounds.data.array) @@ -241,7 +241,7 @@ bounds = a.bounds bounds print(bounds.data.array) -crs = t.construct('standard_name:atmosphere_hybrid_height_coordinate') +crs = t.coordinate_reference('standard_name:atmosphere_hybrid_height_coordinate') crs crs.dump() crs.coordinates() @@ -251,7 +251,7 @@ crs.coordinate_conversion.parameters() crs.coordinate_conversion.domain_ancillaries() print(t.cell_methods()) -cm = t.construct('method:mean') +cm = t.cell_method('method:mean') cm cm.get_axes() cm.get_method() @@ -557,7 +557,7 @@ t.constructs('grid_latitude') import copy u = copy.deepcopy(t) -orog = t.construct('surface_altitude').copy() +orog = t.domain_ancillary('surface_altitude').copy() t.equals(t) t.equals(t.copy()) t.equals(t[...]) @@ -573,7 +573,7 @@ with cfdm.atol(1e-5): print(cfdm.atol()) print(cfdm.atol()) -orog = t.construct('surface_altitude') +orog = t.domain_ancillary('surface_altitude') orog.equals(orog.copy()) print(t.constructs.filter_by_ncvar('b')) t.construct('ncvar%x') @@ -638,7 +638,7 @@ f.equals(g) u = cfdm.read('parent.nc')[0] print(u) -area = u.construct('measure:area') +area = u.cell_measure('measure:area') area area.nc_get_external() area.nc_get_variable() @@ -646,7 +646,7 @@ area.has_data() g = cfdm.read('parent.nc', external='external.nc')[0] print(g) -area = g.construct('measure:area') +area = g.cell_measure('measure:area') area area.nc_get_external() area.nc_get_variable() diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index 982bccffaa..09dc0c5c36 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -1583,7 +1583,7 @@ returning a default value or raising a customised exception: Traceback (most recent call last): ... Exception: my error - >>> c = t.cell_measures.filter_by_measure("volume") + >>> c = t.cell_measures().filter_by_measure("volume") >>> len(c) 0 >>> d = t.constructs("units=degrees") diff --git a/test_tutorial_code b/test_tutorial_code index 82ba210563..87066fab6e 100755 --- a/test_tutorial_code +++ b/test_tutorial_code @@ -11,7 +11,7 @@ # -------------------------------------------------------------------- set -x -echo $PYTHONPATH +echo PYTHONPATH=$PYTHONPATH d=$PWD cd docs/source From fc54aa1afa130843d1d0a314afba9234c100c8db Mon Sep 17 00:00:00 2001 From: David Hassell Date: Thu, 7 Apr 2022 08:56:38 +0100 Subject: [PATCH 7/8] Clarity and typos Co-authored-by: Sadie L. Bartholomew --- cfdm/docstring/docstring.py | 4 ++-- cfdm/field.py | 2 +- docs/source/tutorial.rst | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cfdm/docstring/docstring.py b/cfdm/docstring/docstring.py index 23407b9738..154657adc1 100644 --- a/cfdm/docstring/docstring.py +++ b/cfdm/docstring/docstring.py @@ -310,8 +310,8 @@ returned.""", # item: `bool`, optional "{{item: `bool`, optional}}": """item: `bool`, optional - If True then return the selected construct identifier - and the construct itself. By default the construct + If True then return as a tuple the selected construct identifier + and the construct itself. By default only the construct itself is returned. If *key* is True then *item* is ignored.""", # chunks subarrays diff --git a/cfdm/field.py b/cfdm/field.py index adae514d77..2daabed62c 100644 --- a/cfdm/field.py +++ b/cfdm/field.py @@ -1739,7 +1739,7 @@ def get_data_axes(self, *identity, default=ValueError(), **filter_kwargs): `construct` for details. If neither *identity* nor *filter_kwargs* are set then - the domain of the field constructs's data are + the domain of the field construct's data are returned. .. versionadded:: (cfdm) 1.9.1.0 diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index 09dc0c5c36..71376f9845 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -1525,7 +1525,7 @@ construct key, by any of the following techniques: >>> c = t.constructs.get(key) -* with the `~Constructs.value` method of a `Constructs` instance, or +* with the `~Constructs.value` method of a `Constructs` instance. .. code-block:: python :caption: *Get the "latitude" metadata construct via its 'value' @@ -1564,7 +1564,7 @@ of the chosen type. >>> t.auxiliary_coordinate('latitude', item=True) ('auxiliarycoordinate0', ) -All of these techniques will raise an exception of there is not a +All of these techniques will raise an exception if there is not a unique metadata construct to return, but this may be replaced with returning a default value or raising a customised exception: From 19992019fcce16d932e2b73d7a934fbe6a1c58b5 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Thu, 7 Apr 2022 08:58:25 +0100 Subject: [PATCH 8/8] Clarity and typos Co-authored-by: Sadie L. Bartholomew --- cfdm/mixin/fielddomain.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cfdm/mixin/fielddomain.py b/cfdm/mixin/fielddomain.py index 25a5e6854f..e1d870983e 100644 --- a/cfdm/mixin/fielddomain.py +++ b/cfdm/mixin/fielddomain.py @@ -247,8 +247,8 @@ def _filter_interface( .. versionadded:: (cfdm) 1.9.1.0 default: optional - If *construct* is True then return the value of the - *default* parameter if there is no unique construct. + If *construct* is True and there is no unique construct + then return the value of the *default* parameter. {{default Exception}} @@ -1014,7 +1014,7 @@ def domain_axis( Additionally, the values are matched against construct identifiers, with or without the ``'key%'`` prefix. - Additionally, if for a given `value``, + Additionally, if for a given ``value``, ``f.coordinates(value, filter_by_naxes=(1,))`` returns 1-d coordinate constructs that all span the same domain axis construct then that domain axis construct