From e7a5f4a9e85a1cd7ab4951d23700002a5f9941fc Mon Sep 17 00:00:00 2001 From: Olufunke Awowale Date: Tue, 2 Apr 2024 12:20:53 -0400 Subject: [PATCH 01/13] DAS-2066: formatting_html.py merge --- xarray/core/formatting_html.py | 131 ++++++++++++++++++ xarray/tests/test_formatting_html.py | 194 +++++++++++++++++++++++++++ 2 files changed, 325 insertions(+) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index 2c76b182207..384b6f09a2a 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -2,9 +2,11 @@ import uuid from collections import OrderedDict +from collections.abc import Mapping from functools import lru_cache, partial from html import escape from importlib.resources import files +from typing import Any from xarray.core.formatting import ( inline_index_repr, @@ -18,6 +20,10 @@ ("xarray.static.css", "style.css"), ) +from xarray.core.options import OPTIONS + +OPTIONS["display_expand_groups"] = "default" + @lru_cache(None) def _load_static_files(): @@ -341,3 +347,128 @@ def dataset_repr(ds) -> str: ] return _obj_repr(ds, header_components, sections) + + +def summarize_children(children: Mapping[str, Any]) -> str: + N_CHILDREN = len(children) - 1 + + # Get result from node_repr and wrap it + lines_callback = lambda n, c, end: _wrap_repr(node_repr(n, c), end=end) + + children_html = "".join( + ( + lines_callback(n, c, end=False) # Long lines + if i < N_CHILDREN + else lines_callback(n, c, end=True) + ) # Short lines + for i, (n, c) in enumerate(children.items()) + ) + + return "".join( + [ + "
", + children_html, + "
", + ] + ) + + +children_section = partial( + _mapping_section, + name="Groups", + details_func=summarize_children, + max_items_collapse=1, + expand_option_name="display_expand_groups", +) + + +def node_repr(group_title: str, dt: Any) -> str: + header_components = [f"
{escape(group_title)}
"] + + ds = dt.ds + + sections = [ + children_section(dt.children), + dim_section(ds), + coord_section(ds.coords), + datavar_section(ds.data_vars), + attr_section(ds.attrs), + ] + + return _obj_repr(ds, header_components, sections) + + +def _wrap_repr(r: str, end: bool = False) -> str: + """ + Wrap HTML representation with a tee to the left of it. + + Enclosing HTML tag is a
with :code:`display: inline-grid` style. + + Turns: + [ title ] + | details | + |_____________| + + into (A): + |─ [ title ] + | | details | + | |_____________| + + or (B): + └─ [ title ] + | details | + |_____________| + + Parameters + ---------- + r: str + HTML representation to wrap. + end: bool + Specify if the line on the left should continue or end. + + Default is True. + + Returns + ------- + str + Wrapped HTML representation. + + Tee color is set to the variable :code:`--xr-border-color`. + """ + # height of line + end = bool(end) + height = "100%" if end is False else "1.2em" + return "".join( + [ + "
", + "
", + "
", + "
", + "
", + "
", + "
    ", + r, + "
" "
", + "
", + ] + ) + + +def datatree_repr(dt: Any) -> str: + obj_type = f"datatree.{type(dt).__name__}" + return node_repr(obj_type, dt) diff --git a/xarray/tests/test_formatting_html.py b/xarray/tests/test_formatting_html.py index 6540406e914..61abaa24b74 100644 --- a/xarray/tests/test_formatting_html.py +++ b/xarray/tests/test_formatting_html.py @@ -7,6 +7,7 @@ import xarray as xr from xarray.core import formatting_html as fh from xarray.core.coordinates import Coordinates +from xarray.core.datatree import DataTree @pytest.fixture @@ -196,3 +197,196 @@ def test_nonstr_variable_repr_html() -> None: html = v._repr_html_().strip() assert "
22 :
bar
" in html assert "
  • 10: 3
  • " in html + + +@pytest.fixture(scope="module", params=["some html", "some other html"]) +def repr(request): + return request.param + + +class Test_summarize_children: + """ + Unit tests for summarize_children. + """ + + func = staticmethod(fh.summarize_children) + + @pytest.fixture(scope="class") + def childfree_tree_factory(self): + """ + Fixture for a child-free DataTree factory. + """ + from random import randint + + def _childfree_tree_factory(): + return DataTree( + data=xr.Dataset({"z": ("y", [randint(1, 100) for _ in range(3)])}) + ) + + return _childfree_tree_factory + + @pytest.fixture(scope="class") + def childfree_tree(self, childfree_tree_factory): + """ + Fixture for a child-free DataTree. + """ + return childfree_tree_factory() + + @pytest.fixture(scope="function") + def mock_node_repr(self, monkeypatch): + """ + Apply mocking for node_repr. + """ + + def mock(group_title, dt): + """ + Mock with a simple result + """ + return group_title + " " + str(id(dt)) + + monkeypatch.setattr(fh, "node_repr", mock) + + @pytest.fixture(scope="function") + def mock_wrap_repr(self, monkeypatch): + """ + Apply mocking for _wrap_repr. + """ + + def mock(r, *, end, **kwargs): + """ + Mock by appending "end" or "not end". + """ + return r + " " + ("end" if end else "not end") + "//" + + monkeypatch.setattr(fh, "_wrap_repr", mock) + + def test_empty_mapping(self): + """ + Test with an empty mapping of children. + """ + children = {} + assert self.func(children) == ( + "
    " "
    " + ) + + def test_one_child(self, childfree_tree, mock_wrap_repr, mock_node_repr): + """ + Test with one child. + + Uses a mock of _wrap_repr and node_repr to essentially mock + the inline lambda function "lines_callback". + """ + # Create mapping of children + children = {"a": childfree_tree} + + # Expect first line to be produced from the first child, and + # wrapped as the last child + first_line = f"a {id(children['a'])} end//" + + assert self.func(children) == ( + "
    " + f"{first_line}" + "
    " + ) + + def test_two_children(self, childfree_tree_factory, mock_wrap_repr, mock_node_repr): + """ + Test with two level deep children. + + Uses a mock of _wrap_repr and node_repr to essentially mock + the inline lambda function "lines_callback". + """ + + # Create mapping of children + children = {"a": childfree_tree_factory(), "b": childfree_tree_factory()} + + # Expect first line to be produced from the first child, and + # wrapped as _not_ the last child + first_line = f"a {id(children['a'])} not end//" + + # Expect second line to be produced from the second child, and + # wrapped as the last child + second_line = f"b {id(children['b'])} end//" + + assert self.func(children) == ( + "
    " + f"{first_line}" + f"{second_line}" + "
    " + ) + + +class Test__wrap_repr: + """ + Unit tests for _wrap_repr. + """ + + func = staticmethod(fh._wrap_repr) + + def test_end(self, repr): + """ + Test with end=True. + """ + r = self.func(repr, end=True) + assert r == ( + "
    " + "
    " + "
    " + "
    " + "
    " + "
    " + "
      " + f"{repr}" + "
    " + "
    " + "
    " + ) + + def test_not_end(self, repr): + """ + Test with end=False. + """ + r = self.func(repr, end=False) + assert r == ( + "
    " + "
    " + "
    " + "
    " + "
    " + "
    " + "
      " + f"{repr}" + "
    " + "
    " + "
    " + ) From 1af00bc2955ef86bafa62e1b07968c8b87f4ac42 Mon Sep 17 00:00:00 2001 From: Olufunke Awowale Date: Tue, 2 Apr 2024 12:25:46 -0400 Subject: [PATCH 02/13] Revert "DAS-2066: formatting_html.py merge" oopsies This reverts commit d68ae3c823bbbe26c918605a388464b5ba2309c2. --- xarray/core/formatting_html.py | 131 ------------------ xarray/tests/test_formatting_html.py | 194 --------------------------- 2 files changed, 325 deletions(-) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index 384b6f09a2a..2c76b182207 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -2,11 +2,9 @@ import uuid from collections import OrderedDict -from collections.abc import Mapping from functools import lru_cache, partial from html import escape from importlib.resources import files -from typing import Any from xarray.core.formatting import ( inline_index_repr, @@ -20,10 +18,6 @@ ("xarray.static.css", "style.css"), ) -from xarray.core.options import OPTIONS - -OPTIONS["display_expand_groups"] = "default" - @lru_cache(None) def _load_static_files(): @@ -347,128 +341,3 @@ def dataset_repr(ds) -> str: ] return _obj_repr(ds, header_components, sections) - - -def summarize_children(children: Mapping[str, Any]) -> str: - N_CHILDREN = len(children) - 1 - - # Get result from node_repr and wrap it - lines_callback = lambda n, c, end: _wrap_repr(node_repr(n, c), end=end) - - children_html = "".join( - ( - lines_callback(n, c, end=False) # Long lines - if i < N_CHILDREN - else lines_callback(n, c, end=True) - ) # Short lines - for i, (n, c) in enumerate(children.items()) - ) - - return "".join( - [ - "
    ", - children_html, - "
    ", - ] - ) - - -children_section = partial( - _mapping_section, - name="Groups", - details_func=summarize_children, - max_items_collapse=1, - expand_option_name="display_expand_groups", -) - - -def node_repr(group_title: str, dt: Any) -> str: - header_components = [f"
    {escape(group_title)}
    "] - - ds = dt.ds - - sections = [ - children_section(dt.children), - dim_section(ds), - coord_section(ds.coords), - datavar_section(ds.data_vars), - attr_section(ds.attrs), - ] - - return _obj_repr(ds, header_components, sections) - - -def _wrap_repr(r: str, end: bool = False) -> str: - """ - Wrap HTML representation with a tee to the left of it. - - Enclosing HTML tag is a
    with :code:`display: inline-grid` style. - - Turns: - [ title ] - | details | - |_____________| - - into (A): - |─ [ title ] - | | details | - | |_____________| - - or (B): - └─ [ title ] - | details | - |_____________| - - Parameters - ---------- - r: str - HTML representation to wrap. - end: bool - Specify if the line on the left should continue or end. - - Default is True. - - Returns - ------- - str - Wrapped HTML representation. - - Tee color is set to the variable :code:`--xr-border-color`. - """ - # height of line - end = bool(end) - height = "100%" if end is False else "1.2em" - return "".join( - [ - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - "
      ", - r, - "
    " "
    ", - "
    ", - ] - ) - - -def datatree_repr(dt: Any) -> str: - obj_type = f"datatree.{type(dt).__name__}" - return node_repr(obj_type, dt) diff --git a/xarray/tests/test_formatting_html.py b/xarray/tests/test_formatting_html.py index 61abaa24b74..6540406e914 100644 --- a/xarray/tests/test_formatting_html.py +++ b/xarray/tests/test_formatting_html.py @@ -7,7 +7,6 @@ import xarray as xr from xarray.core import formatting_html as fh from xarray.core.coordinates import Coordinates -from xarray.core.datatree import DataTree @pytest.fixture @@ -197,196 +196,3 @@ def test_nonstr_variable_repr_html() -> None: html = v._repr_html_().strip() assert "
    22 :
    bar
    " in html assert "
  • 10: 3
  • " in html - - -@pytest.fixture(scope="module", params=["some html", "some other html"]) -def repr(request): - return request.param - - -class Test_summarize_children: - """ - Unit tests for summarize_children. - """ - - func = staticmethod(fh.summarize_children) - - @pytest.fixture(scope="class") - def childfree_tree_factory(self): - """ - Fixture for a child-free DataTree factory. - """ - from random import randint - - def _childfree_tree_factory(): - return DataTree( - data=xr.Dataset({"z": ("y", [randint(1, 100) for _ in range(3)])}) - ) - - return _childfree_tree_factory - - @pytest.fixture(scope="class") - def childfree_tree(self, childfree_tree_factory): - """ - Fixture for a child-free DataTree. - """ - return childfree_tree_factory() - - @pytest.fixture(scope="function") - def mock_node_repr(self, monkeypatch): - """ - Apply mocking for node_repr. - """ - - def mock(group_title, dt): - """ - Mock with a simple result - """ - return group_title + " " + str(id(dt)) - - monkeypatch.setattr(fh, "node_repr", mock) - - @pytest.fixture(scope="function") - def mock_wrap_repr(self, monkeypatch): - """ - Apply mocking for _wrap_repr. - """ - - def mock(r, *, end, **kwargs): - """ - Mock by appending "end" or "not end". - """ - return r + " " + ("end" if end else "not end") + "//" - - monkeypatch.setattr(fh, "_wrap_repr", mock) - - def test_empty_mapping(self): - """ - Test with an empty mapping of children. - """ - children = {} - assert self.func(children) == ( - "
    " "
    " - ) - - def test_one_child(self, childfree_tree, mock_wrap_repr, mock_node_repr): - """ - Test with one child. - - Uses a mock of _wrap_repr and node_repr to essentially mock - the inline lambda function "lines_callback". - """ - # Create mapping of children - children = {"a": childfree_tree} - - # Expect first line to be produced from the first child, and - # wrapped as the last child - first_line = f"a {id(children['a'])} end//" - - assert self.func(children) == ( - "
    " - f"{first_line}" - "
    " - ) - - def test_two_children(self, childfree_tree_factory, mock_wrap_repr, mock_node_repr): - """ - Test with two level deep children. - - Uses a mock of _wrap_repr and node_repr to essentially mock - the inline lambda function "lines_callback". - """ - - # Create mapping of children - children = {"a": childfree_tree_factory(), "b": childfree_tree_factory()} - - # Expect first line to be produced from the first child, and - # wrapped as _not_ the last child - first_line = f"a {id(children['a'])} not end//" - - # Expect second line to be produced from the second child, and - # wrapped as the last child - second_line = f"b {id(children['b'])} end//" - - assert self.func(children) == ( - "
    " - f"{first_line}" - f"{second_line}" - "
    " - ) - - -class Test__wrap_repr: - """ - Unit tests for _wrap_repr. - """ - - func = staticmethod(fh._wrap_repr) - - def test_end(self, repr): - """ - Test with end=True. - """ - r = self.func(repr, end=True) - assert r == ( - "
    " - "
    " - "
    " - "
    " - "
    " - "
    " - "
      " - f"{repr}" - "
    " - "
    " - "
    " - ) - - def test_not_end(self, repr): - """ - Test with end=False. - """ - r = self.func(repr, end=False) - assert r == ( - "
    " - "
    " - "
    " - "
    " - "
    " - "
    " - "
      " - f"{repr}" - "
    " - "
    " - "
    " - ) From 015e43f30af3343661c5c4028afead66b4b10ebf Mon Sep 17 00:00:00 2001 From: Olufunke Awowale Date: Wed, 3 Apr 2024 18:05:16 -0400 Subject: [PATCH 03/13] Adding datatree functions to core --- xarray/core/formatting_html.py | 127 ++++++++++++++++++ xarray/tests/test_formatting_html.py | 194 +++++++++++++++++++++++++++ 2 files changed, 321 insertions(+) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index 2c76b182207..6c6af07fd2c 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -2,9 +2,11 @@ import uuid from collections import OrderedDict +from collections.abc import Mapping from functools import lru_cache, partial from html import escape from importlib.resources import files +from typing import Any from xarray.core.formatting import ( inline_index_repr, @@ -341,3 +343,128 @@ def dataset_repr(ds) -> str: ] return _obj_repr(ds, header_components, sections) + + +def summarize_children(children: Mapping[str, Any]) -> str: + N_CHILDREN = len(children) - 1 + + # Get result from node_repr and wrap it + lines_callback = lambda n, c, end: _wrap_repr(node_repr(n, c), end=end) + + children_html = "".join( + ( + lines_callback(n, c, end=False) # Long lines + if i < N_CHILDREN + else lines_callback(n, c, end=True) + ) # Short lines + for i, (n, c) in enumerate(children.items()) + ) + + return "".join( + [ + "
    ", + children_html, + "
    ", + ] + ) + + +children_section = partial( + _mapping_section, + name="Groups", + details_func=summarize_children, + max_items_collapse=1, + expand_option_name="display_expand_groups", +) + + +def node_repr(group_title: str, dt: Any) -> str: + header_components = [f"
    {escape(group_title)}
    "] + + ds = dt.ds + + sections = [ + children_section(dt.children), + dim_section(ds), + coord_section(ds.coords), + datavar_section(ds.data_vars), + attr_section(ds.attrs), + ] + + return _obj_repr(ds, header_components, sections) + + +def _wrap_repr(r: str, end: bool = False) -> str: + """ + Wrap HTML representation with a tee to the left of it. + + Enclosing HTML tag is a
    with :code:`display: inline-grid` style. + + Turns: + [ title ] + | details | + |_____________| + + into (A): + |─ [ title ] + | | details | + | |_____________| + + or (B): + └─ [ title ] + | details | + |_____________| + + Parameters + ---------- + r: str + HTML representation to wrap. + end: bool + Specify if the line on the left should continue or end. + + Default is True. + + Returns + ------- + str + Wrapped HTML representation. + + Tee color is set to the variable :code:`--xr-border-color`. + """ + # height of line + end = bool(end) + height = "100%" if end is False else "1.2em" + return "".join( + [ + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
      ", + r, + "
    " "
    ", + "
    ", + ] + ) + + +def datatree_repr(dt: Any) -> str: + obj_type = f"datatree.{type(dt).__name__}" + return node_repr(obj_type, dt) diff --git a/xarray/tests/test_formatting_html.py b/xarray/tests/test_formatting_html.py index 6540406e914..61abaa24b74 100644 --- a/xarray/tests/test_formatting_html.py +++ b/xarray/tests/test_formatting_html.py @@ -7,6 +7,7 @@ import xarray as xr from xarray.core import formatting_html as fh from xarray.core.coordinates import Coordinates +from xarray.core.datatree import DataTree @pytest.fixture @@ -196,3 +197,196 @@ def test_nonstr_variable_repr_html() -> None: html = v._repr_html_().strip() assert "
    22 :
    bar
    " in html assert "
  • 10: 3
  • " in html + + +@pytest.fixture(scope="module", params=["some html", "some other html"]) +def repr(request): + return request.param + + +class Test_summarize_children: + """ + Unit tests for summarize_children. + """ + + func = staticmethod(fh.summarize_children) + + @pytest.fixture(scope="class") + def childfree_tree_factory(self): + """ + Fixture for a child-free DataTree factory. + """ + from random import randint + + def _childfree_tree_factory(): + return DataTree( + data=xr.Dataset({"z": ("y", [randint(1, 100) for _ in range(3)])}) + ) + + return _childfree_tree_factory + + @pytest.fixture(scope="class") + def childfree_tree(self, childfree_tree_factory): + """ + Fixture for a child-free DataTree. + """ + return childfree_tree_factory() + + @pytest.fixture(scope="function") + def mock_node_repr(self, monkeypatch): + """ + Apply mocking for node_repr. + """ + + def mock(group_title, dt): + """ + Mock with a simple result + """ + return group_title + " " + str(id(dt)) + + monkeypatch.setattr(fh, "node_repr", mock) + + @pytest.fixture(scope="function") + def mock_wrap_repr(self, monkeypatch): + """ + Apply mocking for _wrap_repr. + """ + + def mock(r, *, end, **kwargs): + """ + Mock by appending "end" or "not end". + """ + return r + " " + ("end" if end else "not end") + "//" + + monkeypatch.setattr(fh, "_wrap_repr", mock) + + def test_empty_mapping(self): + """ + Test with an empty mapping of children. + """ + children = {} + assert self.func(children) == ( + "
    " "
    " + ) + + def test_one_child(self, childfree_tree, mock_wrap_repr, mock_node_repr): + """ + Test with one child. + + Uses a mock of _wrap_repr and node_repr to essentially mock + the inline lambda function "lines_callback". + """ + # Create mapping of children + children = {"a": childfree_tree} + + # Expect first line to be produced from the first child, and + # wrapped as the last child + first_line = f"a {id(children['a'])} end//" + + assert self.func(children) == ( + "
    " + f"{first_line}" + "
    " + ) + + def test_two_children(self, childfree_tree_factory, mock_wrap_repr, mock_node_repr): + """ + Test with two level deep children. + + Uses a mock of _wrap_repr and node_repr to essentially mock + the inline lambda function "lines_callback". + """ + + # Create mapping of children + children = {"a": childfree_tree_factory(), "b": childfree_tree_factory()} + + # Expect first line to be produced from the first child, and + # wrapped as _not_ the last child + first_line = f"a {id(children['a'])} not end//" + + # Expect second line to be produced from the second child, and + # wrapped as the last child + second_line = f"b {id(children['b'])} end//" + + assert self.func(children) == ( + "
    " + f"{first_line}" + f"{second_line}" + "
    " + ) + + +class Test__wrap_repr: + """ + Unit tests for _wrap_repr. + """ + + func = staticmethod(fh._wrap_repr) + + def test_end(self, repr): + """ + Test with end=True. + """ + r = self.func(repr, end=True) + assert r == ( + "
    " + "
    " + "
    " + "
    " + "
    " + "
    " + "
      " + f"{repr}" + "
    " + "
    " + "
    " + ) + + def test_not_end(self, repr): + """ + Test with end=False. + """ + r = self.func(repr, end=False) + assert r == ( + "
    " + "
    " + "
    " + "
    " + "
    " + "
    " + "
      " + f"{repr}" + "
    " + "
    " + "
    " + ) From 3034b35722a5d388d85736c23be7954aef9ecce2 Mon Sep 17 00:00:00 2001 From: Olufunke Awowale Date: Thu, 4 Apr 2024 14:52:07 -0400 Subject: [PATCH 04/13] Deleted formatting_html.py and test_formatting_html.py in datatree_ --- xarray/core/datatree.py | 6 +- xarray/datatree_/datatree/formatting_html.py | 135 ------------ .../datatree/tests/test_formatting_html.py | 198 ------------------ 3 files changed, 3 insertions(+), 336 deletions(-) delete mode 100644 xarray/datatree_/datatree/formatting_html.py delete mode 100644 xarray/datatree_/datatree/tests/test_formatting_html.py diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index 1b06d87c9fb..96b2779be3c 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -18,6 +18,9 @@ from xarray.core.coordinates import DatasetCoordinates from xarray.core.dataarray import DataArray from xarray.core.dataset import Dataset, DataVariables +from xarray.core.formatting_html import ( + datatree_repr as datatree_repr_html, +) from xarray.core.indexes import Index, Indexes from xarray.core.merge import dataset_update_method from xarray.core.options import OPTIONS as XR_OPTS @@ -33,9 +36,6 @@ from xarray.core.variable import Variable from xarray.datatree_.datatree.common import TreeAttrAccessMixin from xarray.datatree_.datatree.formatting import datatree_repr -from xarray.datatree_.datatree.formatting_html import ( - datatree_repr as datatree_repr_html, -) from xarray.datatree_.datatree.mapping import ( TreeIsomorphismError, check_isomorphic, diff --git a/xarray/datatree_/datatree/formatting_html.py b/xarray/datatree_/datatree/formatting_html.py deleted file mode 100644 index 547b567a396..00000000000 --- a/xarray/datatree_/datatree/formatting_html.py +++ /dev/null @@ -1,135 +0,0 @@ -from functools import partial -from html import escape -from typing import Any, Mapping - -from xarray.core.formatting_html import ( - _mapping_section, - _obj_repr, - attr_section, - coord_section, - datavar_section, - dim_section, -) - - -def summarize_children(children: Mapping[str, Any]) -> str: - N_CHILDREN = len(children) - 1 - - # Get result from node_repr and wrap it - lines_callback = lambda n, c, end: _wrap_repr(node_repr(n, c), end=end) - - children_html = "".join( - lines_callback(n, c, end=False) # Long lines - if i < N_CHILDREN - else lines_callback(n, c, end=True) # Short lines - for i, (n, c) in enumerate(children.items()) - ) - - return "".join( - [ - "
    ", - children_html, - "
    ", - ] - ) - - -children_section = partial( - _mapping_section, - name="Groups", - details_func=summarize_children, - max_items_collapse=1, - expand_option_name="display_expand_groups", -) - - -def node_repr(group_title: str, dt: Any) -> str: - header_components = [f"
    {escape(group_title)}
    "] - - ds = dt.ds - - sections = [ - children_section(dt.children), - dim_section(ds), - coord_section(ds.coords), - datavar_section(ds.data_vars), - attr_section(ds.attrs), - ] - - return _obj_repr(ds, header_components, sections) - - -def _wrap_repr(r: str, end: bool = False) -> str: - """ - Wrap HTML representation with a tee to the left of it. - - Enclosing HTML tag is a
    with :code:`display: inline-grid` style. - - Turns: - [ title ] - | details | - |_____________| - - into (A): - |─ [ title ] - | | details | - | |_____________| - - or (B): - └─ [ title ] - | details | - |_____________| - - Parameters - ---------- - r: str - HTML representation to wrap. - end: bool - Specify if the line on the left should continue or end. - - Default is True. - - Returns - ------- - str - Wrapped HTML representation. - - Tee color is set to the variable :code:`--xr-border-color`. - """ - # height of line - end = bool(end) - height = "100%" if end is False else "1.2em" - return "".join( - [ - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - "
      ", - r, - "
    " "
    ", - "
    ", - ] - ) - - -def datatree_repr(dt: Any) -> str: - obj_type = f"datatree.{type(dt).__name__}" - return node_repr(obj_type, dt) diff --git a/xarray/datatree_/datatree/tests/test_formatting_html.py b/xarray/datatree_/datatree/tests/test_formatting_html.py deleted file mode 100644 index 98cdf02bff4..00000000000 --- a/xarray/datatree_/datatree/tests/test_formatting_html.py +++ /dev/null @@ -1,198 +0,0 @@ -import pytest -import xarray as xr - -from xarray.core.datatree import DataTree -from xarray.datatree_.datatree import formatting_html - - -@pytest.fixture(scope="module", params=["some html", "some other html"]) -def repr(request): - return request.param - - -class Test_summarize_children: - """ - Unit tests for summarize_children. - """ - - func = staticmethod(formatting_html.summarize_children) - - @pytest.fixture(scope="class") - def childfree_tree_factory(self): - """ - Fixture for a child-free DataTree factory. - """ - from random import randint - - def _childfree_tree_factory(): - return DataTree( - data=xr.Dataset({"z": ("y", [randint(1, 100) for _ in range(3)])}) - ) - - return _childfree_tree_factory - - @pytest.fixture(scope="class") - def childfree_tree(self, childfree_tree_factory): - """ - Fixture for a child-free DataTree. - """ - return childfree_tree_factory() - - @pytest.fixture(scope="function") - def mock_node_repr(self, monkeypatch): - """ - Apply mocking for node_repr. - """ - - def mock(group_title, dt): - """ - Mock with a simple result - """ - return group_title + " " + str(id(dt)) - - monkeypatch.setattr(formatting_html, "node_repr", mock) - - @pytest.fixture(scope="function") - def mock_wrap_repr(self, monkeypatch): - """ - Apply mocking for _wrap_repr. - """ - - def mock(r, *, end, **kwargs): - """ - Mock by appending "end" or "not end". - """ - return r + " " + ("end" if end else "not end") + "//" - - monkeypatch.setattr(formatting_html, "_wrap_repr", mock) - - def test_empty_mapping(self): - """ - Test with an empty mapping of children. - """ - children = {} - assert self.func(children) == ( - "
    " "
    " - ) - - def test_one_child(self, childfree_tree, mock_wrap_repr, mock_node_repr): - """ - Test with one child. - - Uses a mock of _wrap_repr and node_repr to essentially mock - the inline lambda function "lines_callback". - """ - # Create mapping of children - children = {"a": childfree_tree} - - # Expect first line to be produced from the first child, and - # wrapped as the last child - first_line = f"a {id(children['a'])} end//" - - assert self.func(children) == ( - "
    " - f"{first_line}" - "
    " - ) - - def test_two_children(self, childfree_tree_factory, mock_wrap_repr, mock_node_repr): - """ - Test with two level deep children. - - Uses a mock of _wrap_repr and node_repr to essentially mock - the inline lambda function "lines_callback". - """ - - # Create mapping of children - children = {"a": childfree_tree_factory(), "b": childfree_tree_factory()} - - # Expect first line to be produced from the first child, and - # wrapped as _not_ the last child - first_line = f"a {id(children['a'])} not end//" - - # Expect second line to be produced from the second child, and - # wrapped as the last child - second_line = f"b {id(children['b'])} end//" - - assert self.func(children) == ( - "
    " - f"{first_line}" - f"{second_line}" - "
    " - ) - - -class Test__wrap_repr: - """ - Unit tests for _wrap_repr. - """ - - func = staticmethod(formatting_html._wrap_repr) - - def test_end(self, repr): - """ - Test with end=True. - """ - r = self.func(repr, end=True) - assert r == ( - "
    " - "
    " - "
    " - "
    " - "
    " - "
    " - "
      " - f"{repr}" - "
    " - "
    " - "
    " - ) - - def test_not_end(self, repr): - """ - Test with end=False. - """ - r = self.func(repr, end=False) - assert r == ( - "
    " - "
    " - "
    " - "
    " - "
    " - "
    " - "
      " - f"{repr}" - "
    " - "
    " - "
    " - ) From e16a01c6372b88220ec397dfb4fbc45d7b6622d5 Mon Sep 17 00:00:00 2001 From: Olufunke Awowale Date: Fri, 5 Apr 2024 13:55:27 -0400 Subject: [PATCH 05/13] renamed all of the relevant functions so they represent datatree --- xarray/core/formatting_html.py | 16 +++++++------ xarray/tests/test_formatting_html.py | 36 +++++++++++++++------------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index 6c6af07fd2c..bef0ab03b6a 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -345,11 +345,13 @@ def dataset_repr(ds) -> str: return _obj_repr(ds, header_components, sections) -def summarize_children(children: Mapping[str, Any]) -> str: +def summarize_datatree_children(children: Mapping[str, Any]) -> str: N_CHILDREN = len(children) - 1 - # Get result from node_repr and wrap it - lines_callback = lambda n, c, end: _wrap_repr(node_repr(n, c), end=end) + # Get result from datatree_node_repr and wrap it + lines_callback = lambda n, c, end: _wrap_datatree_repr( + datatree_node_repr(n, c), end=end + ) children_html = "".join( ( @@ -372,13 +374,13 @@ def summarize_children(children: Mapping[str, Any]) -> str: children_section = partial( _mapping_section, name="Groups", - details_func=summarize_children, + details_func=summarize_datatree_children, max_items_collapse=1, expand_option_name="display_expand_groups", ) -def node_repr(group_title: str, dt: Any) -> str: +def datatree_node_repr(group_title: str, dt: Any) -> str: header_components = [f"
    {escape(group_title)}
    "] ds = dt.ds @@ -394,7 +396,7 @@ def node_repr(group_title: str, dt: Any) -> str: return _obj_repr(ds, header_components, sections) -def _wrap_repr(r: str, end: bool = False) -> str: +def _wrap_datatree_repr(r: str, end: bool = False) -> str: """ Wrap HTML representation with a tee to the left of it. @@ -467,4 +469,4 @@ def _wrap_repr(r: str, end: bool = False) -> str: def datatree_repr(dt: Any) -> str: obj_type = f"datatree.{type(dt).__name__}" - return node_repr(obj_type, dt) + return datatree_node_repr(obj_type, dt) diff --git a/xarray/tests/test_formatting_html.py b/xarray/tests/test_formatting_html.py index 61abaa24b74..e546bb9a645 100644 --- a/xarray/tests/test_formatting_html.py +++ b/xarray/tests/test_formatting_html.py @@ -204,12 +204,12 @@ def repr(request): return request.param -class Test_summarize_children: +class Test_summarize_datatree_children: """ - Unit tests for summarize_children. + Unit tests for summarize_datatree_children. """ - func = staticmethod(fh.summarize_children) + func = staticmethod(fh.summarize_datatree_children) @pytest.fixture(scope="class") def childfree_tree_factory(self): @@ -233,9 +233,9 @@ def childfree_tree(self, childfree_tree_factory): return childfree_tree_factory() @pytest.fixture(scope="function") - def mock_node_repr(self, monkeypatch): + def mock_datatree_node_repr(self, monkeypatch): """ - Apply mocking for node_repr. + Apply mocking for datatree_node_repr. """ def mock(group_title, dt): @@ -244,12 +244,12 @@ def mock(group_title, dt): """ return group_title + " " + str(id(dt)) - monkeypatch.setattr(fh, "node_repr", mock) + monkeypatch.setattr(fh, "datatree_node_repr", mock) @pytest.fixture(scope="function") - def mock_wrap_repr(self, monkeypatch): + def mock_wrap_datatree_repr(self, monkeypatch): """ - Apply mocking for _wrap_repr. + Apply mocking for _wrap_datatree_repr. """ def mock(r, *, end, **kwargs): @@ -258,7 +258,7 @@ def mock(r, *, end, **kwargs): """ return r + " " + ("end" if end else "not end") + "//" - monkeypatch.setattr(fh, "_wrap_repr", mock) + monkeypatch.setattr(fh, "_wrap_datatree_repr", mock) def test_empty_mapping(self): """ @@ -269,11 +269,13 @@ def test_empty_mapping(self): "
    " "
    " ) - def test_one_child(self, childfree_tree, mock_wrap_repr, mock_node_repr): + def test_one_child( + self, childfree_tree, mock_wrap_datatree_repr, mock_datatree_node_repr + ): """ Test with one child. - Uses a mock of _wrap_repr and node_repr to essentially mock + Uses a mock of _wrap_datatree_repr and _datatree_node_repr to essentially mock the inline lambda function "lines_callback". """ # Create mapping of children @@ -289,11 +291,13 @@ def test_one_child(self, childfree_tree, mock_wrap_repr, mock_node_repr): "
    " ) - def test_two_children(self, childfree_tree_factory, mock_wrap_repr, mock_node_repr): + def test_two_children( + self, childfree_tree_factory, mock_wrap_datatree_repr, mock_datatree_node_repr + ): """ Test with two level deep children. - Uses a mock of _wrap_repr and node_repr to essentially mock + Uses a mock of _wrap_datatree_repr and datatree_node_repr to essentially mock the inline lambda function "lines_callback". """ @@ -316,12 +320,12 @@ def test_two_children(self, childfree_tree_factory, mock_wrap_repr, mock_node_re ) -class Test__wrap_repr: +class Test__wrap_datatree_repr: """ - Unit tests for _wrap_repr. + Unit tests for _wrap_datatree_repr. """ - func = staticmethod(fh._wrap_repr) + func = staticmethod(fh._wrap_datatree_repr) def test_end(self, repr): """ From a5e143ae762565004f00728e902c9d8feb91d9db Mon Sep 17 00:00:00 2001 From: Olufunke Awowale Date: Thu, 11 Apr 2024 12:32:30 -0400 Subject: [PATCH 06/13] added comment for mypy --- xarray/tests/test_formatting_html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_formatting_html.py b/xarray/tests/test_formatting_html.py index e546bb9a645..dac2d89fc00 100644 --- a/xarray/tests/test_formatting_html.py +++ b/xarray/tests/test_formatting_html.py @@ -264,7 +264,7 @@ def test_empty_mapping(self): """ Test with an empty mapping of children. """ - children = {} + children = {} # type: Dict[Any, Any] assert self.func(children) == ( "
    " "
    " ) From 4313d152ef24b186356f52555959dbed5f145a1f Mon Sep 17 00:00:00 2001 From: Olufunke Awowale Date: Thu, 11 Apr 2024 12:50:36 -0400 Subject: [PATCH 07/13] added dict[any, any] as type hint for children --- xarray/tests/test_formatting_html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_formatting_html.py b/xarray/tests/test_formatting_html.py index dac2d89fc00..2f2c8743166 100644 --- a/xarray/tests/test_formatting_html.py +++ b/xarray/tests/test_formatting_html.py @@ -264,7 +264,7 @@ def test_empty_mapping(self): """ Test with an empty mapping of children. """ - children = {} # type: Dict[Any, Any] + children: dict[any, any] = {} assert self.func(children) == ( "
    " "
    " ) From 6ce9211461e9b68a256eb44c0fda333bf7164815 Mon Sep 17 00:00:00 2001 From: Olufunke Awowale Date: Thu, 11 Apr 2024 12:57:45 -0400 Subject: [PATCH 08/13] added dict[Any, Any] as type hint for children --- xarray/tests/test_formatting_html.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_formatting_html.py b/xarray/tests/test_formatting_html.py index 2f2c8743166..470b6325475 100644 --- a/xarray/tests/test_formatting_html.py +++ b/xarray/tests/test_formatting_html.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Any + import numpy as np import pandas as pd import pytest @@ -264,7 +266,7 @@ def test_empty_mapping(self): """ Test with an empty mapping of children. """ - children: dict[any, any] = {} + children: dict[Any, Any] = {} assert self.func(children) == ( "
    " "
    " ) From 2d8f1047adf3c114b130d182b378de89d03c686f Mon Sep 17 00:00:00 2001 From: Olufunke Awowale Date: Thu, 11 Apr 2024 15:03:48 -0400 Subject: [PATCH 09/13] updated typhint to dict[str, DataTree] and increased pixel min width to 700 --- xarray/static/css/style.css | 2 +- xarray/tests/test_formatting_html.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/xarray/static/css/style.css b/xarray/static/css/style.css index e0a51312b10..0079e7d3467 100644 --- a/xarray/static/css/style.css +++ b/xarray/static/css/style.css @@ -28,7 +28,7 @@ body.vscode-dark { .xr-wrap { display: block !important; - min-width: 300px; + min-width: 700px; max-width: 700px; } diff --git a/xarray/tests/test_formatting_html.py b/xarray/tests/test_formatting_html.py index 470b6325475..a1b30db60dd 100644 --- a/xarray/tests/test_formatting_html.py +++ b/xarray/tests/test_formatting_html.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Any - import numpy as np import pandas as pd import pytest @@ -266,7 +264,7 @@ def test_empty_mapping(self): """ Test with an empty mapping of children. """ - children: dict[Any, Any] = {} + children: dict[str, DataTree] = {} assert self.func(children) == ( "
    " "
    " ) From 4e1def6113373dab7a5797a318bc66031189f30f Mon Sep 17 00:00:00 2001 From: Olufunke Awowale Date: Fri, 12 Apr 2024 11:25:07 -0400 Subject: [PATCH 10/13] replaced Any with DataTree --- xarray/core/formatting_html.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index bef0ab03b6a..fa1097b087a 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -6,8 +6,8 @@ from functools import lru_cache, partial from html import escape from importlib.resources import files -from typing import Any +from xarray.core.datatree import DataTree from xarray.core.formatting import ( inline_index_repr, inline_variable_array_repr, @@ -345,7 +345,7 @@ def dataset_repr(ds) -> str: return _obj_repr(ds, header_components, sections) -def summarize_datatree_children(children: Mapping[str, Any]) -> str: +def summarize_datatree_children(children: Mapping[str, DataTree]) -> str: N_CHILDREN = len(children) - 1 # Get result from datatree_node_repr and wrap it @@ -380,7 +380,7 @@ def summarize_datatree_children(children: Mapping[str, Any]) -> str: ) -def datatree_node_repr(group_title: str, dt: Any) -> str: +def datatree_node_repr(group_title: str, dt: DataTree) -> str: header_components = [f"
    {escape(group_title)}
    "] ds = dt.ds @@ -467,6 +467,6 @@ def _wrap_datatree_repr(r: str, end: bool = False) -> str: ) -def datatree_repr(dt: Any) -> str: +def datatree_repr(dt: DataTree) -> str: obj_type = f"datatree.{type(dt).__name__}" return datatree_node_repr(obj_type, dt) From 33fb7deb636ff4b12d5e000629897c54fd4a90dc Mon Sep 17 00:00:00 2001 From: Olufunke Awowale Date: Fri, 12 Apr 2024 12:43:38 -0400 Subject: [PATCH 11/13] added type checking --- xarray/core/formatting_html.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index fa1097b087a..2efe2b96d76 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -6,8 +6,8 @@ from functools import lru_cache, partial from html import escape from importlib.resources import files +from typing import TYPE_CHECKING -from xarray.core.datatree import DataTree from xarray.core.formatting import ( inline_index_repr, inline_variable_array_repr, @@ -20,6 +20,9 @@ ("xarray.static.css", "style.css"), ) +if TYPE_CHECKING: + from xarray.core.datatree import DataTree + @lru_cache(None) def _load_static_files(): From bf1807549abcb9e0253e679a6ca69df090a673a3 Mon Sep 17 00:00:00 2001 From: Owen Littlejohns Date: Wed, 17 Apr 2024 13:42:07 -0400 Subject: [PATCH 12/13] DAS-2066 - Incorporate CSS changes for DataTree display. --- xarray/core/formatting_html.py | 7 +++---- xarray/static/css/style.css | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index 2efe2b96d76..9bf5befbe3f 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -367,7 +367,7 @@ def summarize_datatree_children(children: Mapping[str, DataTree]) -> str: return "".join( [ - "
    ", + "
    ", children_html, "
    ", ] @@ -441,7 +441,7 @@ def _wrap_datatree_repr(r: str, end: bool = False) -> str: height = "100%" if end is False else "1.2em" return "".join( [ - "
    ", + "
    ", "
    ", - "
      ", r, - "
    " "
    ", + "
    ", "
    ", ] ) diff --git a/xarray/static/css/style.css b/xarray/static/css/style.css index 0079e7d3467..e0a51312b10 100644 --- a/xarray/static/css/style.css +++ b/xarray/static/css/style.css @@ -28,7 +28,7 @@ body.vscode-dark { .xr-wrap { display: block !important; - min-width: 700px; + min-width: 300px; max-width: 700px; } From fdf53f5d290c1e1e98197ef5046ce07eecb3e87b Mon Sep 17 00:00:00 2001 From: Owen Littlejohns Date: Wed, 17 Apr 2024 14:22:35 -0400 Subject: [PATCH 13/13] DAS-2066 - Fix tests. --- doc/whats-new.rst | 3 +++ xarray/tests/test_formatting_html.py | 15 ++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 41b39bf2b1a..759b12c11da 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -34,6 +34,9 @@ Bug fixes Internal Changes ~~~~~~~~~~~~~~~~ +- Migrates ``formatting_html`` functionality for `DataTree` into ``xarray/core`` (:pull: `8930`) + By `Eni Awowale `_, `Julia Signell `_ + and `Tom Nicholas `_. .. _whats-new.2024.03.0: diff --git a/xarray/tests/test_formatting_html.py b/xarray/tests/test_formatting_html.py index a1b30db60dd..ada7f75b21b 100644 --- a/xarray/tests/test_formatting_html.py +++ b/xarray/tests/test_formatting_html.py @@ -266,7 +266,8 @@ def test_empty_mapping(self): """ children: dict[str, DataTree] = {} assert self.func(children) == ( - "
    " "
    " + "
    " + "
    " ) def test_one_child( @@ -286,7 +287,7 @@ def test_one_child( first_line = f"a {id(children['a'])} end//" assert self.func(children) == ( - "
    " + "
    " f"{first_line}" "
    " ) @@ -313,7 +314,7 @@ def test_two_children( second_line = f"b {id(children['b'])} end//" assert self.func(children) == ( - "
    " + "
    " f"{first_line}" f"{second_line}" "
    " @@ -333,7 +334,7 @@ def test_end(self, repr): """ r = self.func(repr, end=True) assert r == ( - "
    " + "
    " "
    " - "
      " f"{repr}" - "
    " "
    " "
    " ) @@ -367,7 +366,7 @@ def test_not_end(self, repr): """ r = self.func(repr, end=False) assert r == ( - "
    " + "
    " "
    " - "
      " f"{repr}" - "
    " "
    " "
    " )