Skip to content

Commit 0dc287e

Browse files
committed
feat: Refactor anywidget display to use _ipython_display_
1 parent f7fd2d2 commit 0dc287e

File tree

3 files changed

+75
-47
lines changed

3 files changed

+75
-47
lines changed

README.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ For details, see the `third_party
110110
directory.
111111

112112

113+
Display Enhancements
114+
--------------------
115+
116+
**DataFrame `_repr_html_()` and `_ipython_display_()` Integration**
117+
118+
The `bigframes.pandas.DataFrame._repr_html_()` method has been updated. When `bpd.options.display.repr_mode` is set to “anywidget”, it will:
119+
120+
* Wrap the import of `anywidget` in a `try...except ImportError` block. If the dependency is not found, it will issue a `warnings.warn` message and fall back to returning the deferred representation.
121+
* If the import is successful, it instantiates a new `TableWidget`, passing the DataFrame's data.
122+
* Return the widget instance, which Jupyter automatically renders. A new widget instance is created for each `_repr_html_()` call to ensure cell outputs are isolated.
123+
124+
A new `_ipython_display_()` method has been introduced to handle the actual widget rendering, separating concerns from `_repr_html_()`.
125+
113126
Contact Us
114127
----------
115128

bigframes/dataframe.py

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -837,10 +837,12 @@ def _repr_html_(self) -> str:
837837
"""
838838
opts = bigframes.options.display
839839
max_results = opts.max_rows
840-
if opts.repr_mode == "deferred":
840+
# For anywidget mode, return deferred representation
841+
# The actual widget display is handled by _ipython_display_()
842+
if opts.repr_mode in ("deferred", "anywidget"):
841843
return formatter.repr_query_job(self._compute_dry_run())
842844

843-
# Process blob columns first, regardless of display mode
845+
# Process blob columns first for non-deferred modes
844846
self._cached()
845847
df = self.copy()
846848
if bigframes.options.display.blob_display:
@@ -855,29 +857,6 @@ def _repr_html_(self) -> str:
855857
else:
856858
blob_cols = []
857859

858-
if opts.repr_mode == "anywidget":
859-
try:
860-
from IPython.display import display as ipython_display
861-
862-
from bigframes import display
863-
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())
868-
869-
ipython_display(widget)
870-
return "" # Return empty string since we used display()
871-
872-
except (AttributeError, ValueError, ImportError):
873-
# Fallback if anywidget is not available
874-
warnings.warn(
875-
"Anywidget mode is not available. "
876-
"Please `pip install anywidget traitlets` or `pip install 'bigframes[anywidget]'` to use interactive tables. "
877-
f"Falling back to deferred mode. Error: {traceback.format_exc()}"
878-
)
879-
return formatter.repr_query_job(self._compute_dry_run())
880-
881860
# Continue with regular HTML rendering for non-anywidget modes
882861
# TODO(swast): pass max_columns and get the true column count back. Maybe
883862
# get 1 more column than we have requested so that pandas can add the
@@ -937,6 +916,55 @@ def obj_ref_rt_to_html(obj_ref_rt) -> str:
937916
html_string += f"[{row_count} rows x {column_count} columns in total]"
938917
return html_string
939918

919+
def _ipython_display_(self):
920+
"""
921+
Custom display method for IPython/Jupyter environments.
922+
This is called by IPython's display system when the object is displayed.
923+
"""
924+
opts = bigframes.options.display
925+
926+
# Only handle widget display in anywidget mode
927+
if opts.repr_mode == "anywidget":
928+
try:
929+
from bigframes import display
930+
931+
# Process blob columns if needed
932+
self._cached()
933+
df = self.copy()
934+
if bigframes.options.display.blob_display:
935+
blob_cols = [
936+
series_name
937+
for series_name, series in df.items()
938+
if series.dtype == bigframes.dtypes.OBJ_REF_DTYPE
939+
]
940+
for col in blob_cols:
941+
df[col] = df[col].blob._get_runtime(
942+
mode="R", with_metadata=True
943+
)
944+
945+
# Create and display the widget
946+
widget = display.TableWidget(df)
947+
948+
# IPython will automatically display the widget
949+
# since we're returning it from _ipython_display_()
950+
from IPython.display import display as ipython_display
951+
952+
ipython_display(widget)
953+
return # Important: return None to signal we handled display
954+
955+
except (AttributeError, ValueError, ImportError):
956+
# Fallback: let IPython use _repr_html_() instead
957+
warnings.warn(
958+
"Anywidget mode is not available. "
959+
"Please `pip install anywidget traitlets` or `pip install 'bigframes[anywidget]'` to use interactive tables. "
960+
f"Falling back to deferred mode. Error: {traceback.format_exc()}"
961+
)
962+
# Don't return anything - let IPython fall back to _repr_html_()
963+
return
964+
965+
# For other modes, don't handle display - let IPython use _repr_html_()
966+
return
967+
940968
def __delitem__(self, key: str):
941969
df = self.drop(columns=[key])
942970
self._set_block(df._get_block())

notebooks/dataframes/anywidget_mode.ipynb

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"id": "ca22f059",
3737
"metadata": {},
3838
"outputs": [],
39+
"outputs": [],
3940
"source": [
4041
"import bigframes.pandas as bpd"
4142
]
@@ -143,6 +144,7 @@
143144
"data": {
144145
"application/vnd.jupyter.widget-view+json": {
145146
"model_id": "8fcad7b7e408422cae71d519cd2d4980",
147+
"model_id": "4cc789c49be246bb94967e625986900a",
146148
"version_major": 2,
147149
"version_minor": 1
148150
},
@@ -152,17 +154,6 @@
152154
},
153155
"metadata": {},
154156
"output_type": "display_data"
155-
},
156-
{
157-
"data": {
158-
"text/html": [],
159-
"text/plain": [
160-
"Computation deferred. Computation will process 171.4 MB"
161-
]
162-
},
163-
"execution_count": 6,
164-
"metadata": {},
165-
"output_type": "execute_result"
166157
}
167158
],
168159
"source": [
@@ -206,6 +197,7 @@
206197
"data": {
207198
"application/vnd.jupyter.widget-view+json": {
208199
"model_id": "06cb98c577514d5c9654a7792d93f8e6",
200+
"model_id": "b1089664ce03440d81ef206da1210229",
209201
"version_major": 2,
210202
"version_minor": 1
211203
},
@@ -284,6 +276,8 @@
284276
{
285277
"data": {
286278
"text/html": [
279+
"✅ Completed. \n",
280+
" Query processed 171.4 MB in a moment of slot time.\n",
287281
"✅ Completed. \n",
288282
" Query processed 171.4 MB in a moment of slot time.\n",
289283
" "
@@ -306,6 +300,7 @@
306300
"data": {
307301
"application/vnd.jupyter.widget-view+json": {
308302
"model_id": "1672f826f7a347e38539dbb5fb72cd43",
303+
"model_id": "f18925fc13304fb2ae34056f2cb1c68b",
309304
"version_major": 2,
310305
"version_minor": 1
311306
},
@@ -346,6 +341,7 @@
346341
"text/html": [
347342
"✅ Completed. \n",
348343
" Query processed 85.9 kB in 12 seconds of slot time.\n",
344+
" Query processed 85.9 kB in 15 seconds of slot time.\n",
349345
" "
350346
],
351347
"text/plain": [
@@ -359,6 +355,7 @@
359355
"name": "stderr",
360356
"output_type": "stream",
361357
"text": [
358+
"/usr/local/google/home/shuowei/src/python-bigquery-dataframes/bigframes/dtypes.py:987: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n",
362359
"/usr/local/google/home/shuowei/src/python-bigquery-dataframes/bigframes/dtypes.py:987: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n",
363360
"instead of using `db_dtypes` in the future when available in pandas\n",
364361
"(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n",
@@ -381,6 +378,7 @@
381378
"data": {
382379
"application/vnd.jupyter.widget-view+json": {
383380
"model_id": "127a2e356b834c18b6f07c58ee2c4228",
381+
"model_id": "2335d3161b704a6da85165dbebf5ca0f",
384382
"version_major": 2,
385383
"version_minor": 1
386384
},
@@ -390,17 +388,6 @@
390388
},
391389
"metadata": {},
392390
"output_type": "display_data"
393-
},
394-
{
395-
"data": {
396-
"text/html": [],
397-
"text/plain": [
398-
"Computation deferred. Computation will process 0 Bytes"
399-
]
400-
},
401-
"execution_count": 10,
402-
"metadata": {},
403-
"output_type": "execute_result"
404391
}
405392
],
406393
"source": [

0 commit comments

Comments
 (0)