Skip to content

Commit e93bff6

Browse files
committed
ipython_display_ to set fallback mimetypes
1 parent dee8701 commit e93bff6

File tree

2 files changed

+33
-25
lines changed

2 files changed

+33
-25
lines changed

bigframes/dataframe.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -783,9 +783,7 @@ def __repr__(self) -> str:
783783

784784
opts = bigframes.options.display
785785
max_results = opts.max_rows
786-
# anywdiget mode uses the same display logic as the "deferred" mode
787-
# for faster execution
788-
if opts.repr_mode in ("deferred", "anywidget"):
786+
if opts.repr_mode == "deferred":
789787
return formatter.repr_query_job(self._compute_dry_run())
790788

791789
# TODO(swast): pass max_columns and get the true column count back. Maybe
@@ -831,9 +829,7 @@ def _repr_html_(self) -> str:
831829
"""
832830
opts = bigframes.options.display
833831
max_results = opts.max_rows
834-
# For anywidget mode, return deferred representation
835-
# The actual widget display is handled by _ipython_display_()
836-
if opts.repr_mode in ("deferred", "anywidget"):
832+
if opts.repr_mode == "deferred":
837833
return formatter.repr_query_job(self._compute_dry_run())
838834

839835
# Process blob columns first for non-deferred modes
@@ -910,7 +906,7 @@ def obj_ref_rt_to_html(obj_ref_rt) -> str:
910906
html_string += f"[{row_count} rows x {column_count} columns in total]"
911907
return html_string
912908

913-
def _ipython_display_(self):
909+
def _repr_mimebundle_(self, include=None, exclude=None):
914910
"""
915911
Custom display method for IPython/Jupyter environments.
916912
This is called by IPython's display system when the object is displayed.
@@ -938,26 +934,26 @@ def _ipython_display_(self):
938934

939935
# Create and display the widget
940936
widget = display.TableWidget(df)
937+
widget_repr = widget._repr_mimebundle_(include=include, exclude=exclude)
941938

942-
# IPython will automatically display the widget
943-
# since we're returning it from _ipython_display_()
944-
from IPython.display import display as ipython_display
945-
946-
ipython_display(widget)
947-
return # Important: return None to signal we handled display
939+
# Use deferred repr for text/plain of anywidget display.
940+
# This avoids kicking off a query when the user is just
941+
# printing the last expression in a cell.
942+
widget_repr["text/plain"] = repr(self)
943+
widget_repr["text/html"] = self._repr_html_()
944+
return widget_repr
948945

949946
except (AttributeError, ValueError, ImportError):
950947
# Fallback: let IPython use _repr_html_() instead
951948
warnings.warn(
952949
"Anywidget mode is not available. "
953950
"Please `pip install anywidget traitlets` or `pip install 'bigframes[anywidget]'` to use interactive tables. "
954-
f"Falling back to deferred mode. Error: {traceback.format_exc()}"
951+
f"Falling back to static HTML. Error: {traceback.format_exc()}"
955952
)
956953
# Don't return anything - let IPython fall back to _repr_html_()
957-
return
954+
pass
958955

959-
# For other modes, don't handle display - let IPython use _repr_html_()
960-
return
956+
return {"text/html": self._repr_html_(), "text/plain": repr(self)}
961957

962958
def __delitem__(self, key: str):
963959
df = self.drop(columns=[key])

tests/system/small/test_anywidget.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -551,21 +551,33 @@ def test_widget_row_count_reflects_actual_data_available(
551551
assert widget.page_size == 2 # Respects the display option
552552

553553

554-
def test_repr_html_anywidget_fallback(paginated_bf_df: bf.dataframe.DataFrame):
554+
def test_repr_mimebundle_anywidget_fallback(paginated_bf_df: bf.dataframe.DataFrame):
555555
"""
556-
Test that _repr_html_ falls back to deferred mode when anywidget is not available.
556+
Test that _repr_mimebundle_ falls back to static html when anywidget is not available.
557557
"""
558558
with bf.option_context("display.repr_mode", "anywidget"):
559559
# Use a mock to simulate the absence of the 'anywidget' module.
560560
with mock.patch.dict(
561561
"sys.modules", {"anywidget": None, "IPython": mock.MagicMock()}
562562
):
563-
# The warning is now expected inside the _ipython_display_ call, not _repr_html_
564-
# The test setup doesn't easily allow capturing warnings from ipython display hooks.
565-
# Instead we focus on the fallback behavior of _repr_html_
566-
html = paginated_bf_df._repr_html_()
567-
assert "Computation deferred." in html
568-
assert "Computation will process" in html
563+
bundle = paginated_bf_df._repr_mimebundle_()
564+
assert "application/vnd.jupyter.widget-view+json" not in bundle
565+
assert "text/html" in bundle
566+
html = bundle["text/html"]
567+
assert "page_1_row_1" in html
568+
assert "page_1_row_2" in html
569+
assert "page_2_row_1" not in html
570+
571+
572+
def test_repr_mimebundle_anywidget_success(paginated_bf_df: bf.dataframe.DataFrame):
573+
"""
574+
Test that _repr_mimebundle_ returns a widget view when anywidget is available.
575+
"""
576+
with bf.option_context("display.repr_mode", "anywidget"):
577+
bundle = paginated_bf_df._repr_mimebundle_()
578+
assert "application/vnd.jupyter.widget-view+json" in bundle
579+
assert "text/html" in bundle
580+
assert "text/plain" in bundle
569581

570582

571583
# TODO(b/332316283): Add tests for custom index and multiindex

0 commit comments

Comments
 (0)