Skip to content

Commit 84a7b3d

Browse files
committed
handle opitonal import
1 parent e000fdd commit 84a7b3d

File tree

5 files changed

+55
-37
lines changed

5 files changed

+55
-37
lines changed

bigframes/dataframe.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -780,9 +780,14 @@ def _repr_html_(self) -> str:
780780
try:
781781
from bigframes import display
782782

783-
return display.TableWidget(self)
784-
except AttributeError:
783+
# Store the widget for _repr_mimebundle_ to use
784+
self._anywidget_instance = display.TableWidget(self)
785+
# Return a fallback HTML string
786+
return "Interactive table widget (anywidget mode)"
787+
except (AttributeError, ValueError):
785788
# Fallback if anywidget is not available
789+
import warnings
790+
786791
warnings.warn(
787792
"Anywidget mode is not available, falling back to deferred mode."
788793
)

bigframes/display/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,11 @@
1414

1515
from __future__ import annotations
1616

17-
import warnings
18-
1917
try:
2018
import anywidget # noqa
2119

2220
from bigframes.display.anywidget import TableWidget
2321

2422
__all__ = ["TableWidget"]
2523
except Exception:
26-
msg = "Anywidget mode not available as anywidget is not installed."
27-
warnings.warn(msg)
24+
pass

bigframes/display/anywidget.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,41 +12,45 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from __future__ import annotations
16+
1517
from importlib import resources
1618
import functools
1719
import math
18-
from typing import Iterator
20+
from typing import Iterator, TYPE_CHECKING
1921
import uuid
2022

21-
import anywidget # type: ignore
2223
import pandas as pd
23-
import traitlets
2424

2525
import bigframes
2626

27+
# Follow the same pattern as Polars
28+
anywidget_installed = True
29+
if TYPE_CHECKING:
30+
import anywidget
31+
import traitlets
32+
else:
33+
try:
34+
import anywidget
35+
import traitlets
36+
except Exception:
37+
anywidget_installed = False
38+
2739

2840
class TableWidget(anywidget.AnyWidget):
2941
"""
3042
An interactive, paginated table widget for BigFrames DataFrames.
3143
"""
3244

33-
@functools.cached_property
34-
def _esm(self):
35-
"""Load JavaScript code from external file."""
36-
return resources.read_text(bigframes.display, "table_widget.js")
37-
38-
page = traitlets.Int(0).tag(sync=True)
39-
page_size = traitlets.Int(25).tag(sync=True)
40-
row_count = traitlets.Int(0).tag(sync=True)
41-
table_html = traitlets.Unicode().tag(sync=True)
42-
4345
def __init__(self, dataframe):
4446
"""
4547
Initialize the TableWidget.
46-
4748
Args:
4849
dataframe: The Bigframes Dataframe to display.
4950
"""
51+
if not anywidget_installed:
52+
raise ValueError("Anywidget is not installed, cannot create TableWidget.")
53+
5054
super().__init__()
5155
self._dataframe = dataframe
5256

@@ -67,6 +71,17 @@ def __init__(self, dataframe):
6771
# get the initial page
6872
self._set_table_html()
6973

74+
# Use functools.cached_property instead of @property for _esm
75+
@functools.cached_property
76+
def _esm(self):
77+
"""Load JavaScript code from external file."""
78+
return resources.read_text(bigframes.display, "table_widget.js")
79+
80+
page = traitlets.Int(0).tag(sync=True)
81+
page_size = traitlets.Int(25).tag(sync=True)
82+
row_count = traitlets.Int(0).tag(sync=True)
83+
table_html = traitlets.Unicode().tag(sync=True)
84+
7085
@traitlets.validate("page")
7186
def _validate_page(self, proposal):
7287
"""Validate and clamp page number to valid range."""
@@ -79,7 +94,6 @@ def _validate_page(self, proposal):
7994
def _get_next_batch(self) -> bool:
8095
"""
8196
Gets the next batch of data from the generator and appends to cache.
82-
8397
Returns:
8498
bool: True if a batch was successfully loaded, False otherwise.
8599
"""
@@ -115,7 +129,7 @@ def _set_table_html(self):
115129
while len(self._cached_data) < end and not self._all_data_loaded:
116130
self._get_next_batch()
117131

118-
# Get the data fro the current page
132+
# Get the data for the current page
119133
page_data = self._cached_data.iloc[start:end]
120134

121135
# Generate HTML table
@@ -129,5 +143,5 @@ def _set_table_html(self):
129143

130144
@traitlets.observe("page")
131145
def _page_changed(self, change):
132-
"""Handler for when the page nubmer is changed from the frontend."""
146+
"""Handler for when the page number is changed from the frontend."""
133147
self._set_table_html()

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
# install anywidget for SQL
9090
"anywidget": [
9191
"anywidget>=0.9.18",
92+
"traitlets",
9293
],
9394
}
9495
extras["all"] = list(sorted(frozenset(itertools.chain.from_iterable(extras.values()))))

tests/system/small/test_anywidget.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import pytest
1717

1818
import bigframes as bf
19-
from bigframes.display import TableWidget
2019

2120
pytest.importorskip("anywidget")
2221

@@ -52,13 +51,15 @@ def paginated_bf_df(
5251

5352

5453
@pytest.fixture(scope="module")
55-
def table_widget(paginated_bf_df: bf.dataframe.DataFrame) -> TableWidget:
54+
def table_widget(paginated_bf_df: bf.dataframe.DataFrame):
5655
"""
5756
Helper fixture to create a TableWidget instance with a fixed page size.
5857
This reduces duplication across tests that use the same widget configuration.
5958
"""
59+
from bigframes import display
60+
6061
with bf.option_context("display.repr_mode", "anywidget", "display.max_rows", 2):
61-
widget = TableWidget(paginated_bf_df)
62+
widget = display.TableWidget(paginated_bf_df)
6263
return widget
6364

6465

@@ -89,9 +90,9 @@ def test_repr_anywidget_initialization_sets_page_to_zero(
8990
):
9091
"""A TableWidget should initialize with the page number set to 0."""
9192
with bf.option_context("display.repr_mode", "anywidget"):
92-
from bigframes.display import TableWidget
93+
from bigframes import display
9394

94-
widget = TableWidget(paginated_bf_df)
95+
widget = display.TableWidget(paginated_bf_df)
9596

9697
assert widget.page == 0
9798

@@ -101,9 +102,9 @@ def test_repr_anywidget_initialization_sets_page_size_from_options(
101102
):
102103
"""A TableWidget should initialize its page size from bf.options."""
103104
with bf.option_context("display.repr_mode", "anywidget"):
104-
from bigframes.display import TableWidget
105+
from bigframes import display
105106

106-
widget = TableWidget(paginated_bf_df)
107+
widget = display.TableWidget(paginated_bf_df)
107108

108109
assert widget.page_size == bf.options.display.max_rows
109110

@@ -114,15 +115,15 @@ def test_repr_anywidget_initialization_sets_row_count(
114115
):
115116
"""A TableWidget should initialize with the correct total row count."""
116117
with bf.option_context("display.repr_mode", "anywidget"):
117-
from bigframes.display import TableWidget
118+
from bigframes import display
118119

119-
widget = TableWidget(paginated_bf_df)
120+
widget = display.TableWidget(paginated_bf_df)
120121

121122
assert widget.row_count == len(paginated_pandas_df)
122123

123124

124125
def test_repr_anywidget_display_first_page_on_load(
125-
table_widget: TableWidget, paginated_pandas_df: pd.DataFrame
126+
table_widget, paginated_pandas_df: pd.DataFrame
126127
):
127128
"""
128129
Given a widget, when it is first loaded, then it should display
@@ -136,7 +137,7 @@ def test_repr_anywidget_display_first_page_on_load(
136137

137138

138139
def test_repr_anywidget_navigate_to_second_page(
139-
table_widget: TableWidget, paginated_pandas_df: pd.DataFrame
140+
table_widget, paginated_pandas_df: pd.DataFrame
140141
):
141142
"""
142143
Given a widget, when the page is set to 1, then it should display
@@ -152,7 +153,7 @@ def test_repr_anywidget_navigate_to_second_page(
152153

153154

154155
def test_repr_anywidget_navigate_to_last_page(
155-
table_widget: TableWidget, paginated_pandas_df: pd.DataFrame
156+
table_widget, paginated_pandas_df: pd.DataFrame
156157
):
157158
"""
158159
Given a widget, when the page is set to the last page (2),
@@ -168,7 +169,7 @@ def test_repr_anywidget_navigate_to_last_page(
168169

169170

170171
def test_repr_anywidget_page_clamp_to_zero_for_negative_input(
171-
table_widget: TableWidget, paginated_pandas_df: pd.DataFrame
172+
table_widget, paginated_pandas_df: pd.DataFrame
172173
):
173174
"""
174175
Given a widget, when a negative page number is set,
@@ -184,7 +185,7 @@ def test_repr_anywidget_page_clamp_to_zero_for_negative_input(
184185

185186

186187
def test_repr_anywidget_page_clamp_to_last_page_for_out_of_bounds_input(
187-
table_widget: TableWidget, paginated_pandas_df: pd.DataFrame
188+
table_widget, paginated_pandas_df: pd.DataFrame
188189
):
189190
"""
190191
Given a widget, when a page number greater than the max is set,

0 commit comments

Comments
 (0)