From 646c5782b92f289e1d7f2a872f69828bf7e4343b Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Thu, 21 Jan 2021 12:45:54 +0000 Subject: [PATCH 01/12] add abstract summary --- lib/iris/_representation.py | 283 ++++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 lib/iris/_representation.py diff --git a/lib/iris/_representation.py b/lib/iris/_representation.py new file mode 100644 index 0000000000..40609a92e5 --- /dev/null +++ b/lib/iris/_representation.py @@ -0,0 +1,283 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Provides objects describing cube summaries. +""" + +import iris.util + + +def sorted_axes(axes): + """ + Returns the axis names sorted alphabetically, with the exception that + 't', 'z', 'y', and, 'x' are sorted to the end. + """ + return sorted( + axes, + key=lambda name: ({"x": 4, "y": 3, "z": 2, "t": 1}.get(name, 0), name), + ) + + +class DimensionHeader: + def __init__(self, cube): + if cube.shape == (): + self.scalar = True + self.dim_names = [] + self.shape = [] + self.contents = ["scalar cube"] + else: + self.scalar = False + self.dim_names = [] + for dim in range(len(cube.shape)): + dim_coords = cube.coords( + contains_dimension=dim, dim_coords=True + ) + if dim_coords: + self.dim_names.append(dim_coords[0].name()) + else: + self.dim_names.append("-- ") + self.shape = list(cube.shape) + self.contents = [ + name + ": %d" % dim_len + for name, dim_len in zip(self.dim_names, self.shape) + ] + + +class FullHeader: + def __init__(self, cube, name_padding=35): + self.name = cube.name() + self.unit = cube.units + self.nameunit = "{name} / ({units})".format( + name=self.name, units=self.unit + ) + self.name_padding = name_padding + self.dimension_header = DimensionHeader(cube) + + +class CoordSummary: + def _summary_coord_extra(self, cube, coord): + # Returns the text needed to ensure this coordinate can be + # distinguished from all others with the same name. + extra = "" + similar_coords = cube.coords(coord.name()) + if len(similar_coords) > 1: + # Find all the attribute keys + keys = set() + for similar_coord in similar_coords: + keys.update(similar_coord.attributes.keys()) + # Look for any attributes that vary + vary = set() + attributes = {} + for key in keys: + for similar_coord in similar_coords: + if key not in similar_coord.attributes: + vary.add(key) + break + value = similar_coord.attributes[key] + if attributes.setdefault(key, value) != value: + vary.add(key) + break + keys = sorted(vary & set(coord.attributes.keys())) + bits = [ + "{}={!r}".format(key, coord.attributes[key]) for key in keys + ] + if bits: + extra = ", ".join(bits) + return extra + + +class VectorSummary(CoordSummary): + def __init__(self, cube, vector, iscoord): + vector_indent = 10 + extra_indent = 13 + self.name = iris.util.clip_string( + vector.name(), clip_length=70 - vector_indent + ) + dims = vector.cube_dims(cube) + self.dim_chars = [ + "x" if dim in dims else "-" for dim in range(len(cube.shape)) + ] + if iscoord: + extra = self._summary_coord_extra() + self.extra = iris.util.clip_string( + extra, clip_length=70 - extra_indent + ) + else: + self.extra = "" + + +class ScalarSummary(CoordSummary): + def __init__(self, coord): + extra_indent = 13 + self.name = coord.name() + if ( + coord.units in ["1", "no_unit", "unknown"] + or coord.units.is_time_reference() + ): + self.unit = "" + else: + self.unit = " {!s}".format(coord.units) + coord_cell = coord.cell(0) + if isinstance(coord_cell.point, str): + self.string_type = True + self.lines = [ + iris.util.clip_string(str(item)) + for item in coord_cell.point.split("\n") + ] + self.point = None + self.bound = None + self.content = "\n".join(self.lines) + else: + self.string_type = False + self.point = "{!s}".format(coord_cell.point) + coord_cell_cbound = coord_cell.bound + if coord_cell_cbound is not None: + self.bound = "({})".format( + ", ".join(str(val) for val in coord_cell_cbound) + ) + else: + self.bound = None + self.lines = None + self.content = "{}{}, bound={}{}".format( + self.point, self.unit, self.bound, self.unit + ) + extra = self._summary_coord_extra() + self.extra = iris.util.clip_string( + extra, clip_length=70 - extra_indent + ) + + +class Section: + def is_empty(self): + return self.contents == [] + + +class VectorSection(Section): + def __init__(self, title, vectors): + self.title = title + self.contents = [VectorSummary(vector) for vector in vectors] + + +class ScalarSection(Section): + def __init__(self, title, scalars): + self.title = title + self.contents = [ScalarSummary(scalar) for scalar in scalars] + + +class ScalarCMSection(Section): + def __init__(self, title, cell_measures): + self.title = title + self.contents = [cm.name() for cm in cell_measures] + + +class AttributeSection(Section): + def __init__(self, title, attributes): + self.title = title + self.names = [] + self.values = [] + self.contents = [] + for name, value in sorted(attributes.items()): + value = iris.util.clip_string(str(value)) + self.names.append(name) + self.values.append(value) + content = "{}: {}".format(name, value) + self.contents.append(content) + + +class CellMethodSection(Section): + def __init__(self, title, cell_methods): + self.title = title + self.contents = [str(cm) for cm in cell_methods] + + +class CubeSummary: + def __init__(self, cube, shorten=False, name_padding=35): + self.section_indent = 5 + self.item_indent = 10 + self.extra_indent = 13 + self.shorten = shorten + self.header = FullHeader(cube, name_padding) + + # Cache the derived coords so we can rely on consistent + # object IDs. + derived_coords = cube.derived_coords + # Determine the cube coordinates that are scalar (single-valued) + # AND non-dimensioned. + dim_coords = cube.dim_coords + aux_coords = cube.aux_coords + all_coords = dim_coords + aux_coords + derived_coords + scalar_coords = [ + coord + for coord in all_coords + if not cube.coord_dims(coord) and coord.shape == (1,) + ] + # Determine the cube coordinates that are not scalar BUT + # dimensioned. + scalar_coord_ids = set(map(id, scalar_coords)) + vector_dim_coords = [ + coord for coord in dim_coords if id(coord) not in scalar_coord_ids + ] + vector_aux_coords = [ + coord for coord in aux_coords if id(coord) not in scalar_coord_ids + ] + vector_derived_coords = [ + coord + for coord in derived_coords + if id(coord) not in scalar_coord_ids + ] + + # cell measures + vector_cell_measures = [ + cm for cm in cube.cell_measures() if cm.shape != (1,) + ] + + # Ancillary Variables + vector_ancillary_variables = [av for av in cube.ancillary_variables()] + + # Sort scalar coordinates by name. + scalar_coords.sort(key=lambda coord: coord.name()) + # Sort vector coordinates by data dimension and name. + vector_dim_coords.sort( + key=lambda coord: (cube.coord_dims(coord), coord.name()) + ) + vector_aux_coords.sort( + key=lambda coord: (cube.coord_dims(coord), coord.name()) + ) + vector_derived_coords.sort( + key=lambda coord: (cube.coord_dims(coord), coord.name()) + ) + scalar_cell_measures = [ + cm for cm in cube.cell_measures() if cm.shape == (1,) + ] + + self.dim_coord_section = VectorSection( + "Dimension coordinates:", vector_dim_coords + ) + self.aux_coord_section = VectorSection( + "Auxiliary coordinates:", vector_aux_coords + ) + self.derived_coord_section = VectorSection( + "Derived coordinates:", vector_derived_coords + ) + self.cell_measure_section = VectorSection( + "Cell Measures:", vector_cell_measures + ) + self.ancillary_variable_section = VectorSection( + "Ancillary variables:", vector_ancillary_variables + ) + + self.scalar_section = ScalarSection( + "Scalar Coordinates:", scalar_coords + ) + self.scalar_cm_section = ScalarCMSection( + "Scalar cell measures:", scalar_cell_measures + ) + self.attribute_section = AttributeSection( + "Attributes:", cube.attributes + ) + self.cell_method_section = CellMethodSection( + "Cell methods:", cube.cell_methods + ) From a6098fa3982e42e9c6cf68943eb721233898e501 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Thu, 21 Jan 2021 16:08:51 +0000 Subject: [PATCH 02/12] bug fix --- lib/iris/_representation.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/iris/_representation.py b/lib/iris/_representation.py index 40609a92e5..0f042b5bfe 100644 --- a/lib/iris/_representation.py +++ b/lib/iris/_representation.py @@ -156,9 +156,9 @@ def is_empty(self): class VectorSection(Section): - def __init__(self, title, vectors): + def __init__(self, title, cube, vectors, iscoord): self.title = title - self.contents = [VectorSummary(vector) for vector in vectors] + self.contents = [VectorSummary(vector, cube, iscoord) for vector in vectors] class ScalarSection(Section): @@ -254,19 +254,19 @@ def __init__(self, cube, shorten=False, name_padding=35): ] self.dim_coord_section = VectorSection( - "Dimension coordinates:", vector_dim_coords + "Dimension coordinates:", cube, vector_dim_coords, True ) self.aux_coord_section = VectorSection( - "Auxiliary coordinates:", vector_aux_coords + "Auxiliary coordinates:", cube, vector_aux_coords, True ) self.derived_coord_section = VectorSection( - "Derived coordinates:", vector_derived_coords + "Derived coordinates:", cube, vector_derived_coords, True ) self.cell_measure_section = VectorSection( - "Cell Measures:", vector_cell_measures + "Cell Measures:", cube, vector_cell_measures, False ) self.ancillary_variable_section = VectorSection( - "Ancillary variables:", vector_ancillary_variables + "Ancillary variables:", cube, vector_ancillary_variables, False ) self.scalar_section = ScalarSection( From e7ff4a78a63ca1e49dcea1ac90de1d60d3325447 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Fri, 22 Jan 2021 14:02:29 +0000 Subject: [PATCH 03/12] bug fix with tests --- lib/iris/_representation.py | 6 +- .../tests/unit/representation/__init__.py | 6 ++ .../representation/test_representation.py | 64 +++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 lib/iris/tests/unit/representation/__init__.py create mode 100644 lib/iris/tests/unit/representation/test_representation.py diff --git a/lib/iris/_representation.py b/lib/iris/_representation.py index 0f042b5bfe..a34eced1a1 100644 --- a/lib/iris/_representation.py +++ b/lib/iris/_representation.py @@ -101,7 +101,7 @@ def __init__(self, cube, vector, iscoord): "x" if dim in dims else "-" for dim in range(len(cube.shape)) ] if iscoord: - extra = self._summary_coord_extra() + extra = self._summary_coord_extra(cube, vector) self.extra = iris.util.clip_string( extra, clip_length=70 - extra_indent ) @@ -158,7 +158,9 @@ def is_empty(self): class VectorSection(Section): def __init__(self, title, cube, vectors, iscoord): self.title = title - self.contents = [VectorSummary(vector, cube, iscoord) for vector in vectors] + self.contents = [ + VectorSummary(cube, vector, iscoord) for vector in vectors + ] class ScalarSection(Section): diff --git a/lib/iris/tests/unit/representation/__init__.py b/lib/iris/tests/unit/representation/__init__.py new file mode 100644 index 0000000000..e943ad149b --- /dev/null +++ b/lib/iris/tests/unit/representation/__init__.py @@ -0,0 +1,6 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the :mod:`iris._representation` module.""" diff --git a/lib/iris/tests/unit/representation/test_representation.py b/lib/iris/tests/unit/representation/test_representation.py new file mode 100644 index 0000000000..2075b531d1 --- /dev/null +++ b/lib/iris/tests/unit/representation/test_representation.py @@ -0,0 +1,64 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the :mod:`iris._representation` module.""" + +import numpy as np +import iris.tests as tests +import iris._representation +from iris.cube import Cube +from iris.coords import ( + DimCoord, + AuxCoord, + CellMeasure, + AncillaryVariable, + CellMethod, +) + + +def example_cube(): + cube = Cube( + np.arange(6).reshape([3, 2]), + standard_name="air_temperature", + long_name="screen_air_temp", + var_name="airtemp", + units="K", + ) + lat = DimCoord([0, 1, 2], standard_name="latitude", units="degrees") + cube.add_dim_coord(lat, 0) + return cube + + +class Test_CubeSummary(tests.IrisTest): + def setUp(self): + self.cube = example_cube() + + def test_header(self): + rep = iris._representation.CubeSummary(self.cube) + header_left = rep.header.nameunit + header_right = rep.header.dimension_header.contents + + self.assertEqual(header_left, "air_temperature / (K)") + self.assertEqual(header_right, ["latitude: 3", "-- : 2"]) + + def test_coord(self): + rep = iris._representation.CubeSummary(self.cube) + dim_section = rep.dim_coord_section + + self.assertEqual(len(dim_section.contents), 1) + + dim_summary = dim_section.contents[0] + + name = dim_summary.name + dim_chars = dim_summary.dim_chars + extra = dim_summary.extra + + self.assertEqual(name, "latitude") + self.assertEqual(dim_chars, ["x", "-"]) + self.assertEqual(extra, "") + + +if __name__ == "__main__": + tests.main() From 60c6e4d72799b3c1f907284cd351e27d4dc8fb8c Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Fri, 22 Jan 2021 14:45:22 +0000 Subject: [PATCH 04/12] fix scalar bug and add test --- lib/iris/_representation.py | 19 +++++----- .../representation/test_representation.py | 36 ++++++++++++++++++- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/lib/iris/_representation.py b/lib/iris/_representation.py index a34eced1a1..0ccc3f4cfc 100644 --- a/lib/iris/_representation.py +++ b/lib/iris/_representation.py @@ -110,7 +110,7 @@ def __init__(self, cube, vector, iscoord): class ScalarSummary(CoordSummary): - def __init__(self, coord): + def __init__(self, cube, coord): extra_indent = 13 self.name = coord.name() if ( @@ -132,19 +132,20 @@ def __init__(self, coord): self.content = "\n".join(self.lines) else: self.string_type = False + self.lines = None self.point = "{!s}".format(coord_cell.point) coord_cell_cbound = coord_cell.bound if coord_cell_cbound is not None: self.bound = "({})".format( ", ".join(str(val) for val in coord_cell_cbound) ) + self.content = "{}{}, bound={}{}".format( + self.point, self.unit, self.bound, self.unit + ) else: self.bound = None - self.lines = None - self.content = "{}{}, bound={}{}".format( - self.point, self.unit, self.bound, self.unit - ) - extra = self._summary_coord_extra() + self.content = "{}{}".format(self.point, self.unit) + extra = self._summary_coord_extra(cube, coord) self.extra = iris.util.clip_string( extra, clip_length=70 - extra_indent ) @@ -164,9 +165,9 @@ def __init__(self, title, cube, vectors, iscoord): class ScalarSection(Section): - def __init__(self, title, scalars): + def __init__(self, title, cube, scalars): self.title = title - self.contents = [ScalarSummary(scalar) for scalar in scalars] + self.contents = [ScalarSummary(cube, scalar) for scalar in scalars] class ScalarCMSection(Section): @@ -272,7 +273,7 @@ def __init__(self, cube, shorten=False, name_padding=35): ) self.scalar_section = ScalarSection( - "Scalar Coordinates:", scalar_coords + "Scalar Coordinates:", cube, scalar_coords ) self.scalar_cm_section = ScalarCMSection( "Scalar cell measures:", scalar_cell_measures diff --git a/lib/iris/tests/unit/representation/test_representation.py b/lib/iris/tests/unit/representation/test_representation.py index 2075b531d1..87191ed4d2 100644 --- a/lib/iris/tests/unit/representation/test_representation.py +++ b/lib/iris/tests/unit/representation/test_representation.py @@ -43,7 +43,7 @@ def test_header(self): self.assertEqual(header_left, "air_temperature / (K)") self.assertEqual(header_right, ["latitude: 3", "-- : 2"]) - def test_coord(self): + def test_vector_coord(self): rep = iris._representation.CubeSummary(self.cube) dim_section = rep.dim_coord_section @@ -59,6 +59,40 @@ def test_coord(self): self.assertEqual(dim_chars, ["x", "-"]) self.assertEqual(extra, "") + def test_scalar_coord(self): + cube = self.cube + scalar_coord_no_bounds = AuxCoord([10], long_name="bar", units="K") + scalar_coord_with_bounds = AuxCoord( + [10], long_name="foo", units="K", bounds=[(5, 15)] + ) + scalar_coord_text = AuxCoord( + ["a\nb\nc"], long_name="foo", attributes={"key": "value"} + ) + cube.add_aux_coord(scalar_coord_no_bounds) + cube.add_aux_coord(scalar_coord_with_bounds) + cube.add_aux_coord(scalar_coord_text) + rep = iris._representation.CubeSummary(cube) + + scalar_section = rep.scalar_section + + self.assertEqual(len(scalar_section.contents), 3) + + no_bounds_summary = scalar_section.contents[0] + bounds_summary = scalar_section.contents[1] + text_summary = scalar_section.contents[2] + + self.assertEqual(no_bounds_summary.name, "bar") + self.assertEqual(no_bounds_summary.content, "10 K") + self.assertEqual(no_bounds_summary.extra, "") + + self.assertEqual(bounds_summary.name, "foo") + self.assertEqual(bounds_summary.content, "10 K, bound=(5, 15) K") + self.assertEqual(bounds_summary.extra, "") + + self.assertEqual(text_summary.name, "foo") + self.assertEqual(text_summary.content, "a\nb\nc") + self.assertEqual(text_summary.extra, "key='value'") + if __name__ == "__main__": tests.main() From 611ce0665293e7aa15330f89a8d04b35d0bcdcd3 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Fri, 22 Jan 2021 16:08:22 +0000 Subject: [PATCH 05/12] apply suggestions from PR --- lib/iris/_representation.py | 48 ++++++++++--------- .../representation/test_representation.py | 4 +- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/lib/iris/_representation.py b/lib/iris/_representation.py index 0ccc3f4cfc..8169a73483 100644 --- a/lib/iris/_representation.py +++ b/lib/iris/_representation.py @@ -256,31 +256,33 @@ def __init__(self, cube, shorten=False, name_padding=35): cm for cm in cube.cell_measures() if cm.shape == (1,) ] - self.dim_coord_section = VectorSection( - "Dimension coordinates:", cube, vector_dim_coords, True - ) - self.aux_coord_section = VectorSection( - "Auxiliary coordinates:", cube, vector_aux_coords, True - ) - self.derived_coord_section = VectorSection( - "Derived coordinates:", cube, vector_derived_coords, True - ) - self.cell_measure_section = VectorSection( - "Cell Measures:", cube, vector_cell_measures, False - ) - self.ancillary_variable_section = VectorSection( - "Ancillary variables:", cube, vector_ancillary_variables, False - ) + self.vector_sections = {} - self.scalar_section = ScalarSection( - "Scalar Coordinates:", cube, scalar_coords + def add_vector_section(title, contents, iscoord=True): + self.vector_sections[title] = VectorSection( + title, cube, contents, iscoord + ) + + add_vector_section("Dimension coordinates:", vector_dim_coords) + add_vector_section("Auxiliary coordinates:", vector_aux_coords) + add_vector_section("Derived coordinates:", vector_derived_coords) + add_vector_section("Cell Measures:", vector_cell_measures, False) + add_vector_section( + "Ancillary Variables:", vector_ancillary_variables, False ) - self.scalar_cm_section = ScalarCMSection( - "Scalar cell measures:", scalar_cell_measures + + self.scalar_sections = {} + + def add_scalar_section(section_class, title, *args): + self.scalar_sections[title] = section_class(title, *args) + + add_scalar_section( + ScalarSection, "Scalar Coordinates:", cube, scalar_coords ) - self.attribute_section = AttributeSection( - "Attributes:", cube.attributes + add_scalar_section( + ScalarCMSection, "Scalar cell measures:", scalar_cell_measures ) - self.cell_method_section = CellMethodSection( - "Cell methods:", cube.cell_methods + add_scalar_section(AttributeSection, "Attributes:", cube.attributes) + add_scalar_section( + CellMethodSection, "Cell methods:", cube.cell_methods ) diff --git a/lib/iris/tests/unit/representation/test_representation.py b/lib/iris/tests/unit/representation/test_representation.py index 87191ed4d2..c6b234f076 100644 --- a/lib/iris/tests/unit/representation/test_representation.py +++ b/lib/iris/tests/unit/representation/test_representation.py @@ -45,7 +45,7 @@ def test_header(self): def test_vector_coord(self): rep = iris._representation.CubeSummary(self.cube) - dim_section = rep.dim_coord_section + dim_section = rep.vector_sections["Dimension coordinates:"] self.assertEqual(len(dim_section.contents), 1) @@ -73,7 +73,7 @@ def test_scalar_coord(self): cube.add_aux_coord(scalar_coord_text) rep = iris._representation.CubeSummary(cube) - scalar_section = rep.scalar_section + scalar_section = rep.scalar_sections["Scalar Coordinates:"] self.assertEqual(len(scalar_section.contents), 3) From 18e6fa141d6999f3de42722c5cd19e9b80a8588a Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Fri, 22 Jan 2021 17:04:21 +0000 Subject: [PATCH 06/12] add test coverage --- .../representation/test_representation.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/iris/tests/unit/representation/test_representation.py b/lib/iris/tests/unit/representation/test_representation.py index c6b234f076..88eb7a34cb 100644 --- a/lib/iris/tests/unit/representation/test_representation.py +++ b/lib/iris/tests/unit/representation/test_representation.py @@ -93,6 +93,32 @@ def test_scalar_coord(self): self.assertEqual(text_summary.content, "a\nb\nc") self.assertEqual(text_summary.extra, "key='value'") + def test_cell_measure(self): + cube = self.cube + cell_measure = CellMeasure([1, 2, 3], long_name="foo") + cube.add_cell_measure(cell_measure, 0) + rep = iris._representation.CubeSummary(cube) + + cm_section = rep.vector_sections["Cell Measures:"] + self.assertEqual(len(cm_section.contents), 1) + + cm_summary = cm_section.contents[0] + self.assertEqual(cm_summary.name, "foo") + self.assertEqual(cm_summary.dim_chars, ["x", "-"]) + + def test_ancillary_variable(self): + cube = self.cube + cell_measure = AncillaryVariable([1, 2, 3], long_name="foo") + cube.add_ancillary_variable(cell_measure, 0) + rep = iris._representation.CubeSummary(cube) + + av_section = rep.vector_sections["Ancillary Variables:"] + self.assertEqual(len(av_section.contents), 1) + + av_summary = av_section.contents[0] + self.assertEqual(av_summary.name, "foo") + self.assertEqual(av_summary.dim_chars, ["x", "-"]) + if __name__ == "__main__": tests.main() From 5d504ab8cf42243ffdaf4831047fb87e5389e463 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Mon, 25 Jan 2021 09:49:43 +0000 Subject: [PATCH 07/12] add test coverage --- .../representation/test_representation.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/iris/tests/unit/representation/test_representation.py b/lib/iris/tests/unit/representation/test_representation.py index 88eb7a34cb..19c776be65 100644 --- a/lib/iris/tests/unit/representation/test_representation.py +++ b/lib/iris/tests/unit/representation/test_representation.py @@ -119,6 +119,31 @@ def test_ancillary_variable(self): self.assertEqual(av_summary.name, "foo") self.assertEqual(av_summary.dim_chars, ["x", "-"]) + def test_attributes(self): + cube = self.cube + cube.attributes = {"a": 1, "b": "two"} + rep = iris._representation.CubeSummary(cube) + + attribute_section = rep.scalar_sections["Attributes:"] + attribute_contents = attribute_section.contents + expected_contents = ["a: 1", "b: two"] + + self.assertEqual(attribute_contents, expected_contents) + + def test_cell_methods(self): + cube = self.cube + x = AuxCoord(1, long_name="x") + y = AuxCoord(1, long_name="y") + cell_method_xy = CellMethod("mean", [x, y]) + cell_method_x = CellMethod("mean", x) + cube.add_cell_method(cell_method_xy) + cube.add_cell_method(cell_method_x) + + rep = iris._representation.CubeSummary(cube) + cell_method_section = rep.scalar_sections["Cell methods:"] + expected_contents = ["mean: x, y", "mean: x"] + self.assertEqual(cell_method_section.contents, expected_contents) + if __name__ == "__main__": tests.main() From 1cb3bf74d21bde9c43bc535c90665e03d34e3112 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Thu, 4 Feb 2021 16:15:48 +0000 Subject: [PATCH 08/12] add tests, simplify clipping --- lib/iris/_representation.py | 18 +++------ .../representation/test_representation.py | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/lib/iris/_representation.py b/lib/iris/_representation.py index 8169a73483..fc980bd923 100644 --- a/lib/iris/_representation.py +++ b/lib/iris/_representation.py @@ -91,27 +91,20 @@ def _summary_coord_extra(self, cube, coord): class VectorSummary(CoordSummary): def __init__(self, cube, vector, iscoord): - vector_indent = 10 - extra_indent = 13 - self.name = iris.util.clip_string( - vector.name(), clip_length=70 - vector_indent - ) + self.name = iris.util.clip_string(vector.name()) dims = vector.cube_dims(cube) self.dim_chars = [ "x" if dim in dims else "-" for dim in range(len(cube.shape)) ] if iscoord: extra = self._summary_coord_extra(cube, vector) - self.extra = iris.util.clip_string( - extra, clip_length=70 - extra_indent - ) + self.extra = iris.util.clip_string(extra) else: self.extra = "" class ScalarSummary(CoordSummary): def __init__(self, cube, coord): - extra_indent = 13 self.name = coord.name() if ( coord.units in ["1", "no_unit", "unknown"] @@ -146,12 +139,12 @@ def __init__(self, cube, coord): self.bound = None self.content = "{}{}".format(self.point, self.unit) extra = self._summary_coord_extra(cube, coord) - self.extra = iris.util.clip_string( - extra, clip_length=70 - extra_indent - ) + self.extra = iris.util.clip_string(extra) class Section: + contents = [] + def is_empty(self): return self.contents == [] @@ -181,7 +174,6 @@ def __init__(self, title, attributes): self.title = title self.names = [] self.values = [] - self.contents = [] for name, value in sorted(attributes.items()): value = iris.util.clip_string(str(value)) self.names.append(name) diff --git a/lib/iris/tests/unit/representation/test_representation.py b/lib/iris/tests/unit/representation/test_representation.py index 19c776be65..212f454e70 100644 --- a/lib/iris/tests/unit/representation/test_representation.py +++ b/lib/iris/tests/unit/representation/test_representation.py @@ -43,11 +43,49 @@ def test_header(self): self.assertEqual(header_left, "air_temperature / (K)") self.assertEqual(header_right, ["latitude: 3", "-- : 2"]) + def test_blank_cube(self): + cube = Cube([1, 2]) + rep = iris._representation.CubeSummary(cube) + + self.assertEqual(rep.header.nameunit, "unknown / (unknown)") + self.assertEqual(rep.header.dimension_header.contents, ["-- : 2"]) + + expected_vector_sections = [ + "Dimension coordinates:", + "Auxiliary coordinates:", + "Derived coordinates:", + "Cell Measures:", + "Ancillary Variables:", + ] + self.assertEqual( + list(rep.vector_sections.keys()), expected_vector_sections + ) + for title in expected_vector_sections: + vector_section = rep.vector_sections[title] + self.assertEqual(vector_section.contents, []) + self.assertTrue(vector_section.is_empty()) + + expected_scalar_sections = [ + "Scalar Coordinates:", + "Scalar cell measures:", + "Attributes:", + "Cell methods:", + ] + + self.assertEqual( + list(rep.scalar_sections.keys()), expected_scalar_sections + ) + for title in expected_scalar_sections: + scalar_section = rep.scalar_sections[title] + self.assertEqual(scalar_section.contents, []) + self.assertTrue(scalar_section.is_empty()) + def test_vector_coord(self): rep = iris._representation.CubeSummary(self.cube) dim_section = rep.vector_sections["Dimension coordinates:"] self.assertEqual(len(dim_section.contents), 1) + self.assertFalse(dim_section.is_empty()) dim_summary = dim_section.contents[0] From ad2c68fc9d3b40278024c608e4132daa195a5cff Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Fri, 5 Feb 2021 09:46:01 +0000 Subject: [PATCH 09/12] clarify section name --- lib/iris/_representation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/iris/_representation.py b/lib/iris/_representation.py index fc980bd923..33ac3f920e 100644 --- a/lib/iris/_representation.py +++ b/lib/iris/_representation.py @@ -163,7 +163,7 @@ def __init__(self, title, cube, scalars): self.contents = [ScalarSummary(cube, scalar) for scalar in scalars] -class ScalarCMSection(Section): +class ScalarCellMeasureSection(Section): def __init__(self, title, cell_measures): self.title = title self.contents = [cm.name() for cm in cell_measures] @@ -272,7 +272,7 @@ def add_scalar_section(section_class, title, *args): ScalarSection, "Scalar Coordinates:", cube, scalar_coords ) add_scalar_section( - ScalarCMSection, "Scalar cell measures:", scalar_cell_measures + ScalarCellMeasureSection, "Scalar cell measures:", scalar_cell_measures ) add_scalar_section(AttributeSection, "Attributes:", cube.attributes) add_scalar_section( From 47fc0762b08545ef61e9b15f80751d3dbebef6d3 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Fri, 5 Feb 2021 11:18:07 +0000 Subject: [PATCH 10/12] blacken --- lib/iris/_representation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/iris/_representation.py b/lib/iris/_representation.py index 33ac3f920e..1c5f55d227 100644 --- a/lib/iris/_representation.py +++ b/lib/iris/_representation.py @@ -272,7 +272,9 @@ def add_scalar_section(section_class, title, *args): ScalarSection, "Scalar Coordinates:", cube, scalar_coords ) add_scalar_section( - ScalarCellMeasureSection, "Scalar cell measures:", scalar_cell_measures + ScalarCellMeasureSection, + "Scalar cell measures:", + scalar_cell_measures, ) add_scalar_section(AttributeSection, "Attributes:", cube.attributes) add_scalar_section( From e6e15f088c70b76e3244685fb520cda397fd20ff Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Fri, 5 Feb 2021 11:27:33 +0000 Subject: [PATCH 11/12] fix test --- lib/iris/_representation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/iris/_representation.py b/lib/iris/_representation.py index 1c5f55d227..f201b40492 100644 --- a/lib/iris/_representation.py +++ b/lib/iris/_representation.py @@ -143,7 +143,8 @@ def __init__(self, cube, coord): class Section: - contents = [] + def _init_(self): + self.contents = [] def is_empty(self): return self.contents == [] @@ -174,6 +175,7 @@ def __init__(self, title, attributes): self.title = title self.names = [] self.values = [] + self.contents = [] for name, value in sorted(attributes.items()): value = iris.util.clip_string(str(value)) self.names.append(name) From b7f71c04db17c30f03de00858f6ce69b844df91e Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Fri, 5 Feb 2021 12:52:20 +0000 Subject: [PATCH 12/12] remove unused function --- lib/iris/_representation.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/iris/_representation.py b/lib/iris/_representation.py index f201b40492..301f4a9a22 100644 --- a/lib/iris/_representation.py +++ b/lib/iris/_representation.py @@ -10,17 +10,6 @@ import iris.util -def sorted_axes(axes): - """ - Returns the axis names sorted alphabetically, with the exception that - 't', 'z', 'y', and, 'x' are sorted to the end. - """ - return sorted( - axes, - key=lambda name: ({"x": 4, "y": 3, "z": 2, "t": 1}.get(name, 0), name), - ) - - class DimensionHeader: def __init__(self, cube): if cube.shape == ():