diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py index 067c10ecad82..9dba682e11ed 100644 --- a/ibis/backends/tests/test_client.py +++ b/ibis/backends/tests/test_client.py @@ -1202,7 +1202,7 @@ def test_interactive_repr_max_columns(alltypes, is_jupyter, monkeypatch): @pytest.mark.parametrize("expr_type", ["table", "column"]) @pytest.mark.parametrize("interactive", [True, False]) -def test_repr_html(alltypes, interactive, expr_type, monkeypatch): +def test_repr_mimebundle(alltypes, interactive, expr_type, monkeypatch): pytest.importorskip("rich") monkeypatch.setattr(ibis.options, "interactive", interactive) @@ -1212,11 +1212,12 @@ def test_repr_html(alltypes, interactive, expr_type, monkeypatch): else: expr = alltypes.select("date_string_col") - text = expr._repr_html_() - if interactive: - assert "r0.date_string_col" not in text - else: - assert "r0.date_string_col" in text + reprs = expr._repr_mimebundle_(include=["text/plain", "text/html"], exclude=[]) + for format in ["text/plain", "text/html"]: + if interactive: + assert "r0.date_string_col" not in reprs[format] + else: + assert "r0.date_string_col" in reprs[format] @pytest.mark.never( diff --git a/ibis/expr/types/core.py b/ibis/expr/types/core.py index de645aa4fa4a..033aae6a889c 100644 --- a/ibis/expr/types/core.py +++ b/ibis/expr/types/core.py @@ -32,6 +32,26 @@ from ibis.backends import BaseBackend from ibis.expr.visualize import EdgeAttributeGetter, NodeAttributeGetter +try: + from rich.jupyter import JupyterMixin +except ImportError: + + class _FixedTextJupyterMixin: + """No-op when rich is not installed.""" +else: + + class _FixedTextJupyterMixin(JupyterMixin): + """JupyterMixin adds a spurious newline to text, this fixes the issue.""" + + def _repr_mimebundle_(self, *args, **kwargs): + try: + bundle = super()._repr_mimebundle_(*args, **kwargs) + except Exception: # noqa: BLE001 + return None + else: + bundle["text/plain"] = bundle["text/plain"].rstrip() + return bundle + def _capture_rich_renderable( renderable: RenderableType, *, force_terminal: bool | None = None @@ -67,19 +87,6 @@ def __repr__(self) -> str: def _repr_pretty_(self, p, cycle): p.text(_capture_rich_renderable(self)) - def _repr_html_(self): - from rich import get_console - from rich.jupyter import _render_segments - - console = get_console() - - try: - segments = list(console.render(self, console.options)) - except Exception: # noqa: BLE001 - return None - html = _render_segments(segments) - return html.rstrip() - def __rich_console__(self, console: Console, options: ConsoleOptions): if console.is_jupyter: # Rich infers a console width in jupyter notebooks, but since diff --git a/ibis/expr/types/generic.py b/ibis/expr/types/generic.py index 6871a6c59b53..c9d8b6c9429f 100644 --- a/ibis/expr/types/generic.py +++ b/ibis/expr/types/generic.py @@ -13,7 +13,7 @@ from ibis.common.deferred import Deferred, _, deferrable from ibis.common.grounds import Singleton from ibis.expr.rewrites import rewrite_window_input -from ibis.expr.types.core import Expr, _binop, _is_null_literal +from ibis.expr.types.core import Expr, _binop, _FixedTextJupyterMixin, _is_null_literal from ibis.util import deprecated, promote_list, warn_deprecated if TYPE_CHECKING: @@ -1367,7 +1367,7 @@ def _repr_html_(self) -> str | None: @public -class Column(Value): +class Column(Value, _FixedTextJupyterMixin): # Higher than numpy & dask objects __array_priority__ = 20 diff --git a/ibis/expr/types/relations.py b/ibis/expr/types/relations.py index 3295de1a0cd2..7204e50ca78d 100644 --- a/ibis/expr/types/relations.py +++ b/ibis/expr/types/relations.py @@ -20,7 +20,7 @@ from ibis.common.deferred import Deferred, Resolver from ibis.common.selectors import Expandable, Selector from ibis.expr.rewrites import DerefMap -from ibis.expr.types.core import Expr +from ibis.expr.types.core import Expr, _FixedTextJupyterMixin from ibis.expr.types.generic import Value, literal from ibis.expr.types.temporal import TimestampColumn from ibis.util import deprecated @@ -140,7 +140,7 @@ def unwrap_aliases(values: Iterator[ir.Value]) -> Mapping[str, ir.Value]: @public -class Table(Expr): +class Table(Expr, _FixedTextJupyterMixin): """An immutable and lazy dataframe. Analogous to a SQL table or a pandas DataFrame. A table expression contains