|
6 | 6 | import contextlib
|
7 | 7 | import functools
|
8 | 8 | import math
|
9 |
| -from collections import defaultdict |
10 |
| -from collections.abc import Collection, Hashable, Sequence |
| 9 | +from collections import ChainMap, defaultdict |
| 10 | +from collections.abc import Collection, Hashable, Mapping, Sequence |
11 | 11 | from datetime import datetime, timedelta
|
12 | 12 | from itertools import chain, zip_longest
|
13 | 13 | from reprlib import recursive_repr
|
14 | 14 | from textwrap import dedent
|
15 |
| -from typing import TYPE_CHECKING |
| 15 | +from typing import TYPE_CHECKING, Any |
16 | 16 |
|
17 | 17 | import numpy as np
|
18 | 18 | import pandas as pd
|
|
29 | 29 | if TYPE_CHECKING:
|
30 | 30 | from xarray.core.coordinates import AbstractCoordinates
|
31 | 31 | from xarray.core.datatree import DataTree
|
| 32 | + from xarray.core.variable import Variable |
32 | 33 |
|
33 | 34 | UNITS = ("B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
34 | 35 |
|
@@ -318,7 +319,7 @@ def inline_variable_array_repr(var, max_width):
|
318 | 319 |
|
319 | 320 | def summarize_variable(
|
320 | 321 | name: Hashable,
|
321 |
| - var, |
| 322 | + var: Variable, |
322 | 323 | col_width: int,
|
323 | 324 | max_width: int | None = None,
|
324 | 325 | is_index: bool = False,
|
@@ -446,6 +447,21 @@ def coords_repr(coords: AbstractCoordinates, col_width=None, max_rows=None):
|
446 | 447 | )
|
447 | 448 |
|
448 | 449 |
|
| 450 | +def inherited_coords_repr(node: DataTree, col_width=None, max_rows=None): |
| 451 | + coords = _inherited_vars(node._coord_variables) |
| 452 | + if col_width is None: |
| 453 | + col_width = _calculate_col_width(coords) |
| 454 | + return _mapping_repr( |
| 455 | + coords, |
| 456 | + title="Inherited coordinates", |
| 457 | + summarizer=summarize_variable, |
| 458 | + expand_option_name="display_expand_coords", |
| 459 | + col_width=col_width, |
| 460 | + indexes=node._indexes, |
| 461 | + max_rows=max_rows, |
| 462 | + ) |
| 463 | + |
| 464 | + |
449 | 465 | def inline_index_repr(index: pd.Index, max_width=None):
|
450 | 466 | if hasattr(index, "_repr_inline_"):
|
451 | 467 | repr_ = index._repr_inline_(max_width=max_width)
|
@@ -498,12 +514,12 @@ def filter_nondefault_indexes(indexes, filter_indexes: bool):
|
498 | 514 | }
|
499 | 515 |
|
500 | 516 |
|
501 |
| -def indexes_repr(indexes, max_rows: int | None = None) -> str: |
| 517 | +def indexes_repr(indexes, max_rows: int | None = None, title: str = "Indexes") -> str: |
502 | 518 | col_width = _calculate_col_width(chain.from_iterable(indexes))
|
503 | 519 |
|
504 | 520 | return _mapping_repr(
|
505 | 521 | indexes,
|
506 |
| - "Indexes", |
| 522 | + title, |
507 | 523 | summarize_index,
|
508 | 524 | "display_expand_indexes",
|
509 | 525 | col_width=col_width,
|
@@ -571,8 +587,10 @@ def _element_formatter(
|
571 | 587 | return "".join(out)
|
572 | 588 |
|
573 | 589 |
|
574 |
| -def dim_summary_limited(obj, col_width: int, max_rows: int | None = None) -> str: |
575 |
| - elements = [f"{k}: {v}" for k, v in obj.sizes.items()] |
| 590 | +def dim_summary_limited( |
| 591 | + sizes: Mapping[Any, int], col_width: int, max_rows: int | None = None |
| 592 | +) -> str: |
| 593 | + elements = [f"{k}: {v}" for k, v in sizes.items()] |
576 | 594 | return _element_formatter(elements, col_width, max_rows)
|
577 | 595 |
|
578 | 596 |
|
@@ -676,7 +694,7 @@ def array_repr(arr):
|
676 | 694 | data_repr = inline_variable_array_repr(arr.variable, OPTIONS["display_width"])
|
677 | 695 |
|
678 | 696 | start = f"<xarray.{type(arr).__name__} {name_str}"
|
679 |
| - dims = dim_summary_limited(arr, col_width=len(start) + 1, max_rows=max_rows) |
| 697 | + dims = dim_summary_limited(arr.sizes, col_width=len(start) + 1, max_rows=max_rows) |
680 | 698 | nbytes_str = render_human_readable_nbytes(arr.nbytes)
|
681 | 699 | summary = [
|
682 | 700 | f"{start}({dims})> Size: {nbytes_str}",
|
@@ -721,7 +739,9 @@ def dataset_repr(ds):
|
721 | 739 | max_rows = OPTIONS["display_max_rows"]
|
722 | 740 |
|
723 | 741 | dims_start = pretty_print("Dimensions:", col_width)
|
724 |
| - dims_values = dim_summary_limited(ds, col_width=col_width + 1, max_rows=max_rows) |
| 742 | + dims_values = dim_summary_limited( |
| 743 | + ds.sizes, col_width=col_width + 1, max_rows=max_rows |
| 744 | + ) |
725 | 745 | summary.append(f"{dims_start}({dims_values})")
|
726 | 746 |
|
727 | 747 | if ds.coords:
|
@@ -756,7 +776,9 @@ def dims_and_coords_repr(ds) -> str:
|
756 | 776 | max_rows = OPTIONS["display_max_rows"]
|
757 | 777 |
|
758 | 778 | dims_start = pretty_print("Dimensions:", col_width)
|
759 |
| - dims_values = dim_summary_limited(ds, col_width=col_width + 1, max_rows=max_rows) |
| 779 | + dims_values = dim_summary_limited( |
| 780 | + ds.sizes, col_width=col_width + 1, max_rows=max_rows |
| 781 | + ) |
760 | 782 | summary.append(f"{dims_start}({dims_values})")
|
761 | 783 |
|
762 | 784 | if ds.coords:
|
@@ -1050,39 +1072,95 @@ def diff_datatree_repr(a: DataTree, b: DataTree, compat):
|
1050 | 1072 | return "\n".join(summary)
|
1051 | 1073 |
|
1052 | 1074 |
|
1053 |
| -def _single_node_repr(node: DataTree) -> str: |
1054 |
| - """Information about this node, not including its relationships to other nodes.""" |
1055 |
| - if node.has_data or node.has_attrs: |
1056 |
| - # TODO: change this to inherited=False, in order to clarify what is |
1057 |
| - # inherited? https://github.com/pydata/xarray/issues/9463 |
1058 |
| - node_view = node._to_dataset_view(rebuild_dims=False, inherited=True) |
1059 |
| - ds_info = "\n" + repr(node_view) |
1060 |
| - else: |
1061 |
| - ds_info = "" |
1062 |
| - return f"Group: {node.path}{ds_info}" |
| 1075 | +def _inherited_vars(mapping: ChainMap) -> dict: |
| 1076 | + return {k: v for k, v in mapping.parents.items() if k not in mapping.maps[0]} |
| 1077 | + |
| 1078 | + |
| 1079 | +def _datatree_node_repr(node: DataTree, show_inherited: bool) -> str: |
| 1080 | + summary = [f"Group: {node.path}"] |
| 1081 | + |
| 1082 | + col_width = _calculate_col_width(node.variables) |
| 1083 | + max_rows = OPTIONS["display_max_rows"] |
| 1084 | + |
| 1085 | + inherited_coords = _inherited_vars(node._coord_variables) |
| 1086 | + |
| 1087 | + # Only show dimensions if also showing a variable or coordinates section. |
| 1088 | + show_dims = ( |
| 1089 | + node._node_coord_variables |
| 1090 | + or (show_inherited and inherited_coords) |
| 1091 | + or node._data_variables |
| 1092 | + ) |
| 1093 | + |
| 1094 | + dim_sizes = node.sizes if show_inherited else node._node_dims |
| 1095 | + |
| 1096 | + if show_dims: |
| 1097 | + # Includes inherited dimensions. |
| 1098 | + dims_start = pretty_print("Dimensions:", col_width) |
| 1099 | + dims_values = dim_summary_limited( |
| 1100 | + dim_sizes, col_width=col_width + 1, max_rows=max_rows |
| 1101 | + ) |
| 1102 | + summary.append(f"{dims_start}({dims_values})") |
| 1103 | + |
| 1104 | + if node._node_coord_variables: |
| 1105 | + summary.append(coords_repr(node.coords, col_width=col_width, max_rows=max_rows)) |
| 1106 | + |
| 1107 | + if show_inherited and inherited_coords: |
| 1108 | + summary.append( |
| 1109 | + inherited_coords_repr(node, col_width=col_width, max_rows=max_rows) |
| 1110 | + ) |
| 1111 | + |
| 1112 | + if show_dims: |
| 1113 | + unindexed_dims_str = unindexed_dims_repr( |
| 1114 | + dim_sizes, node.coords, max_rows=max_rows |
| 1115 | + ) |
| 1116 | + if unindexed_dims_str: |
| 1117 | + summary.append(unindexed_dims_str) |
| 1118 | + |
| 1119 | + if node._data_variables: |
| 1120 | + summary.append( |
| 1121 | + data_vars_repr(node._data_variables, col_width=col_width, max_rows=max_rows) |
| 1122 | + ) |
1063 | 1123 |
|
| 1124 | + # TODO: only show indexes defined at this node, with a separate section for |
| 1125 | + # inherited indexes (if show_inherited=True) |
| 1126 | + display_default_indexes = _get_boolean_with_default( |
| 1127 | + "display_default_indexes", False |
| 1128 | + ) |
| 1129 | + xindexes = filter_nondefault_indexes( |
| 1130 | + _get_indexes_dict(node.xindexes), not display_default_indexes |
| 1131 | + ) |
| 1132 | + if xindexes: |
| 1133 | + summary.append(indexes_repr(xindexes, max_rows=max_rows)) |
1064 | 1134 |
|
1065 |
| -def datatree_repr(dt: DataTree): |
| 1135 | + if node.attrs: |
| 1136 | + summary.append(attrs_repr(node.attrs, max_rows=max_rows)) |
| 1137 | + |
| 1138 | + return "\n".join(summary) |
| 1139 | + |
| 1140 | + |
| 1141 | +def datatree_repr(dt: DataTree) -> str: |
1066 | 1142 | """A printable representation of the structure of this entire tree."""
|
1067 | 1143 | renderer = RenderDataTree(dt)
|
1068 | 1144 |
|
1069 | 1145 | name_info = "" if dt.name is None else f" {dt.name!r}"
|
1070 | 1146 | header = f"<xarray.DataTree{name_info}>"
|
1071 | 1147 |
|
1072 | 1148 | lines = [header]
|
| 1149 | + show_inherited = True |
1073 | 1150 | for pre, fill, node in renderer:
|
1074 |
| - node_repr = _single_node_repr(node) |
| 1151 | + node_repr = _datatree_node_repr(node, show_inherited=show_inherited) |
| 1152 | + show_inherited = False # only show inherited coords on the root |
| 1153 | + |
| 1154 | + raw_repr_lines = node_repr.splitlines() |
1075 | 1155 |
|
1076 |
| - node_line = f"{pre}{node_repr.splitlines()[0]}" |
| 1156 | + node_line = f"{pre}{raw_repr_lines[0]}" |
1077 | 1157 | lines.append(node_line)
|
1078 | 1158 |
|
1079 |
| - if node.has_data or node.has_attrs: |
1080 |
| - ds_repr = node_repr.splitlines()[2:] |
1081 |
| - for line in ds_repr: |
1082 |
| - if len(node.children) > 0: |
1083 |
| - lines.append(f"{fill}{renderer.style.vertical}{line}") |
1084 |
| - else: |
1085 |
| - lines.append(f"{fill}{' ' * len(renderer.style.vertical)}{line}") |
| 1159 | + for line in raw_repr_lines[1:]: |
| 1160 | + if len(node.children) > 0: |
| 1161 | + lines.append(f"{fill}{renderer.style.vertical}{line}") |
| 1162 | + else: |
| 1163 | + lines.append(f"{fill}{' ' * len(renderer.style.vertical)}{line}") |
1086 | 1164 |
|
1087 | 1165 | return "\n".join(lines)
|
1088 | 1166 |
|
|
0 commit comments