Skip to content

Commit e4e3ec8

Browse files
authored
revert: DataFrame display uses IPython's _repr_mimebundle_ (#2316)
revert "refactor: Migrate DataFrame display to use IPython's _repr_mimebundle_() protocol for anywidget mode (#2271)" This reverts commit 41630b5 for bug 466155761. Verified at Colab: screen/AjTEQC8SrSfMqhN Fixes #< 466155761 > 🦕
1 parent d993831 commit e4e3ec8

File tree

9 files changed

+128
-796
lines changed

9 files changed

+128
-796
lines changed

bigframes/core/indexes/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,9 @@ def __repr__(self) -> __builtins__.str:
376376
# metadata, like we do with DataFrame.
377377
opts = bigframes.options.display
378378
max_results = opts.max_rows
379-
if opts.repr_mode == "deferred":
379+
# anywdiget mode uses the same display logic as the "deferred" mode
380+
# for faster execution
381+
if opts.repr_mode in ("deferred", "anywidget"):
380382
_, dry_run_query_job = self._block._compute_dry_run()
381383
return formatter.repr_query_job(dry_run_query_job)
382384

bigframes/dataframe.py

Lines changed: 48 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,9 @@ def __repr__(self) -> str:
789789

790790
opts = bigframes.options.display
791791
max_results = opts.max_rows
792-
if opts.repr_mode == "deferred":
792+
# anywdiget mode uses the same display logic as the "deferred" mode
793+
# for faster execution
794+
if opts.repr_mode in ("deferred", "anywidget"):
793795
return formatter.repr_query_job(self._compute_dry_run())
794796

795797
# TODO(swast): pass max_columns and get the true column count back. Maybe
@@ -827,138 +829,68 @@ def __repr__(self) -> str:
827829
lines.append(f"[{row_count} rows x {column_count} columns]")
828830
return "\n".join(lines)
829831

830-
def _get_display_df_and_blob_cols(self) -> tuple[DataFrame, list[str]]:
831-
"""Process blob columns for display."""
832-
df = self
833-
blob_cols = []
832+
def _repr_html_(self) -> str:
833+
"""
834+
Returns an html string primarily for use by notebooks for displaying
835+
a representation of the DataFrame. Displays 20 rows by default since
836+
many notebooks are not configured for large tables.
837+
"""
838+
opts = bigframes.options.display
839+
max_results = opts.max_rows
840+
if opts.repr_mode == "deferred":
841+
return formatter.repr_query_job(self._compute_dry_run())
842+
843+
# Process blob columns first, regardless of display mode
844+
self._cached()
845+
df = self.copy()
834846
if bigframes.options.display.blob_display:
835847
blob_cols = [
836848
series_name
837-
for series_name, series in self.items()
849+
for series_name, series in df.items()
838850
if series.dtype == bigframes.dtypes.OBJ_REF_DTYPE
839851
]
840-
if blob_cols:
841-
df = self.copy()
842-
for col in blob_cols:
843-
# TODO(garrettwu): Not necessary to get access urls for all the rows. Update when having a to get URLs from local data.
844-
df[col] = df[col].blob._get_runtime(mode="R", with_metadata=True)
845-
return df, blob_cols
846-
847-
def _get_anywidget_bundle(self, include=None, exclude=None):
848-
"""
849-
Helper method to create and return the anywidget mimebundle.
850-
This function encapsulates the logic for anywidget display.
851-
"""
852-
from bigframes import display
853-
854-
# TODO(shuowei): Keep blob_cols and pass them to TableWidget so that they can render properly.
855-
df, _ = self._get_display_df_and_blob_cols()
856-
857-
# Create and display the widget
858-
widget = display.TableWidget(df)
859-
widget_repr_result = widget._repr_mimebundle_(include=include, exclude=exclude)
860-
861-
# Handle both tuple (data, metadata) and dict returns
862-
if isinstance(widget_repr_result, tuple):
863-
widget_repr = dict(widget_repr_result[0]) # Extract data dict from tuple
852+
for col in blob_cols:
853+
# TODO(garrettwu): Not necessary to get access urls for all the rows. Update when having a to get URLs from local data.
854+
df[col] = df[col].blob._get_runtime(mode="R", with_metadata=True)
864855
else:
865-
widget_repr = dict(widget_repr_result)
866-
867-
# At this point, we have already executed the query as part of the
868-
# widget construction. Let's use the information available to render
869-
# the HTML and plain text versions.
870-
widget_repr["text/html"] = widget.table_html
871-
872-
widget_repr["text/plain"] = self._create_text_representation(
873-
widget._cached_data, widget.row_count
874-
)
875-
876-
return widget_repr
877-
878-
def _create_text_representation(
879-
self, pandas_df: pandas.DataFrame, total_rows: typing.Optional[int]
880-
) -> str:
881-
"""Create a text representation of the DataFrame."""
882-
opts = bigframes.options.display
883-
with display_options.pandas_repr(opts):
884-
import pandas.io.formats
885-
886-
# safe to mutate this, this dict is owned by this code, and does not affect global config
887-
to_string_kwargs = (
888-
pandas.io.formats.format.get_dataframe_repr_params() # type: ignore
889-
)
890-
if not self._has_index:
891-
to_string_kwargs.update({"index": False})
856+
blob_cols = []
892857

893-
# We add our own dimensions string, so don't want pandas to.
894-
to_string_kwargs.update({"show_dimensions": False})
895-
repr_string = pandas_df.to_string(**to_string_kwargs)
858+
if opts.repr_mode == "anywidget":
859+
try:
860+
from IPython.display import display as ipython_display
896861

897-
lines = repr_string.split("\n")
862+
from bigframes import display
898863

899-
if total_rows is not None and total_rows > len(pandas_df):
900-
lines.append("...")
864+
# Always create a new widget instance for each display call
865+
# This ensures that each cell gets its own widget and prevents
866+
# unintended sharing between cells
867+
widget = display.TableWidget(df.copy())
901868

902-
lines.append("")
903-
column_count = len(self.columns)
904-
lines.append(f"[{total_rows or '?'} rows x {column_count} columns]")
905-
return "\n".join(lines)
869+
ipython_display(widget)
870+
return "" # Return empty string since we used display()
906871

907-
def _repr_mimebundle_(self, include=None, exclude=None):
908-
"""
909-
Custom display method for IPython/Jupyter environments.
910-
This is called by IPython's display system when the object is displayed.
911-
"""
912-
opts = bigframes.options.display
913-
# Only handle widget display in anywidget mode
914-
if opts.repr_mode == "anywidget":
915-
try:
916-
return self._get_anywidget_bundle(include=include, exclude=exclude)
917-
918-
except ImportError:
919-
# Anywidget is an optional dependency, so warn rather than fail.
920-
# TODO(shuowei): When Anywidget becomes the default for all repr modes,
921-
# remove this warning.
872+
except (AttributeError, ValueError, ImportError):
873+
# Fallback if anywidget is not available
922874
warnings.warn(
923875
"Anywidget mode is not available. "
924876
"Please `pip install anywidget traitlets` or `pip install 'bigframes[anywidget]'` to use interactive tables. "
925-
f"Falling back to static HTML. Error: {traceback.format_exc()}"
877+
f"Falling back to deferred mode. Error: {traceback.format_exc()}"
926878
)
879+
return formatter.repr_query_job(self._compute_dry_run())
927880

928-
# In non-anywidget mode, fetch data once and use it for both HTML
929-
# and plain text representations to avoid multiple queries.
930-
opts = bigframes.options.display
931-
max_results = opts.max_rows
932-
933-
df, blob_cols = self._get_display_df_and_blob_cols()
934-
881+
# Continue with regular HTML rendering for non-anywidget modes
882+
# TODO(swast): pass max_columns and get the true column count back. Maybe
883+
# get 1 more column than we have requested so that pandas can add the
884+
# ... for us?
935885
pandas_df, row_count, query_job = df._block.retrieve_repr_request_results(
936886
max_results
937887
)
888+
938889
self._set_internal_query_job(query_job)
939890
column_count = len(pandas_df.columns)
940891

941-
html_string = self._create_html_representation(
942-
pandas_df, row_count, column_count, blob_cols
943-
)
944-
945-
text_representation = self._create_text_representation(pandas_df, row_count)
946-
947-
return {"text/html": html_string, "text/plain": text_representation}
948-
949-
def _create_html_representation(
950-
self,
951-
pandas_df: pandas.DataFrame,
952-
row_count: int,
953-
column_count: int,
954-
blob_cols: list[str],
955-
) -> str:
956-
"""Create an HTML representation of the DataFrame."""
957-
opts = bigframes.options.display
958892
with display_options.pandas_repr(opts):
959-
# TODO(shuowei, b/464053870): Escaping HTML would be useful, but
960-
# `escape=False` is needed to show images. We may need to implement
961-
# a full-fledged repr module to better support types not in pandas.
893+
# Allows to preview images in the DataFrame. The implementation changes the string repr as well, that it doesn't truncate strings or escape html charaters such as "<" and ">". We may need to implement a full-fledged repr module to better support types not in pandas.
962894
if bigframes.options.display.blob_display and blob_cols:
963895

964896
def obj_ref_rt_to_html(obj_ref_rt) -> str:
@@ -987,12 +919,15 @@ def obj_ref_rt_to_html(obj_ref_rt) -> str:
987919

988920
# set max_colwidth so not to truncate the image url
989921
with pandas.option_context("display.max_colwidth", None):
922+
max_rows = pandas.get_option("display.max_rows")
923+
max_cols = pandas.get_option("display.max_columns")
924+
show_dimensions = pandas.get_option("display.show_dimensions")
990925
html_string = pandas_df.to_html(
991926
escape=False,
992927
notebook=True,
993-
max_rows=pandas.get_option("display.max_rows"),
994-
max_cols=pandas.get_option("display.max_columns"),
995-
show_dimensions=pandas.get_option("display.show_dimensions"),
928+
max_rows=max_rows,
929+
max_cols=max_cols,
930+
show_dimensions=show_dimensions,
996931
formatters=formatters, # type: ignore
997932
)
998933
else:

bigframes/streaming/dataframe.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,13 @@ def __repr__(self, *args, **kwargs):
291291

292292
__repr__.__doc__ = _curate_df_doc(inspect.getdoc(dataframe.DataFrame.__repr__))
293293

294-
def _repr_mimebundle_(self, *args, **kwargs):
295-
return _return_type_wrapper(self._df._repr_mimebundle_, StreamingDataFrame)(
294+
def _repr_html_(self, *args, **kwargs):
295+
return _return_type_wrapper(self._df._repr_html_, StreamingDataFrame)(
296296
*args, **kwargs
297297
)
298298

299-
_repr_mimebundle_.__doc__ = _curate_df_doc(
300-
inspect.getdoc(dataframe.DataFrame._repr_mimebundle_)
299+
_repr_html_.__doc__ = _curate_df_doc(
300+
inspect.getdoc(dataframe.DataFrame._repr_html_)
301301
)
302302

303303
@property

0 commit comments

Comments
 (0)