Skip to content

Commit

Permalink
feat(ux): add Table and Column.preview()
Browse files Browse the repository at this point in the history
  • Loading branch information
NickCrews committed Mar 1, 2024
1 parent 5006f2d commit a0b2852
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 112 deletions.
53 changes: 37 additions & 16 deletions ibis/expr/types/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@

import ibis.expr.operations as ops
from ibis.common.annotations import ValidationError
from ibis.common.exceptions import IbisError, TranslationError
from ibis.common.exceptions import IbisError
from ibis.common.grounds import Immutable
from ibis.common.patterns import Coercible, CoercionError
from ibis.config import _default_backend
from ibis.config import options as opts
from ibis.expr.types.pretty import to_rich
from ibis.util import experimental

if TYPE_CHECKING:
Expand Down Expand Up @@ -57,7 +58,38 @@ def __rich_console__(self, console, options):
return self.__interactive_rich_console__(console, options)

def __interactive_rich_console__(self, console, options):
raise NotImplementedError()
if console.is_jupyter:
# Rich infers a console width in jupyter notebooks, but since
# notebooks can use horizontal scroll bars we don't want to apply a
# limit here. Since rich requires an integer for max_width, we
# choose an arbitrarily large integer bound. Note that we need to
# handle this here rather than in `to_rich_table`, as this setting
# also needs to be forwarded to `console.render`.
options = options.update(max_width=1_000_000)
console_width = None
else:
console_width = options.max_width

try:
rt = to_rich(self, console_width=console_width)
except Exception as e:
# In IPython exceptions inside of _repr_mimebundle_ are swallowed to
# allow calling several display functions and choosing to display
# the "best" result based on some priority.
# This behavior, though, means that exceptions that bubble up inside of the interactive repr
# are silently caught.
#
# We can't stop the exception from being swallowed, but we can force
# the display of that exception as we do here.
#
# A _very_ annoying caveat is that this exception is _not_ being
# ` raise`d, it is only being printed to the console. This means
# that you cannot "catch" it.
#
# This restriction is only present in IPython, not in other REPLs.
console.print_exception()
raise e
return console.render(rt, options=options)

def __init__(self, arg: ops.Node) -> None:
object.__setattr__(self, "_arg", arg)
Expand All @@ -78,20 +110,9 @@ def __repr__(self) -> str:
if not opts.interactive:
return self._repr()

from ibis.expr.types.pretty import simple_console

with simple_console.capture() as capture:
try:
simple_console.print(self)
except TranslationError as e:
lines = [
"Translation to backend failed",
f"Error message: {e!r}",
"Expression repr follows:",
self._repr(),
]
return "\n".join(lines)
return capture.get().rstrip()
from ibis.expr.types.pretty import pretty_repr

return pretty_repr(self)

def __reduce__(self):
return (self.__class__, (self._arg,))
Expand Down
76 changes: 58 additions & 18 deletions ibis/expr/types/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections.abc import Iterable, Sequence
from typing import TYPE_CHECKING, Any, Literal

import rich
from public import public

import ibis
Expand All @@ -14,6 +15,7 @@
from ibis.common.grounds import Singleton
from ibis.expr.rewrites import rewrite_window_input
from ibis.expr.types.core import Expr, _binop, _FixedTextJupyterMixin
from ibis.expr.types.pretty import to_rich
from ibis.util import deprecated

if TYPE_CHECKING:
Expand Down Expand Up @@ -1177,20 +1179,6 @@ def to_pandas(self, **kwargs) -> pd.Series:

@public
class Scalar(Value):
def __interactive_rich_console__(self, console, options):
import rich.pretty

interactive = ibis.options.repr.interactive
return console.render(
rich.pretty.Pretty(
self.execute(),
max_length=interactive.max_length,
max_string=interactive.max_string,
max_depth=interactive.max_depth,
),
options=options,
)

def __pyarrow_result__(
self, table: pa.Table, data_mapper: type[PyArrowData] | None = None
) -> pa.Scalar:
Expand Down Expand Up @@ -1308,10 +1296,62 @@ def __getitem__(self, _):
def __array__(self, dtype=None):
return self.execute().__array__(dtype)

def __interactive_rich_console__(self, console, options):
named = self.name(self.op().name)
projection = named.as_table()
return console.render(projection, options=options)
def preview(
self,
*,
max_rows: int | None = None,
max_length: int | None = None,
max_string: int | None = None,
max_depth: int | None = None,
console_width: int | float | None = None,
) -> None:
"""Print as a Rich Table.
This is an explicit version of what you get when you inspect
this object in interactive mode, except with this version you
can pass formatting options. The options are the same as those exposed
in `ibis.options.interactive`.
Parameters
----------
max_rows
Maximum number of rows to display
max_length
Maximum length for pretty-printed arrays and maps.
max_string
Maximum length for pretty-printed strings.
max_depth
Maximum depth for nested data types.
console_width
Width of the console in characters. If not specified, the width
will be inferred from the console.
Examples
--------
>>> import ibis
>>> ibis.options.interactive = False
>>> t = ibis.examples.penguins.fetch()
>>> t.island.preview(max_rows=3, max_string=5)
┏━━━━━━━━┓
┃ island ┃
┡━━━━━━━━┩
│ stri… │
├────────┤
│ Torg… │
│ Torg… │
│ Torg… │
│ … │
└────────┘
"""
rt = to_rich(

Check warning on line 1346 in ibis/expr/types/generic.py

View check run for this annotation

Codecov / codecov/patch

ibis/expr/types/generic.py#L1346

Added line #L1346 was not covered by tests
self,
max_rows=max_rows,
max_length=max_length,
max_string=max_string,
max_depth=max_depth,
console_width=console_width,
)
rich.print(rt)

Check warning on line 1354 in ibis/expr/types/generic.py

View check run for this annotation

Codecov / codecov/patch

ibis/expr/types/generic.py#L1354

Added line #L1354 was not covered by tests

def __pyarrow_result__(
self, table: pa.Table, data_mapper: type[PyArrowData] | None = None
Expand Down
Loading

0 comments on commit a0b2852

Please sign in to comment.