From 252976fba774b5fae41373d57f41d5df8657cd5b Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Mon, 26 May 2025 22:06:57 +0100 Subject: [PATCH 01/12] pull html directly from cube summary --- lib/iris/experimental/representation.py | 136 ++++------- .../representation/test_CubeRepresentation.py | 213 ++++++++++-------- 2 files changed, 156 insertions(+), 193 deletions(-) diff --git a/lib/iris/experimental/representation.py b/lib/iris/experimental/representation.py index 0648cc8e0d..d273c0d617 100644 --- a/lib/iris/experimental/representation.py +++ b/lib/iris/experimental/representation.py @@ -8,6 +8,8 @@ from html import escape import re +from iris._representation.cube_summary import CubeSummary + class CubeRepresentation: """Produce representations of a :class:`~iris.cube.Cube`. @@ -77,34 +79,8 @@ class CubeRepresentation: def __init__(self, cube): self.cube = cube + self.summary = CubeSummary(cube) self.cube_id = id(self.cube) - self.cube_str = escape(str(self.cube)) - - # Define the expected vector and scalar sections in output, in expected - # order of appearance. - # NOTE: if we recoded this to use a CubeSummary, these section titles - # would be available from that. - self.vector_section_names = [ - "Dimension coordinates:", - "Mesh coordinates:", - "Auxiliary coordinates:", - "Derived coordinates:", - "Cell measures:", - "Ancillary variables:", - ] - self.scalar_section_names = [ - "Mesh:", - "Scalar coordinates:", - "Scalar cell measures:", - "Cell methods:", - "Attributes:", - ] - self.sections_data = { - name: None for name in self.vector_section_names + self.scalar_section_names - } - # 'Scalar-cell-measures' is currently alone amongst the scalar sections, - # in displaying only a 'name' and no 'value' field. - self.single_cell_section_names = ["Scalar cell measures:"] # Important content that summarises a cube is defined here. self.shapes = self.cube.shape @@ -144,40 +120,6 @@ def _dim_names(self): dim_names = self._get_dim_names() return dim_names - def _get_lines(self): - return self.cube_str.split("\n") - - def _get_bits(self, bits): - """Parse the body content (`bits`) of the cube string. - - Parse the body content (`bits`) of the cube string in preparation for - being converted into table rows. - - """ - left_indent = re.split(r"\w+", bits[1])[0] - - # Get heading indices within the printout. - start_inds = [] - for hdg in self.sections_data.keys(): - heading = "{}{}".format(left_indent, hdg) - try: - start_ind = bits.index(heading) - except ValueError: - continue - else: - start_inds.append(start_ind) - # Mark the end of the file. - start_inds.append(0) - - # Retrieve info for each heading from the printout. - for i0, i1 in zip(start_inds[:-1], start_inds[1:]): - str_heading_name = bits[i0].strip() - if i1 != 0: - content = bits[i0 + 1 : i1] - else: - content = bits[i0 + 1 :] - self.sections_data[str_heading_name] = content - def _make_header(self): """Make the table header. @@ -269,28 +211,45 @@ def _make_row(self, title, body=None, col_span=0): def _make_content(self): elements = [] - for k, v in self.sections_data.items(): - if v is not None: - # Add the sub-heading title. - elements.extend(self._make_row(k)) - for line in v: - # Add every other row in the sub-heading. - if k in self.vector_section_names: - body = re.findall(r"[\w-]+", line) - title = body.pop(0) - colspan = 0 - else: - colspan = self.ndims - if k in self.single_cell_section_names: - title = line.strip() - body = "" - else: - line = line.strip() - split_point = line.index(" ") - title = line[:split_point].strip() - body = line[split_point + 2 :].strip() - - elements.extend(self._make_row(title, body=body, col_span=colspan)) + for sect in self.summary.vector_sections.values(): + if sect.contents: + sect_title = sect.title + elements.extend(self._make_row(sect_title)) + + for content in sect.contents: + body = content.dim_chars + title = content.name + elements.extend(self._make_row(title, body=body, col_span=0)) + for sect in self.summary.scalar_sections.values(): + if sect.contents: + sect_title = sect.title + elements.extend(self._make_row(sect_title)) + st = sect_title.lower() + if st == "scalar coordinates:": + for item in sect.contents: + body = item.content + title = item.name + if item.extra: + # TODO: + pass + elements.extend(self._make_row(title, body=body, col_span=self.ndims)) + elif st in ("attributes:", "cell methods:", "mesh:"): + for title, body in zip(sect.names, sect.values): + body = escape(body) # TODO: escape everything + elements.extend(self._make_row(title, body=body, col_span=self.ndims)) + pass + elif st in ( + "scalar ancillary variables:", + "scalar cell measures:", + ): + body = "" + # These are just strings: nothing in the 'value' column. + for title in sect.contents: + elements.extend(self._make_row(title, body=body, col_span=self.ndims)) + pass + else: + msg = f"Unknown section type : {type(sect)}" + raise ValueError(msg) return "\n".join(element for element in elements) def repr_html(self): @@ -305,16 +264,7 @@ def repr_html(self): self.ndims = 1 else: shape = self._make_shapes_row() - - # Now deal with the rest of the content. - lines = self._get_lines() - # If we only have a single line `cube_str` we have no coords / attrs! - # We need to handle this case specially. - if len(lines) == 1: - content = "" - else: - self._get_bits(lines) - content = self._make_content() + content = self._make_content() return self._template.format( header=header, id=self.cube_id, shape=shape, content=content diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 7515fad08a..dc4f12fa2e 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -14,7 +14,7 @@ from iris.coords import AncillaryVariable, CellMeasure, CellMethod from iris.cube import Cube -from iris.experimental.representation import CubeRepresentation +from iris.experimental.representation import CubeRepresentation, CubeSummary import iris.tests.stock as stock from iris.tests.stock.mesh import sample_mesh @@ -27,7 +27,8 @@ def setUp(self): def test_cube_attributes(self): self.assertEqual(id(self.cube), self.representer.cube_id) - self.assertMultiLineEqual(str(self.cube), self.representer.cube_str) + # self.assertMultiLineEqual(str(self.cube), self.representer.cube_str) + self.assertEqual(self.cube.summary(), self.representer.summary.) def test__heading_contents(self): content = set(self.representer.sections_data.values()) @@ -111,96 +112,96 @@ def test_ndims(self): self.assertEqual(expected, result) -@tests.skip_data -class Test__get_bits(tests.IrisTest): - def setUp(self): - self.cube = stock.realistic_4d() - cmth = CellMethod("mean", "time", "6hr") - self.cube.add_cell_method(cmth) - cms = CellMeasure([0, 1, 2, 3, 4, 5], long_name="foo") - self.cube.add_cell_measure(cms, 0) - avr = AncillaryVariable([0, 1, 2, 3, 4, 5], long_name="bar") - self.cube.add_ancillary_variable(avr, 0) - scms = CellMeasure([0], long_name="baz") - self.cube.add_cell_measure(scms) - self.representer = CubeRepresentation(self.cube) - self.representer._get_bits(self.representer._get_lines()) - - def test_population(self): - nonmesh_values = [ - value - for key, value in self.representer.sections_data.items() - if "Mesh" not in key - ] - for v in nonmesh_values: - self.assertIsNotNone(v) - - def test_headings__dimcoords(self): - contents = self.representer.sections_data["Dimension coordinates:"] - content_str = ",".join(content for content in contents) - dim_coords = [c.name() for c in self.cube.dim_coords] - for coord in dim_coords: - self.assertIn(coord, content_str) - - def test_headings__auxcoords(self): - contents = self.representer.sections_data["Auxiliary coordinates:"] - content_str = ",".join(content for content in contents) - aux_coords = [c.name() for c in self.cube.aux_coords if c.shape != (1,)] - for coord in aux_coords: - self.assertIn(coord, content_str) - - def test_headings__derivedcoords(self): - contents = self.representer.sections_data["Derived coordinates:"] - content_str = ",".join(content for content in contents) - derived_coords = [c.name() for c in self.cube.derived_coords] - for coord in derived_coords: - self.assertIn(coord, content_str) - - def test_headings__cellmeasures(self): - contents = self.representer.sections_data["Cell measures:"] - content_str = ",".join(content for content in contents) - cell_measures = [c.name() for c in self.cube.cell_measures() if c.shape != (1,)] - for coord in cell_measures: - self.assertIn(coord, content_str) - - def test_headings__ancillaryvars(self): - contents = self.representer.sections_data["Ancillary variables:"] - content_str = ",".join(content for content in contents) - ancillary_variables = [c.name() for c in self.cube.ancillary_variables()] - for coord in ancillary_variables: - self.assertIn(coord, content_str) - - def test_headings__scalarcellmeasures(self): - contents = self.representer.sections_data["Scalar cell measures:"] - content_str = ",".join(content for content in contents) - scalar_cell_measures = [ - c.name() for c in self.cube.cell_measures() if c.shape == (1,) - ] - for coord in scalar_cell_measures: - self.assertIn(coord, content_str) - - def test_headings__scalarcoords(self): - contents = self.representer.sections_data["Scalar coordinates:"] - content_str = ",".join(content for content in contents) - scalar_coords = [c.name() for c in self.cube.coords() if c.shape == (1,)] - for coord in scalar_coords: - self.assertIn(coord, content_str) - - def test_headings__attributes(self): - contents = self.representer.sections_data["Attributes:"] - content_str = ",".join(content for content in contents) - for attr_name, attr_value in self.cube.attributes.items(): - self.assertIn(attr_name, content_str) - self.assertIn(attr_value, content_str) - - def test_headings__cellmethods(self): - contents = self.representer.sections_data["Cell methods:"] - content_str = ",".join(content for content in contents) - for method in self.cube.cell_methods: - name = method.method - value = str(method)[len(name + ": ") :] - self.assertIn(name, content_str) - self.assertIn(value, content_str) +# @tests.skip_data +# class Test__get_bits(tests.IrisTest): +# def setUp(self): +# self.cube = stock.realistic_4d() +# cmth = CellMethod("mean", "time", "6hr") +# self.cube.add_cell_method(cmth) +# cms = CellMeasure([0, 1, 2, 3, 4, 5], long_name="foo") +# self.cube.add_cell_measure(cms, 0) +# avr = AncillaryVariable([0, 1, 2, 3, 4, 5], long_name="bar") +# self.cube.add_ancillary_variable(avr, 0) +# scms = CellMeasure([0], long_name="baz") +# self.cube.add_cell_measure(scms) +# self.representer = CubeRepresentation(self.cube) +# self.representer._get_bits(self.representer._get_lines()) +# +# def test_population(self): +# nonmesh_values = [ +# value +# for key, value in self.representer.sections_data.items() +# if "Mesh" not in key +# ] +# for v in nonmesh_values: +# self.assertIsNotNone(v) +# +# def test_headings__dimcoords(self): +# contents = self.representer.sections_data["Dimension coordinates:"] +# content_str = ",".join(content for content in contents) +# dim_coords = [c.name() for c in self.cube.dim_coords] +# for coord in dim_coords: +# self.assertIn(coord, content_str) +# +# def test_headings__auxcoords(self): +# contents = self.representer.sections_data["Auxiliary coordinates:"] +# content_str = ",".join(content for content in contents) +# aux_coords = [c.name() for c in self.cube.aux_coords if c.shape != (1,)] +# for coord in aux_coords: +# self.assertIn(coord, content_str) +# +# def test_headings__derivedcoords(self): +# contents = self.representer.sections_data["Derived coordinates:"] +# content_str = ",".join(content for content in contents) +# derived_coords = [c.name() for c in self.cube.derived_coords] +# for coord in derived_coords: +# self.assertIn(coord, content_str) +# +# def test_headings__cellmeasures(self): +# contents = self.representer.sections_data["Cell measures:"] +# content_str = ",".join(content for content in contents) +# cell_measures = [c.name() for c in self.cube.cell_measures() if c.shape != (1,)] +# for coord in cell_measures: +# self.assertIn(coord, content_str) +# +# def test_headings__ancillaryvars(self): +# contents = self.representer.sections_data["Ancillary variables:"] +# content_str = ",".join(content for content in contents) +# ancillary_variables = [c.name() for c in self.cube.ancillary_variables()] +# for coord in ancillary_variables: +# self.assertIn(coord, content_str) +# +# def test_headings__scalarcellmeasures(self): +# contents = self.representer.sections_data["Scalar cell measures:"] +# content_str = ",".join(content for content in contents) +# scalar_cell_measures = [ +# c.name() for c in self.cube.cell_measures() if c.shape == (1,) +# ] +# for coord in scalar_cell_measures: +# self.assertIn(coord, content_str) +# +# def test_headings__scalarcoords(self): +# contents = self.representer.sections_data["Scalar coordinates:"] +# content_str = ",".join(content for content in contents) +# scalar_coords = [c.name() for c in self.cube.coords() if c.shape == (1,)] +# for coord in scalar_coords: +# self.assertIn(coord, content_str) +# +# def test_headings__attributes(self): +# contents = self.representer.sections_data["Attributes:"] +# content_str = ",".join(content for content in contents) +# for attr_name, attr_value in self.cube.attributes.items(): +# self.assertIn(attr_name, content_str) +# self.assertIn(attr_value, content_str) +# +# def test_headings__cellmethods(self): +# contents = self.representer.sections_data["Cell methods:"] +# content_str = ",".join(content for content in contents) +# for method in self.cube.cell_methods: +# name = method.method +# value = str(method)[len(name + ": ") :] +# self.assertIn(name, content_str) +# self.assertIn(value, content_str) @tests.skip_data @@ -208,7 +209,7 @@ class Test__make_header(tests.IrisTest): def setUp(self): self.cube = stock.simple_3d() self.representer = CubeRepresentation(self.cube) - self.representer._get_bits(self.representer._get_lines()) + # self.representer._get_bits(self.representer._get_lines()) self.header_emts = self.representer._make_header().split("\n") def test_name_and_units(self): @@ -241,7 +242,7 @@ class Test__make_shapes_row(tests.IrisTest): def setUp(self): self.cube = stock.simple_3d() self.representer = CubeRepresentation(self.cube) - self.representer._get_bits(self.representer._get_lines()) + # self.representer._get_bits(self.representer._get_lines()) self.result = self.representer._make_shapes_row().split("\n") def test_row_title(self): @@ -262,7 +263,7 @@ def setUp(self): cm = CellMethod("mean", "time", "6hr") self.cube.add_cell_method(cm) self.representer = CubeRepresentation(self.cube) - self.representer._get_bits(self.representer._get_lines()) + # self.representer._get_bits(self.representer._get_lines()) def test__title_row(self): title = "Wibble:" @@ -323,8 +324,21 @@ class Test__make_content(tests.IrisTest): def setUp(self): self.cube = stock.simple_3d() self.representer = CubeRepresentation(self.cube) - self.representer._get_bits(self.representer._get_lines()) + # self.representer._get_bits(self.representer._get_lines()) self.result = self.representer._make_content() + self.sections_keys = [ + "Dimension coordinates:", + "Mesh coordinates:", + "Auxiliary coordinates:", + "Derived coordinates:", + "Cell measures:", + "Ancillary variables:", + "Mesh:", + "Scalar coordinates:", + "Scalar cell measures:", + "Cell methods:", + "Attributes:", + ] # Also provide an ultra-simple mesh cube, with only meshcoords. mesh = sample_mesh() @@ -334,7 +348,7 @@ def setUp(self): mesh_cube.add_aux_coord(meshco_y, (0,)) self.mesh_cube = mesh_cube self.mesh_representer = CubeRepresentation(self.mesh_cube) - self.mesh_representer._get_bits(self.mesh_representer._get_lines()) + # self.mesh_representer._get_bits(self.mesh_representer._get_lines()) self.mesh_result = self.mesh_representer._make_content() def test_included(self): @@ -346,7 +360,7 @@ def test_included(self): def test_not_included(self): # `stock.simple_3d()` only contains the `Dimension coordinates` attr. - not_included = list(self.representer.sections_data.keys()) + not_included = list(self.sections_keys) not_included.pop(not_included.index("Dimension coordinates:")) for heading in not_included: self.assertNotIn(heading, self.result) @@ -367,7 +381,7 @@ def test_mesh_included(self): def test_mesh_not_included(self): # self.mesh_cube _only_ contains a `Mesh coordinates` section. - not_included = list(self.representer.sections_data.keys()) + not_included = list(self.sections_keys) not_included.pop(not_included.index("Mesh coordinates:")) for heading in not_included: self.assertNotIn(heading, self.result) @@ -391,7 +405,6 @@ def _cube_stringattribute_html(name, attr): cube = Cube([0]) cube.attributes[name] = attr representer = CubeRepresentation(cube) - representer._get_bits(representer._get_lines()) result = representer._make_content() return result From 7f3c1afbcbd0485eed0bb9a3f6b9bc3365f57657 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 21:08:41 +0000 Subject: [PATCH 02/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- lib/iris/experimental/representation.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/iris/experimental/representation.py b/lib/iris/experimental/representation.py index d273c0d617..9741ed0719 100644 --- a/lib/iris/experimental/representation.py +++ b/lib/iris/experimental/representation.py @@ -232,20 +232,26 @@ def _make_content(self): if item.extra: # TODO: pass - elements.extend(self._make_row(title, body=body, col_span=self.ndims)) + elements.extend( + self._make_row(title, body=body, col_span=self.ndims) + ) elif st in ("attributes:", "cell methods:", "mesh:"): for title, body in zip(sect.names, sect.values): body = escape(body) # TODO: escape everything - elements.extend(self._make_row(title, body=body, col_span=self.ndims)) + elements.extend( + self._make_row(title, body=body, col_span=self.ndims) + ) pass elif st in ( - "scalar ancillary variables:", - "scalar cell measures:", + "scalar ancillary variables:", + "scalar cell measures:", ): body = "" # These are just strings: nothing in the 'value' column. for title in sect.contents: - elements.extend(self._make_row(title, body=body, col_span=self.ndims)) + elements.extend( + self._make_row(title, body=body, col_span=self.ndims) + ) pass else: msg = f"Unknown section type : {type(sect)}" From f4dcc8eba42d04a4bf04be83b290b39c3d17acce Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Tue, 27 May 2025 09:10:26 +0100 Subject: [PATCH 03/12] remove line from test --- .../unit/experimental/representation/test_CubeRepresentation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index dc4f12fa2e..341c3b9dff 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -28,7 +28,6 @@ def setUp(self): def test_cube_attributes(self): self.assertEqual(id(self.cube), self.representer.cube_id) # self.assertMultiLineEqual(str(self.cube), self.representer.cube_str) - self.assertEqual(self.cube.summary(), self.representer.summary.) def test__heading_contents(self): content = set(self.representer.sections_data.values()) From 2d3bd9e01f1df62f3839dcb7b2858ed3e7c6f551 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Tue, 27 May 2025 12:37:45 +0100 Subject: [PATCH 04/12] remove test --- .../representation/test_CubeRepresentation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 341c3b9dff..bf0f146da8 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -29,10 +29,10 @@ def test_cube_attributes(self): self.assertEqual(id(self.cube), self.representer.cube_id) # self.assertMultiLineEqual(str(self.cube), self.representer.cube_str) - def test__heading_contents(self): - content = set(self.representer.sections_data.values()) - self.assertEqual(len(content), 1) - self.assertIsNone(list(content)[0]) + # def test__heading_contents(self): + # content = set(self.representer.sections_data.values()) + # self.assertEqual(len(content), 1) + # self.assertIsNone(list(content)[0]) @tests.skip_data From 0fec86d1cc18b87d0711936b2b131474d44ade1c Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Tue, 27 May 2025 13:57:45 +0100 Subject: [PATCH 05/12] remove imports --- lib/iris/experimental/representation.py | 1 - .../unit/experimental/representation/test_CubeRepresentation.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/iris/experimental/representation.py b/lib/iris/experimental/representation.py index 9741ed0719..6400d5d78e 100644 --- a/lib/iris/experimental/representation.py +++ b/lib/iris/experimental/representation.py @@ -6,7 +6,6 @@ """Definitions of how Iris objects should be represented.""" from html import escape -import re from iris._representation.cube_summary import CubeSummary diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index bf0f146da8..9a40379c1b 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -12,7 +12,7 @@ import numpy as np -from iris.coords import AncillaryVariable, CellMeasure, CellMethod +from iris.coords import CellMethod from iris.cube import Cube from iris.experimental.representation import CubeRepresentation, CubeSummary import iris.tests.stock as stock From d14f6938939e8eea8b245aada4a57ab1b904b95b Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Tue, 27 May 2025 14:20:45 +0100 Subject: [PATCH 06/12] remove imports --- .../unit/experimental/representation/test_CubeRepresentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 9a40379c1b..7ab6f6e47e 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -14,7 +14,7 @@ from iris.coords import CellMethod from iris.cube import Cube -from iris.experimental.representation import CubeRepresentation, CubeSummary +from iris.experimental.representation import CubeRepresentation import iris.tests.stock as stock from iris.tests.stock.mesh import sample_mesh From 4c2b3424935f63aa9530438112637f8d370e7d9e Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Thu, 29 May 2025 14:02:30 +0100 Subject: [PATCH 07/12] fix 'extra' behaviour --- lib/iris/experimental/representation.py | 20 +-- .../representation/test_CubeRepresentation.py | 134 +++++++++++++++++- 2 files changed, 144 insertions(+), 10 deletions(-) diff --git a/lib/iris/experimental/representation.py b/lib/iris/experimental/representation.py index 6400d5d78e..a498ac1b28 100644 --- a/lib/iris/experimental/representation.py +++ b/lib/iris/experimental/representation.py @@ -192,8 +192,7 @@ def _make_row(self, title, body=None, col_span=0): content=sub_title, ) ) - # One further item or more than that? - if col_span != 0: + if not isinstance(body, list): html_cls = ' class="{}" colspan="{}"'.format("iris-word-cell", col_span) row.append(template.format(html_cls=html_cls, content=body)) else: @@ -217,7 +216,10 @@ def _make_content(self): for content in sect.contents: body = content.dim_chars - title = content.name + + title = escape(content.name) + if content.extra: + title = title + "
    " + escape(content.extra) elements.extend(self._make_row(title, body=body, col_span=0)) for sect in self.summary.scalar_sections.values(): if sect.contents: @@ -226,17 +228,17 @@ def _make_content(self): st = sect_title.lower() if st == "scalar coordinates:": for item in sect.contents: - body = item.content - title = item.name + body = escape(item.content) + title = escape(item.name) if item.extra: - # TODO: - pass + title = title + "
    " + escape(item.extra) elements.extend( self._make_row(title, body=body, col_span=self.ndims) ) elif st in ("attributes:", "cell methods:", "mesh:"): for title, body in zip(sect.names, sect.values): - body = escape(body) # TODO: escape everything + title = escape(title) + body = escape(body) elements.extend( self._make_row(title, body=body, col_span=self.ndims) ) @@ -248,10 +250,10 @@ def _make_content(self): body = "" # These are just strings: nothing in the 'value' column. for title in sect.contents: + title = escape(title) elements.extend( self._make_row(title, body=body, col_span=self.ndims) ) - pass else: msg = f"Unknown section type : {type(sect)}" raise ValueError(msg) diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 7ab6f6e47e..6e408c9244 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -12,7 +12,7 @@ import numpy as np -from iris.coords import CellMethod +from iris.coords import AuxCoord, CellMethod from iris.cube import Cube from iris.experimental.representation import CubeRepresentation import iris.tests.stock as stock @@ -426,6 +426,138 @@ def test_multi_string_attribute(self): html = self._cube_stringattribute_html("multi-string", attr) self.assertString(html) + def test_coord_distinguishing_attributes(self): + # Printout of differing attributes to differentiate same-named coords. + # include : vector + scalar + cube = Cube([0, 1], long_name="name", units=1) + # Add a pair of vector coords with same name but different attributes. + cube.add_aux_coord(AuxCoord([0, 1], long_name="co1", attributes=dict(a=1)), 0) + cube.add_aux_coord(AuxCoord([0, 1], long_name="co1", attributes=dict(a=2)), 0) + # Likewise for scalar coords with same name but different attributes. + cube.add_aux_coord(AuxCoord([0], long_name="co2", attributes=dict(a=10, b=12))) + cube.add_aux_coord(AuxCoord([1], long_name="co2", attributes=dict(a=10, b=11))) + + rep = CubeRepresentation(cube) + result = rep._make_content() + expected = ( + '\n' + ' Auxiliary coordinates\n' + ' \n' + '\n' + '\n' + ' \t' + 'co1
    a=1\n' + ' x\n' + '\n' + '\n' + ' \t' + 'co1
    a=2\n' + ' x\n' + '\n' + '\n' + ' Scalar coordinates\n' + ' \n' + '\n' + '\n' + ' \t' + 'co2
    b=12\n' + ' 0\n' + '\n' + '\n' + ' \t' + 'co2
    b=11\n' + ' 1\n' + '' + ) + self.assertEqual(result, expected) + def test_coord_extra_attributes__array(self): + cube = Cube(0, long_name="name", units=1) + # Add a pair of vector coords with same name but different attributes. + array1 = np.arange(0, 3) + array2 = np.arange(10, 13) + cube.add_aux_coord( + AuxCoord([1.2], long_name="co1", attributes=dict(a=1, arr=array1)) + ) + cube.add_aux_coord( + AuxCoord([3.4], long_name="co1", attributes=dict(a=1, arr=array2)) + ) + + rep = CubeRepresentation(cube) + result = rep._make_content() + expected = ( + '\n' + ' Scalar coordinates\n' + '\n' + '\n' + ' \t' + 'co1
    arr=array([0, 1, 2])\n' + ' 1.2\n' + '\n' + '\n' + ' \t' + 'co1
    arr=array([10, 11, 12])\n' + ' 3.4\n' + '' + ) + self.assertEqual(result, expected) + + def test_coord_extra_attributes__array__long(self): + # Also test with a long array representation. + array = 10 + np.arange(24.0).reshape((2, 3, 4)) + cube = Cube(0, long_name="name", units=1) + cube.add_aux_coord(AuxCoord([1], long_name="co")) + cube.add_aux_coord( + AuxCoord([2], long_name="co", attributes=dict(a=array + 1.0)) + ) + + rep = CubeRepresentation(cube) + result = rep._make_content() + expected = ( + '\n' + ' Scalar coordinates\n' + '\n' + '\n' + ' \tco\n' + ' 1\n' + '\n' + '\n' + ' \t' + 'co
    a=array([[[11., 12., 13., 14.], [15., 16., ' + '17., 18.], [19., 20., 21., 22.]],...\n' + ' 2\n' + '' + ) + self.assertEqual(result, expected) + + def test_coord_extra_attributes__string_escaped(self): + cube = Cube(0, long_name="name", units=1) + cube.add_aux_coord(AuxCoord([1], long_name="co")) + cube.add_aux_coord( + AuxCoord( + [2], + long_name="co", + attributes=dict(note="line 1\nline 2\t& ends."), + ) + ) + rep = CubeRepresentation(cube) + result = rep._make_content() + expected = ( + '\n' + ' Scalar coordinates\n' + '\n' + '\n' + ' \tco\n' + ' 1\n' + '\n' + '\n' + ' \t' + 'co
    note='line 1\\nline 2\\t& ' + 'ends.'\n' + ' 2\n' + '' + ) + self.assertEqual(result, expected) + @tests.skip_data class Test_repr_html(tests.IrisTest): From 645bba4839a577aab698fe040532d5c6c3882143 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 13:03:17 +0000 Subject: [PATCH 08/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- lib/iris/experimental/representation.py | 10 ++- .../representation/test_CubeRepresentation.py | 65 ++++++++++--------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/lib/iris/experimental/representation.py b/lib/iris/experimental/representation.py index a498ac1b28..cf31a9f189 100644 --- a/lib/iris/experimental/representation.py +++ b/lib/iris/experimental/representation.py @@ -219,7 +219,9 @@ def _make_content(self): title = escape(content.name) if content.extra: - title = title + "
    " + escape(content.extra) + title = ( + title + "
    " + escape(content.extra) + ) elements.extend(self._make_row(title, body=body, col_span=0)) for sect in self.summary.scalar_sections.values(): if sect.contents: @@ -231,7 +233,11 @@ def _make_content(self): body = escape(item.content) title = escape(item.name) if item.extra: - title = title + "
    " + escape(item.extra) + title = ( + title + + "
    " + + escape(item.extra) + ) elements.extend( self._make_row(title, body=body, col_span=self.ndims) ) diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 6e408c9244..3eb94295b9 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -443,33 +443,34 @@ def test_coord_distinguishing_attributes(self): '\n' ' Auxiliary coordinates\n' ' \n' - '\n' + "\n" '\n' ' \t' - 'co1
    a=1\n' + "co1
    a=1\n" ' x\n' - '\n' + "\n" '\n' ' \t' - 'co1
    a=2\n' + "co1
    a=2\n" ' x\n' - '\n' + "\n" '\n' ' Scalar coordinates\n' ' \n' - '\n' + "\n" '\n' ' \t' - 'co2
    b=12\n' + "co2
    b=12\n" ' 0\n' - '\n' + "\n" '\n' ' \t' - 'co2
    b=11\n' + "co2
    b=11\n" ' 1\n' - '' + "" ) self.assertEqual(result, expected) + def test_coord_extra_attributes__array(self): cube = Cube(0, long_name="name", units=1) # Add a pair of vector coords with same name but different attributes. @@ -486,18 +487,18 @@ def test_coord_extra_attributes__array(self): result = rep._make_content() expected = ( '\n' - ' Scalar coordinates\n' - '\n' - '\n' - ' \t' - 'co1
    arr=array([0, 1, 2])\n' - ' 1.2\n' - '\n' - '\n' - ' \t' - 'co1
    arr=array([10, 11, 12])\n' - ' 3.4\n' - '' + ' Scalar coordinates\n' + "\n" + '\n' + ' \t' + "co1
    arr=array([0, 1, 2])\n" + ' 1.2\n' + "\n" + '\n' + ' \t' + "co1
    arr=array([10, 11, 12])\n" + ' 3.4\n' + "" ) self.assertEqual(result, expected) @@ -515,17 +516,17 @@ def test_coord_extra_attributes__array__long(self): expected = ( '\n' ' Scalar coordinates\n' - '\n' + "\n" '\n' ' \tco\n' ' 1\n' - '\n' + "\n" '\n' ' \t' - 'co
    a=array([[[11., 12., 13., 14.], [15., 16., ' - '17., 18.], [19., 20., 21., 22.]],...\n' + "co
    a=array([[[11., 12., 13., 14.], [15., 16., " + "17., 18.], [19., 20., 21., 22.]],...\n" ' 2\n' - '' + "" ) self.assertEqual(result, expected) @@ -544,17 +545,17 @@ def test_coord_extra_attributes__string_escaped(self): expected = ( '\n' ' Scalar coordinates\n' - '\n' + "\n" '\n' ' \tco\n' ' 1\n' - '\n' + "\n" '\n' ' \t' - 'co
    note='line 1\\nline 2\\t& ' - 'ends.'\n' + "co
    note='line 1\\nline 2\\t& " + "ends.'\n" ' 2\n' - '' + "" ) self.assertEqual(result, expected) From cc8ce57b5f1ca77fbf376fbbaf78d60a58947546 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Fri, 30 May 2025 09:02:15 +0100 Subject: [PATCH 09/12] replace tests --- .../representation/test_CubeRepresentation.py | 268 +++++++++++------- 1 file changed, 167 insertions(+), 101 deletions(-) diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 3eb94295b9..43834f7e42 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -12,7 +12,7 @@ import numpy as np -from iris.coords import AuxCoord, CellMethod +from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, CellMethod from iris.cube import Cube from iris.experimental.representation import CubeRepresentation import iris.tests.stock as stock @@ -27,12 +27,7 @@ def setUp(self): def test_cube_attributes(self): self.assertEqual(id(self.cube), self.representer.cube_id) - # self.assertMultiLineEqual(str(self.cube), self.representer.cube_str) - # def test__heading_contents(self): - # content = set(self.representer.sections_data.values()) - # self.assertEqual(len(content), 1) - # self.assertIsNone(list(content)[0]) @tests.skip_data @@ -111,96 +106,172 @@ def test_ndims(self): self.assertEqual(expected, result) -# @tests.skip_data -# class Test__get_bits(tests.IrisTest): -# def setUp(self): -# self.cube = stock.realistic_4d() -# cmth = CellMethod("mean", "time", "6hr") -# self.cube.add_cell_method(cmth) -# cms = CellMeasure([0, 1, 2, 3, 4, 5], long_name="foo") -# self.cube.add_cell_measure(cms, 0) -# avr = AncillaryVariable([0, 1, 2, 3, 4, 5], long_name="bar") -# self.cube.add_ancillary_variable(avr, 0) -# scms = CellMeasure([0], long_name="baz") -# self.cube.add_cell_measure(scms) -# self.representer = CubeRepresentation(self.cube) -# self.representer._get_bits(self.representer._get_lines()) -# -# def test_population(self): -# nonmesh_values = [ -# value -# for key, value in self.representer.sections_data.items() -# if "Mesh" not in key -# ] -# for v in nonmesh_values: -# self.assertIsNotNone(v) -# -# def test_headings__dimcoords(self): -# contents = self.representer.sections_data["Dimension coordinates:"] -# content_str = ",".join(content for content in contents) -# dim_coords = [c.name() for c in self.cube.dim_coords] -# for coord in dim_coords: -# self.assertIn(coord, content_str) -# -# def test_headings__auxcoords(self): -# contents = self.representer.sections_data["Auxiliary coordinates:"] -# content_str = ",".join(content for content in contents) -# aux_coords = [c.name() for c in self.cube.aux_coords if c.shape != (1,)] -# for coord in aux_coords: -# self.assertIn(coord, content_str) -# -# def test_headings__derivedcoords(self): -# contents = self.representer.sections_data["Derived coordinates:"] -# content_str = ",".join(content for content in contents) -# derived_coords = [c.name() for c in self.cube.derived_coords] -# for coord in derived_coords: -# self.assertIn(coord, content_str) -# -# def test_headings__cellmeasures(self): -# contents = self.representer.sections_data["Cell measures:"] -# content_str = ",".join(content for content in contents) -# cell_measures = [c.name() for c in self.cube.cell_measures() if c.shape != (1,)] -# for coord in cell_measures: -# self.assertIn(coord, content_str) -# -# def test_headings__ancillaryvars(self): -# contents = self.representer.sections_data["Ancillary variables:"] -# content_str = ",".join(content for content in contents) -# ancillary_variables = [c.name() for c in self.cube.ancillary_variables()] -# for coord in ancillary_variables: -# self.assertIn(coord, content_str) -# -# def test_headings__scalarcellmeasures(self): -# contents = self.representer.sections_data["Scalar cell measures:"] -# content_str = ",".join(content for content in contents) -# scalar_cell_measures = [ -# c.name() for c in self.cube.cell_measures() if c.shape == (1,) -# ] -# for coord in scalar_cell_measures: -# self.assertIn(coord, content_str) -# -# def test_headings__scalarcoords(self): -# contents = self.representer.sections_data["Scalar coordinates:"] -# content_str = ",".join(content for content in contents) -# scalar_coords = [c.name() for c in self.cube.coords() if c.shape == (1,)] -# for coord in scalar_coords: -# self.assertIn(coord, content_str) -# -# def test_headings__attributes(self): -# contents = self.representer.sections_data["Attributes:"] -# content_str = ",".join(content for content in contents) -# for attr_name, attr_value in self.cube.attributes.items(): -# self.assertIn(attr_name, content_str) -# self.assertIn(attr_value, content_str) -# -# def test_headings__cellmethods(self): -# contents = self.representer.sections_data["Cell methods:"] -# content_str = ",".join(content for content in contents) -# for method in self.cube.cell_methods: -# name = method.method -# value = str(method)[len(name + ": ") :] -# self.assertIn(name, content_str) -# self.assertIn(value, content_str) +@tests.skip_data +def test_realistic(): + cube = stock.realistic_4d() + cmth = CellMethod("mean", "time", "6hr") + cube.add_cell_method(cmth) + cms = CellMeasure([0, 1, 2, 3, 4, 5], long_name="foo") + cube.add_cell_measure(cms, 0) + avr = AncillaryVariable([0, 1, 2, 3, 4, 5], long_name="bar") + cube.add_ancillary_variable(avr, 0) + scms = CellMeasure([0], long_name="baz") + cube.add_cell_measure(scms) + representer = CubeRepresentation(cube) + result = representer._make_content() + expected = ( + '\n' + ' Dimension coordinates\n' + ' \n' + ' \n' + ' \n' + ' \n' + '\n' + '\n' + ' \ttime\n' + ' x\n' + ' -\n' + ' -\n' + ' -\n' + '\n' + '\n' + ' \t' + 'model_level_number\n' + ' -\n' + ' x\n' + ' -\n' + ' -\n' + '\n' + '\n' + ' \tgrid_latitude\n' + ' -\n' + ' -\n' + ' x\n' + ' -\n' + '\n' + '\n' + ' \tgrid_longitude\n' + ' -\n' + ' -\n' + ' -\n' + ' x\n' + '\n' + '\n' + ' Auxiliary coordinates\n' + ' \n' + ' \n' + ' \n' + ' \n' + '\n' + '\n' + ' \tlevel_height\n' + ' -\n' + ' x\n' + ' -\n' + ' -\n' + '\n' + '\n' + ' \tsigma\n' + ' -\n' + ' x\n' + ' -\n' + ' -\n' + '\n' + '\n' + ' \tsurface_altitude\n' + ' -\n' + ' -\n' + ' x\n' + ' x\n' + '\n' + '\n' + ' Derived coordinates\n' + ' \n' + ' \n' + ' \n' + ' \n' + '\n' + '\n' + ' \taltitude\n' + ' -\n' + ' x\n' + ' x\n' + ' x\n' + '\n' + '\n' + ' Cell measures\n' + ' \n' + ' \n' + ' \n' + ' \n' + '\n' + '\n' + ' \tfoo\n' + ' x\n' + ' -\n' + ' -\n' + ' -\n' + '\n' + '\n' + ' Ancillary variables\n' + ' \n' + ' \n' + ' \n' + ' \n' + '\n' + '\n' + ' \tbar\n' + ' x\n' + ' -\n' + ' -\n' + ' -\n' + '\n' + '\n' + ' Scalar coordinates\n' + ' \n' + ' \n' + ' \n' + ' \n' + '\n' + '\n' + ' \tforecast_period\n' + ' 0.0 hours\n' + '\n' + '\n' + ' Scalar cell measures\n' + ' \n' + ' \n' + ' \n' + ' \n' + '\n' + '\n' + ' \tbaz\n' + ' \n' + '\n' + '\n' + ' Cell methods\n' + ' \n' + ' \n' + ' \n' + ' \n' + '\n' + '\n' + ' \t0\n' + ' time: mean (interval: 6hr)\n' + '\n' + '\n' + ' Attributes\n' + ' \n' + ' \n' + ' \n' + ' \n' + '\n' + '\n' + ' \tsource\n' + ' 'Iris test case'\n' + '' + ) + assert expected == result @tests.skip_data @@ -208,7 +279,6 @@ class Test__make_header(tests.IrisTest): def setUp(self): self.cube = stock.simple_3d() self.representer = CubeRepresentation(self.cube) - # self.representer._get_bits(self.representer._get_lines()) self.header_emts = self.representer._make_header().split("\n") def test_name_and_units(self): @@ -241,7 +311,6 @@ class Test__make_shapes_row(tests.IrisTest): def setUp(self): self.cube = stock.simple_3d() self.representer = CubeRepresentation(self.cube) - # self.representer._get_bits(self.representer._get_lines()) self.result = self.representer._make_shapes_row().split("\n") def test_row_title(self): @@ -262,7 +331,6 @@ def setUp(self): cm = CellMethod("mean", "time", "6hr") self.cube.add_cell_method(cm) self.representer = CubeRepresentation(self.cube) - # self.representer._get_bits(self.representer._get_lines()) def test__title_row(self): title = "Wibble:" @@ -323,7 +391,6 @@ class Test__make_content(tests.IrisTest): def setUp(self): self.cube = stock.simple_3d() self.representer = CubeRepresentation(self.cube) - # self.representer._get_bits(self.representer._get_lines()) self.result = self.representer._make_content() self.sections_keys = [ "Dimension coordinates:", @@ -347,7 +414,6 @@ def setUp(self): mesh_cube.add_aux_coord(meshco_y, (0,)) self.mesh_cube = mesh_cube self.mesh_representer = CubeRepresentation(self.mesh_cube) - # self.mesh_representer._get_bits(self.mesh_representer._get_lines()) self.mesh_result = self.mesh_representer._make_content() def test_included(self): From 183226366042f2c411693a32e40bfed367fbb415 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 08:03:39 +0000 Subject: [PATCH 10/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../representation/test_CubeRepresentation.py | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 43834f7e42..314969b683 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -29,7 +29,6 @@ def test_cube_attributes(self): self.assertEqual(id(self.cube), self.representer.cube_id) - @tests.skip_data class Test__get_dim_names(tests.IrisTest): def setUp(self): @@ -126,150 +125,150 @@ def test_realistic(): ' \n' ' \n' ' \n' - '\n' + "\n" '\n' ' \ttime\n' ' x\n' ' -\n' ' -\n' ' -\n' - '\n' + "\n" '\n' ' \t' - 'model_level_number\n' + "model_level_number\n" ' -\n' ' x\n' ' -\n' ' -\n' - '\n' + "\n" '\n' ' \tgrid_latitude\n' ' -\n' ' -\n' ' x\n' ' -\n' - '\n' + "\n" '\n' ' \tgrid_longitude\n' ' -\n' ' -\n' ' -\n' ' x\n' - '\n' + "\n" '\n' ' Auxiliary coordinates\n' ' \n' ' \n' ' \n' ' \n' - '\n' + "\n" '\n' ' \tlevel_height\n' ' -\n' ' x\n' ' -\n' ' -\n' - '\n' + "\n" '\n' ' \tsigma\n' ' -\n' ' x\n' ' -\n' ' -\n' - '\n' + "\n" '\n' ' \tsurface_altitude\n' ' -\n' ' -\n' ' x\n' ' x\n' - '\n' + "\n" '\n' ' Derived coordinates\n' ' \n' ' \n' ' \n' ' \n' - '\n' + "\n" '\n' ' \taltitude\n' ' -\n' ' x\n' ' x\n' ' x\n' - '\n' + "\n" '\n' ' Cell measures\n' ' \n' ' \n' ' \n' ' \n' - '\n' + "\n" '\n' ' \tfoo\n' ' x\n' ' -\n' ' -\n' ' -\n' - '\n' + "\n" '\n' ' Ancillary variables\n' ' \n' ' \n' ' \n' ' \n' - '\n' + "\n" '\n' ' \tbar\n' ' x\n' ' -\n' ' -\n' ' -\n' - '\n' + "\n" '\n' ' Scalar coordinates\n' ' \n' ' \n' ' \n' ' \n' - '\n' + "\n" '\n' ' \tforecast_period\n' ' 0.0 hours\n' - '\n' + "\n" '\n' ' Scalar cell measures\n' ' \n' ' \n' ' \n' ' \n' - '\n' + "\n" '\n' ' \tbaz\n' ' \n' - '\n' + "\n" '\n' ' Cell methods\n' ' \n' ' \n' ' \n' ' \n' - '\n' + "\n" '\n' ' \t0\n' ' time: mean (interval: 6hr)\n' - '\n' + "\n" '\n' ' Attributes\n' ' \n' ' \n' ' \n' ' \n' - '\n' + "\n" '\n' ' \tsource\n' ' 'Iris test case'\n' - '' + "" ) assert expected == result From 3cfceb045b6028888d2fa733da7a013467c8afb4 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Mon, 2 Jun 2025 16:27:21 +0100 Subject: [PATCH 11/12] address review comments --- docs/src/whatsnew/latest.rst | 3 +++ lib/iris/experimental/representation.py | 9 +++------ .../representation/test_CubeRepresentation.py | 13 +++++++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index fcbf84364d..30695b73f3 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -39,6 +39,9 @@ This document explains the changes made to Iris for this release #. `@HGWright`_ added a new warning to inform users that the boolean coordinate generated by :meth:`iris.coord_categorisation.add_season_membership` is not saveable to netcdf. (:pull:`6305`) +#. `@stephenworsley`_ fixed the html representation of cubes in Jupyter when coordinates + share the same name. (:pull:`6476`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/experimental/representation.py b/lib/iris/experimental/representation.py index cf31a9f189..01ced86103 100644 --- a/lib/iris/experimental/representation.py +++ b/lib/iris/experimental/representation.py @@ -209,6 +209,7 @@ def _make_row(self, title, body=None, col_span=0): def _make_content(self): elements = [] + INDENT = 4 * " " for sect in self.summary.vector_sections.values(): if sect.contents: sect_title = sect.title @@ -220,7 +221,7 @@ def _make_content(self): title = escape(content.name) if content.extra: title = ( - title + "
    " + escape(content.extra) + title + "
" + INDENT + escape(content.extra) ) elements.extend(self._make_row(title, body=body, col_span=0)) for sect in self.summary.scalar_sections.values(): @@ -233,11 +234,7 @@ def _make_content(self): body = escape(item.content) title = escape(item.name) if item.extra: - title = ( - title - + "
    " - + escape(item.extra) - ) + title = title + "
" + INDENT + escape(item.extra) elements.extend( self._make_row(title, body=body, col_span=self.ndims) ) diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 43834f7e42..e5b35273cf 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -3,6 +3,7 @@ # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.cube.CubeRepresentation` class.""" +import pytest # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -105,9 +106,8 @@ def test_ndims(self): result = self.representer.ndims self.assertEqual(expected, result) - -@tests.skip_data -def test_realistic(): +@pytest.fixture +def realistic_4d(): cube = stock.realistic_4d() cmth = CellMethod("mean", "time", "6hr") cube.add_cell_method(cmth) @@ -117,7 +117,12 @@ def test_realistic(): cube.add_ancillary_variable(avr, 0) scms = CellMeasure([0], long_name="baz") cube.add_cell_measure(scms) - representer = CubeRepresentation(cube) + return cube + + +@tests.skip_data +def test_realistic(realistic_4d): + representer = CubeRepresentation(realistic_4d) result = representer._make_content() expected = ( '\n' From c2e17649c2960d4d208b10c983dc16a623964a61 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:34:12 +0000 Subject: [PATCH 12/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- lib/iris/experimental/representation.py | 4 +--- .../experimental/representation/test_CubeRepresentation.py | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/iris/experimental/representation.py b/lib/iris/experimental/representation.py index 01ced86103..4beac376ee 100644 --- a/lib/iris/experimental/representation.py +++ b/lib/iris/experimental/representation.py @@ -220,9 +220,7 @@ def _make_content(self): title = escape(content.name) if content.extra: - title = ( - title + "
" + INDENT + escape(content.extra) - ) + title = title + "
" + INDENT + escape(content.extra) elements.extend(self._make_row(title, body=body, col_span=0)) for sect in self.summary.scalar_sections.values(): if sect.contents: diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 8a0e7d20f4..f47a1c9ef5 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -3,6 +3,7 @@ # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.cube.CubeRepresentation` class.""" + import pytest # Import iris.tests first so that some things can be initialised before @@ -105,6 +106,7 @@ def test_ndims(self): result = self.representer.ndims self.assertEqual(expected, result) + @pytest.fixture def realistic_4d(): cube = stock.realistic_4d()