Skip to content

Commit

Permalink
rm IPython (#720)
Browse files Browse the repository at this point in the history
* rm ipython

* rm ipython from tests

* refactor text repr
  • Loading branch information
aaronspring authored Jan 17, 2022
1 parent 6625d93 commit 8ffa68e
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 114 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Internals/Minor Fixes
---------------------
- Refactor ``asv`` benchmarking. Add ``run-benchmarks`` label to ``PR`` to run ``asv``
via Github Actions. (:issue:`664`, :pr:`718`) `Aaron Spring`_.
- Remove ``ipython`` from ``requirements.txt``. (:pr:`720`) `Aaron Spring`_.


climpred v2.2.0 (2021-12-20)
Expand Down
1 change: 0 additions & 1 deletion ci/requirements/climpred-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ dependencies:
- sphinxcontrib-napoleon
- sphinx-copybutton
# IDE
- ipywidgets
- jupyterlab
- nb_conda_kernels # switch conda envs in jupyter
# Input/Output
Expand Down
3 changes: 1 addition & 2 deletions ci/requirements/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ dependencies:
- python=3.9
- matplotlib-base
- netcdf4
- toolz
- xarray>=0.19.0
- xskillscore>=0.0.18
- cftime
- cftime>=0.1.5
# docs
- jupyterlab
- sphinx
Expand Down
3 changes: 1 addition & 2 deletions ci/requirements/maximum-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ channels:
- nodefaults
dependencies:
- python>=3.7
- cftime>=1.1.2
- cftime>=1.5.0
- coveralls
- dask-core
- eofs
- esmpy=*=mpi* # Ensures MPI works with version of esmpy.
- ipython
- matplotlib-base
- nc-time-axis>=1.4.0
- netcdf4
Expand Down
3 changes: 1 addition & 2 deletions ci/requirements/minimum-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ channels:
- nodefaults
dependencies:
- python>=3.7
- cftime>=1.1.2
- cftime>=1.5.0
- coveralls
- dask-core
- ipython
- netcdf4
- pip
- pytest
Expand Down
145 changes: 51 additions & 94 deletions climpred/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import cf_xarray # noqa
import numpy as np
import xarray as xr
from IPython.display import display_html
from xarray.core.coordinates import DatasetCoordinates
from xarray.core.dataset import DataVariables
from xarray.core.formatting_html import dataset_repr
Expand Down Expand Up @@ -109,86 +108,39 @@ def _display_metadata(self) -> str:
>>> hindcast = climpred.HindcastEnsemble(init)
>>> print(hindcast)
<climpred.HindcastEnsemble>
Initialized Ensemble:
Initialized:
SST (init, lead, member) float64 -0.2404 -0.2085 ... 0.7442 0.7384
Observations:
None
Uninitialized:
None
Observations:
None
"""
SPACE = " "
header = f"<climpred.{type(self).__name__}>"
summary = header + "\nInitialized Ensemble:\n"
summary += SPACE + str(self._datasets["initialized"].data_vars)[18:].strip() + "\n"
if isinstance(self, HindcastEnsemble):
# Prints out observations and associated variables if they exist.
# If not, just write "None".
summary += "Observations:\n"
if any(self._datasets["observations"]):
num_obs = len(self._datasets["observations"].data_vars)
for i in range(1, num_obs + 1):
summary += (
SPACE
+ str(self._datasets["observations"].data_vars)
.split("\n")[i]
.strip()
+ "\n"
)
else:
summary += SPACE + "None\n"
elif isinstance(self, PerfectModelEnsemble):
summary += "Control:\n"
# Prints out control variables if a control is appended. If not,
# just write "None".
if any(self._datasets["control"]):
num_ctrl = len(self._datasets["control"].data_vars)
for i in range(1, num_ctrl + 1):
summary += (
SPACE
+ str(self._datasets["control"].data_vars).split("\n")[i].strip()
+ "\n"
summary = f"<climpred.{type(self).__name__}>\n"

for k in self._datasets.keys():
if self._datasets[k]:
summary += (
str(self._datasets[k].data_vars).replace(
"Data variables", k.capitalize()
)
else:
summary += SPACE + "None\n"
if any(self._datasets["uninitialized"]):
summary += "Uninitialized:\n"
summary += SPACE + str(self._datasets["uninitialized"].data_vars)[18:].strip()
else:
summary += "Uninitialized:\n"
summary += SPACE + "None"
return summary


def _display_metadata_html(self) -> str:
"""Print contents of :py:class:`.PredictionEnsemble` as html."""
header = f"<h4>climpred.{type(self).__name__}</h4>"
display_html(header, raw=True)
init_repr_str = dataset_repr(self._datasets["initialized"])
init_repr_str = init_repr_str.replace("xarray.Dataset", "Initialized Ensemble")
display_html(init_repr_str, raw=True)

if isinstance(self, HindcastEnsemble):
if any(self._datasets["observations"]):
obs_repr_str = dataset_repr(self._datasets["observations"])
obs_repr_str = obs_repr_str.replace("xarray.Dataset", "Observations")
display_html(obs_repr_str, raw=True)
elif isinstance(self, PerfectModelEnsemble):
if any(self._datasets["control"]):
control_repr_str = dataset_repr(self._datasets["control"])
control_repr_str = control_repr_str.replace(
"xarray.Dataset", "Control Simulation"
+ "\n"
)
display_html(control_repr_str, raw=True)
else:
summary += f"{k.capitalize()}:\n{SPACE}None\n"
return summary.strip("\n")

if any(self._datasets["uninitialized"]):
uninit_repr_str = dataset_repr(self._datasets["uninitialized"])
uninit_repr_str = uninit_repr_str.replace("xarray.Dataset", "Uninitialized")
display_html(uninit_repr_str, raw=True)
# better would be to aggregate repr_strs and then all return but this fails
# TypeError: __repr__ returned non-string (type NoneType)
# workaround return empty string
return ""

def _display_metadata_html(self):
"""Show contents of :py:class:`.PredictionEnsemble` as html."""
html_str = f"<h4>climpred.{type(self).__name__}</h4>"
for k in self._datasets.keys():
if self._datasets[k]:
html_str += dataset_repr(self._datasets[k]).replace(
"xarray.Dataset", k.capitalize()
)
return html_str


class PredictionEnsemble:
Expand Down Expand Up @@ -377,14 +329,17 @@ def data_vars(self) -> DataVariables:
varlist = list(varset)
return self.get_initialized()[varlist].data_vars

# when you just print it interactively
# https://stackoverflow.com/questions/1535327/how-to-print-objects-of-class-using-print
def _repr_html_(self):
"""Return for :py:class:`.PredictionEnsemble` in html."""
from html import escape

if XR_OPTIONS["display_style"] == "text":
return f"<pre>{escape(repr(self))}</pre>"
return _display_metadata_html(self)

def __repr__(self) -> str:
"""Return for print(PredictionEnsemble)."""
if XR_OPTIONS["display_style"] == "html":
return _display_metadata_html(self)
else:
return _display_metadata(self)
"""Return for print(:py:class:`.PredictionEnsemble`)."""
return _display_metadata(self)

def __len__(self) -> int:
"""Return number of all variables :py:class:`.PredictionEnsemble`."""
Expand Down Expand Up @@ -658,6 +613,8 @@ def __getattr__(
Args:
* name: str of xarray function, e.g., ``.isel()`` or ``.sum()``.
"""
if name == "_ipython_canary_method_should_not_exist_":
raise AttributeError(name)

def wrapper(*args, **kwargs):
"""Apply arbitrary function to all datasets in ``PerfectModelEnsemble``.
Expand Down Expand Up @@ -809,12 +766,12 @@ def smooth(
>>> HindcastEnsemble_3D.smooth({"lon": 1, "lat": 1})
<climpred.HindcastEnsemble>
Initialized Ensemble:
Initialized:
SST (init, lead, lat, lon) float32 -0.3236 -0.3161 -0.3083 ... 0.0 0.0
Observations:
SST (time, lat, lon) float32 0.002937 0.001561 0.002587 ... 0.0 0.0 0.0
Uninitialized:
None
Observations:
SST (time, lat, lon) float32 0.002937 0.001561 0.002587 ... 0.0 0.0 0.0
``smooth`` simultaneously aggregates spatially listening to ``lon`` and
``lat`` and temporally listening to ``lead`` or ``time``.
Expand Down Expand Up @@ -924,21 +881,21 @@ def remove_seasonality(
Examples:
>>> HindcastEnsemble
<climpred.HindcastEnsemble>
Initialized Ensemble:
Initialized:
SST (init, lead, member) float64 -0.2392 -0.2203 ... 0.618 0.6136
Observations:
SST (time) float32 -0.4015 -0.3524 -0.1851 ... 0.2481 0.346 0.4502
Uninitialized:
SST (time, member) float64 -0.1969 -0.01221 -0.275 ... 0.4179 0.3974
Observations:
SST (time) float32 -0.4015 -0.3524 -0.1851 ... 0.2481 0.346 0.4502
>>> # example already effectively without seasonal cycle
>>> HindcastEnsemble.remove_seasonality(seasonality="month")
<climpred.HindcastEnsemble>
Initialized Ensemble:
Initialized:
SST (init, lead, member) float64 -0.2349 -0.216 ... 0.6476 0.6433
Observations:
SST (time) float32 -0.3739 -0.3248 -0.1575 ... 0.2757 0.3736 0.4778
Uninitialized:
SST (time, member) float64 -0.1789 0.005732 -0.257 ... 0.4359 0.4154
Observations:
SST (time) float32 -0.3739 -0.3248 -0.1575 ... 0.2757 0.3736 0.4778
"""

def _remove_seasonality(ds, initialized_dim="init", seasonality=None):
Expand Down Expand Up @@ -1797,21 +1754,21 @@ def generate_uninitialized(
Example:
>>> HindcastEnsemble # uninitialized from historical simulations
<climpred.HindcastEnsemble>
Initialized Ensemble:
Initialized:
SST (init, lead, member) float64 -0.2392 -0.2203 ... 0.618 0.6136
Observations:
SST (time) float32 -0.4015 -0.3524 -0.1851 ... 0.2481 0.346 0.4502
Uninitialized:
SST (time, member) float64 -0.1969 -0.01221 -0.275 ... 0.4179 0.3974
Observations:
SST (time) float32 -0.4015 -0.3524 -0.1851 ... 0.2481 0.346 0.4502
>>> HindcastEnsemble.generate_uninitialized() # newly generated from initialized
<climpred.HindcastEnsemble>
Initialized Ensemble:
Initialized:
SST (init, lead, member) float64 -0.2392 -0.2203 ... 0.618 0.6136
Observations:
SST (time) float32 -0.4015 -0.3524 -0.1851 ... 0.2481 0.346 0.4502
Uninitialized:
SST (time, member) float64 0.04868 0.07173 0.09435 ... 0.4158 0.418
Observations:
SST (time) float32 -0.4015 -0.3524 -0.1851 ... 0.2481 0.346 0.4502
"""
uninit = resample_uninitialized_from_initialized(
self._datasets["initialized"], resample_dim=resample_dim
Expand Down
35 changes: 23 additions & 12 deletions climpred/tests/test_repr.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,51 @@
import pytest
import xarray as xr
from IPython.display import display_html

from climpred.classes import HindcastEnsemble, PerfectModelEnsemble


def assert_repr(pe, display_style):
if display_style == "text":
repr_str = pe.__repr__()
elif display_style == "html":
repr_str = pe._repr_html_()
assert "Ensemble" in repr_str
if display_style == "text":
assert "</pre>" not in repr_str
elif display_style == "html":
assert "icon" in repr_str


@pytest.mark.parametrize("display_style", ("html", "text"))
def test_repr_PM(PM_da_initialized_1d, PM_da_control_1d, display_style):
def test_repr_PerfectModelEnsemble(
PM_da_initialized_1d, PM_da_control_1d, display_style
):
"""Test html and text repr."""
display = print if display_style == "text" else display_html
with xr.set_options(display_style=display_style):
pm = PerfectModelEnsemble(PM_da_initialized_1d)
display(pm)
assert_repr(pm, display_style)
pm = pm.add_control(PM_da_control_1d)
display(pm)
assert_repr(pm, display_style)
pm = pm.generate_uninitialized()
display(pm)
assert_repr(pm, display_style)


@pytest.mark.parametrize("display_style", ("html", "text"))
def test_repr_HC(
def test_repr_HindcastEnsemble(
hind_ds_initialized_1d,
hist_ds_uninitialized_1d,
observations_ds_1d,
display_style,
):
"""Test html repr."""
display = print if display_style == "text" else display_html
with xr.set_options(display_style=display_style):
he = HindcastEnsemble(hind_ds_initialized_1d)
display(he)
assert_repr(he, display_style)
he = he.add_uninitialized(hist_ds_uninitialized_1d)
display(he)
assert_repr(he, display_style)
he = he.add_observations(observations_ds_1d)
display(he)
assert_repr(he, display_style)
# no uninit
he = HindcastEnsemble(hind_ds_initialized_1d)
he = he.add_observations(observations_ds_1d)
display(he)
assert_repr(he, display_style)
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
xarray>=0.19.0
dask>=2021.10.0
ipython<8.0.0
toolz
cftime>=1.5.0
xskillscore>=0.0.20
Expand Down

0 comments on commit 8ffa68e

Please sign in to comment.