Skip to content

Commit ccd4939

Browse files
authored
replace describe with __repr__ (#150)
* show coords std names * add star * introduce __repr__ * more compact * update doc * whats new * minor fix
1 parent c41f2fa commit ccd4939

File tree

5 files changed

+173
-48
lines changed

5 files changed

+173
-48
lines changed

cf_xarray/accessor.py

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ def check_results(names, k):
654654
except KeyError:
655655
raise KeyError(
656656
f"{kind}.cf does not understand the key {k!r}. "
657-
f"Use {kind}.cf.describe() to see a list of key names that can be interpreted."
657+
f"Use 'repr({kind}.cf)' (or '{kind}.cf' in a Jupyter environment) to see a list of key names that can be interpreted."
658658
)
659659

660660

@@ -997,27 +997,80 @@ def describe(self):
997997
"""
998998
Print a string repr to screen.
999999
"""
1000-
text = "Axes:\n"
1001-
axes = self.axes
1002-
for key in _AXIS_NAMES:
1003-
text += f"\t{key}: {axes[key] if key in axes else []}\n"
1004-
1005-
text += "\nCoordinates:\n"
1006-
coords = self.coordinates
1007-
for key in _COORD_NAMES:
1008-
text += f"\t{key}: {coords[key] if key in coords else []}\n"
1009-
1010-
text += "\nCell Measures:\n"
1011-
measures = self.cell_measures
1012-
for key in sorted(self._get_all_cell_measures()):
1013-
text += f"\t{key}: {measures[key] if key in measures else []}\n"
1014-
1015-
text += "\nStandard Names:\n"
1016-
for key, value in sorted(self.standard_names.items()):
1017-
if key not in _COORD_NAMES:
1018-
text += f"\t{key}: {value}\n"
1019-
1020-
print(text)
1000+
1001+
warnings.warn(
1002+
"'obj.cf.describe()' will be removed in a future version. "
1003+
"Use instead 'repr(obj.cf)' or 'obj.cf' in a Jupyter environment.",
1004+
DeprecationWarning,
1005+
)
1006+
print(repr(self))
1007+
1008+
def __repr__(self):
1009+
1010+
coords = self._obj.coords
1011+
dims = self._obj.dims
1012+
1013+
def make_text_section(subtitle, vardict, valid_values, valid_keys=None):
1014+
1015+
star = " * "
1016+
tab = len(star) * " "
1017+
subtitle = f"- {subtitle}:"
1018+
1019+
# Sort keys
1020+
if not valid_keys:
1021+
# Alphabetical order
1022+
vardict = {key: vardict[key] for key in sorted(vardict)}
1023+
else:
1024+
# Hardcoded order
1025+
vardict = {key: vardict[key] for key in valid_keys if key in vardict}
1026+
1027+
# Keep only valid values (e.g., coords or data_vars)
1028+
vardict = {
1029+
key: set(value).intersection(valid_values)
1030+
for key, value in vardict.items()
1031+
if set(value).intersection(valid_values)
1032+
}
1033+
1034+
# Star for keys with dims only, tab otherwise
1035+
rows = [
1036+
f"{star if set(value) <= set(dims) else tab}{key}: {sorted(value)}"
1037+
for key, value in vardict.items()
1038+
]
1039+
1040+
# Add valid keys missing followed by n/a
1041+
if valid_keys:
1042+
missing_keys = [key for key in valid_keys if key not in vardict]
1043+
if missing_keys:
1044+
rows += [tab + ", ".join(missing_keys) + ": n/a"]
1045+
elif not rows:
1046+
rows = [tab + "n/a"]
1047+
1048+
# Add subtitle to the first row, align other rows
1049+
rows = [
1050+
"\n" + subtitle + row if i == 0 else len(subtitle) * " " + row
1051+
for i, row in enumerate(rows)
1052+
]
1053+
1054+
return "\n".join(rows) + "\n"
1055+
1056+
text = "Coordinates:"
1057+
text += make_text_section("CF Axes", self.axes, coords, _AXIS_NAMES)
1058+
text += make_text_section(
1059+
"CF Coordinates", self.coordinates, coords, _COORD_NAMES
1060+
)
1061+
text += make_text_section(
1062+
"Cell Measures", self.cell_measures, coords, _CELL_MEASURES
1063+
)
1064+
text += make_text_section("Standard Names", self.standard_names, coords)
1065+
if isinstance(self._obj, Dataset):
1066+
data_vars = self._obj.data_vars
1067+
text += "\nData Variables:"
1068+
text += make_text_section(
1069+
"Cell Measures", self.cell_measures, data_vars, _CELL_MEASURES
1070+
)
1071+
text += make_text_section("Standard Names", self.standard_names, data_vars)
1072+
1073+
return text
10211074

10221075
def get_valid_keys(self) -> Set[str]:
10231076

cf_xarray/tests/test_accessor.py

Lines changed: 91 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from textwrap import dedent
2+
13
import matplotlib as mpl
24
import numpy as np
35
import pandas as pd
@@ -35,17 +37,83 @@ def assert_dicts_identical(dict1, dict2):
3537
assert_identical(dict1[k], dict2[k])
3638

3739

38-
def test_describe(capsys):
39-
airds.cf.describe()
40-
actual = capsys.readouterr().out
41-
expected = (
42-
"Axes:\n\tX: ['lon']\n\tY: ['lat']\n\tZ: []\n\tT: ['time']\n"
43-
"\nCoordinates:\n\tlongitude: ['lon']\n\tlatitude: ['lat']"
44-
"\n\tvertical: []\n\ttime: ['time']\n"
45-
"\nCell Measures:\n\tarea: ['cell_area']\n\tvolume: []\n"
46-
"\nStandard Names:\n\tair_temperature: ['air']\n\n"
47-
)
48-
assert actual == expected
40+
def test_repr():
41+
# Dataset.
42+
# Stars: axes, coords, and std names
43+
actual = airds.cf.__repr__()
44+
expected = """\
45+
Coordinates:
46+
- CF Axes: * X: ['lon']
47+
* Y: ['lat']
48+
* T: ['time']
49+
Z: n/a
50+
51+
- CF Coordinates: * longitude: ['lon']
52+
* latitude: ['lat']
53+
* time: ['time']
54+
vertical: n/a
55+
56+
- Cell Measures: area: ['cell_area']
57+
volume: n/a
58+
59+
- Standard Names: * latitude: ['lat']
60+
* longitude: ['lon']
61+
* time: ['time']
62+
63+
Data Variables:
64+
- Cell Measures: area, volume: n/a
65+
66+
- Standard Names: air_temperature: ['air']
67+
"""
68+
assert actual == dedent(expected)
69+
70+
# DataArray (Coordinates section same as Dataset)
71+
assert airds.cf.__repr__().startswith(airds["air"].cf.__repr__())
72+
actual = airds["air"].cf.__repr__()
73+
expected = """\
74+
Coordinates:
75+
- CF Axes: * X: ['lon']
76+
* Y: ['lat']
77+
* T: ['time']
78+
Z: n/a
79+
80+
- CF Coordinates: * longitude: ['lon']
81+
* latitude: ['lat']
82+
* time: ['time']
83+
vertical: n/a
84+
85+
- Cell Measures: area: ['cell_area']
86+
volume: n/a
87+
88+
- Standard Names: * latitude: ['lat']
89+
* longitude: ['lon']
90+
* time: ['time']
91+
"""
92+
assert actual == dedent(expected)
93+
94+
# Empty Standard Names
95+
actual = popds.cf.__repr__()
96+
expected = """\
97+
Coordinates:
98+
- CF Axes: * X: ['nlon']
99+
* Y: ['nlat']
100+
Z, T: n/a
101+
102+
- CF Coordinates: longitude: ['TLONG', 'ULONG']
103+
latitude: ['TLAT', 'ULAT']
104+
vertical, time: n/a
105+
106+
- Cell Measures: area, volume: n/a
107+
108+
- Standard Names: n/a
109+
110+
Data Variables:
111+
- Cell Measures: area, volume: n/a
112+
113+
- Standard Names: sea_water_potential_temperature: ['TEMP']
114+
sea_water_x_velocity: ['UVEL']
115+
"""
116+
assert actual == dedent(expected)
49117

50118

51119
def test_axes():
@@ -68,7 +136,7 @@ def test_coordinates():
68136
assert actual == expected
69137

70138

71-
def test_cell_measures(capsys):
139+
def test_cell_measures():
72140
ds = airds.copy(deep=True)
73141
ds["foo"] = xr.DataArray(ds["cell_area"], attrs=dict(standard_name="foo_std_name"))
74142
ds["air"].attrs["cell_measures"] += " foo_measure: foo"
@@ -84,13 +152,17 @@ def test_cell_measures(capsys):
84152
actual = ds.cf.cell_measures
85153
assert actual == expected
86154

87-
ds.cf.describe()
88-
actual = capsys.readouterr().out
89-
expected = (
90-
"\nCell Measures:\n\tarea: ['cell_area']\n\tfoo_measure: ['foo']\n\tvolume: ['foo']\n"
91-
"\nStandard Names:\n\tair_temperature: ['air']\n\tfoo_std_name: ['foo']\n\n"
92-
)
93-
assert actual.endswith(expected)
155+
# Additional cell measure in repr
156+
actual = ds.cf.__repr__()
157+
expected = """\
158+
Data Variables:
159+
- Cell Measures: volume: ['foo']
160+
area: n/a
161+
162+
- Standard Names: air_temperature: ['air']
163+
foo_std_name: ['foo']
164+
"""
165+
assert actual.endswith(dedent(expected))
94166

95167

96168
def test_standard_names():

doc/api.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Methods
4444
:template: autosummary/accessor_method.rst
4545

4646
DataArray.cf.__getitem__
47-
DataArray.cf.describe
47+
DataArray.cf.__repr__
4848
DataArray.cf.guess_coord_axis
4949
DataArray.cf.keys
5050
DataArray.cf.rename_like
@@ -76,10 +76,10 @@ Methods
7676
:template: autosummary/accessor_method.rst
7777

7878
Dataset.cf.__getitem__
79+
Dataset.cf.__repr__
7980
Dataset.cf.add_bounds
8081
Dataset.cf.bounds_to_vertices
8182
Dataset.cf.decode_vertical_coords
82-
Dataset.cf.describe
8383
Dataset.cf.get_bounds
8484
Dataset.cf.get_bounds_dim_name
8585
Dataset.cf.guess_coord_axis

doc/examples/introduction.ipynb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,7 @@
152152
"`'X'` axis as being represented by the `lon` variable.\n",
153153
"\n",
154154
"It can also use the `standard_name` and `units` attributes to infer that `lon`\n",
155-
"is \"Longitude\". To see variable names that `cf_xarray` can infer, use\n",
156-
"`.cf.describe()`\n"
155+
"is \"Longitude\". To see variable names that `cf_xarray` can infer, use `ds.cf`\n"
157156
]
158157
},
159158
{
@@ -167,7 +166,7 @@
167166
},
168167
"outputs": [],
169168
"source": [
170-
"ds.cf.describe()"
169+
"ds.cf"
171170
]
172171
},
173172
{
@@ -190,7 +189,7 @@
190189
},
191190
"outputs": [],
192191
"source": [
193-
"pop.cf.describe()"
192+
"pop.cf"
194193
]
195194
},
196195
{
@@ -211,7 +210,7 @@
211210
},
212211
"outputs": [],
213212
"source": [
214-
"multiple.cf.describe()"
213+
"multiple.cf"
215214
]
216215
},
217216
{

doc/whats-new.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ What's New
66
v0.4.1 (unreleased)
77
===================
88

9+
- Replace ``cf.describe()`` with :py:meth:`Dataset.cf.__repr__`. By `Mattia Almansi`_.
910
- Automatically set ``x`` or ``y`` for :py:attr:`DataArray.cf.plot`. By `Deepak Cherian`_.
1011
- Added scripts to document :ref:`criteria` with tables. By `Mattia Almansi`_.
1112
- Support for ``.drop()``, ``.drop_vars()``, ``.drop_sel()``, ``.drop_dims()``, ``.set_coords()``, ``.reset_coords()``. By `Mattia Almansi`_.

0 commit comments

Comments
 (0)