2323import pandas as pd
2424
2525import bigframes
26- from bigframes .core import blocks
2726import bigframes .dataframe
2827import bigframes .display .html
2928
30- # anywidget and traitlets are optional dependencies. We don't want the import of
31- # this module to fail if they aren't installed, though. Instead, we try to
32- # limit the surface that these packages could affect. This makes unit testing
33- # easier and ensures we don't accidentally make these required packages.
29+ # anywidget and traitlets are optional dependencies. We don't want the import of this
30+ # module to fail if they aren't installed, though. Instead, we try to limit the surface that
31+ # these packages could affect. This makes unit testing easier and ensures we don't
32+ # accidentally make these required packages.
3433try :
3534 import anywidget
3635 import traitlets
4746
4847
4948class TableWidget (WIDGET_BASE ):
50- """An interactive, paginated table widget for BigFrames DataFrames.
51-
52- This widget provides a user-friendly way to display and navigate through
53- large BigQuery DataFrames within a Jupyter environment.
5449 """
55-
56- page = traitlets .Int (0 ).tag (sync = True )
57- page_size = traitlets .Int (0 ).tag (sync = True )
58- row_count = traitlets .Int (0 ).tag (sync = True )
59- table_html = traitlets .Unicode ().tag (sync = True )
60- _initial_load_complete = traitlets .Bool (False ).tag (sync = True )
61- _batches : Optional [blocks .PandasBatches ] = None
62- _error_message = traitlets .Unicode (allow_none = True , default_value = None ).tag (
63- sync = True
64- )
50+ An interactive, paginated table widget for BigFrames DataFrames.
51+ """
6552
6653 def __init__ (self , dataframe : bigframes .dataframe .DataFrame ):
6754 """Initialize the TableWidget.
@@ -74,11 +61,10 @@ def __init__(self, dataframe: bigframes.dataframe.DataFrame):
7461 "Please `pip install anywidget traitlets` or `pip install 'bigframes[anywidget]'` to use TableWidget."
7562 )
7663
77- self ._dataframe = dataframe
78-
7964 super ().__init__ ()
65+ self ._dataframe = dataframe
8066
81- # Initialize attributes that might be needed by observers first
67+ # Initialize attributes that might be needed by observers FIRST
8268 self ._table_id = str (uuid .uuid4 ())
8369 self ._all_data_loaded = False
8470 self ._batch_iter : Optional [Iterator [pd .DataFrame ]] = None
@@ -87,6 +73,9 @@ def __init__(self, dataframe: bigframes.dataframe.DataFrame):
8773 # respect display options for initial page size
8874 initial_page_size = bigframes .options .display .max_rows
8975
76+ # Initialize data fetching attributes.
77+ self ._batches = dataframe ._to_pandas_batches (page_size = initial_page_size )
78+
9079 # set traitlets properties that trigger observers
9180 self .page_size = initial_page_size
9281
@@ -95,21 +84,12 @@ def __init__(self, dataframe: bigframes.dataframe.DataFrame):
9584 # TODO(b/428238610): Start iterating over the result of `to_pandas_batches()`
9685 # before we get here so that the count might already be cached.
9786 # TODO(b/452747934): Allow row_count to be None and check to see if
98- # there are multiple pages and show "page 1 of many" in this case
99- self ._reset_batches_for_new_page_size ()
100- if self ._batches is None or self ._batches .total_rows is None :
101- self ._error_message = "Could not determine total row count. Data might be unavailable or an error occurred."
102- self .row_count = 0
103- else :
104- self .row_count = self ._batches .total_rows
87+ # there are multiple pages and show "page 1 of many" in this case.
88+ self .row_count = self ._batches .total_rows or 0
10589
10690 # get the initial page
10791 self ._set_table_html ()
10892
109- # Signals to the frontend that the initial data load is complete.
110- # Also used as a guard to prevent observers from firing during initialization.
111- self ._initial_load_complete = True
112-
11393 @functools .cached_property
11494 def _esm (self ):
11595 """Load JavaScript code from external file."""
@@ -120,6 +100,11 @@ def _css(self):
120100 """Load CSS code from external file."""
121101 return resources .read_text (bigframes .display , "table_widget.css" )
122102
103+ page = traitlets .Int (0 ).tag (sync = True )
104+ page_size = traitlets .Int (25 ).tag (sync = True )
105+ row_count = traitlets .Int (0 ).tag (sync = True )
106+ table_html = traitlets .Unicode ().tag (sync = True )
107+
123108 @traitlets .validate ("page" )
124109 def _validate_page (self , proposal : Dict [str , Any ]) -> int :
125110 """Validate and clamp the page number to a valid range.
@@ -186,10 +171,7 @@ def _get_next_batch(self) -> bool:
186171 def _batch_iterator (self ) -> Iterator [pd .DataFrame ]:
187172 """Lazily initializes and returns the batch iterator."""
188173 if self ._batch_iter is None :
189- if self ._batches is None :
190- self ._batch_iter = iter ([])
191- else :
192- self ._batch_iter = iter (self ._batches )
174+ self ._batch_iter = iter (self ._batches )
193175 return self ._batch_iter
194176
195177 @property
@@ -199,22 +181,15 @@ def _cached_data(self) -> pd.DataFrame:
199181 return pd .DataFrame (columns = self ._dataframe .columns )
200182 return pd .concat (self ._cached_batches , ignore_index = True )
201183
202- def _reset_batches_for_new_page_size (self ) -> None :
184+ def _reset_batches_for_new_page_size (self ):
203185 """Reset the batch iterator when page size changes."""
204186 self ._batches = self ._dataframe ._to_pandas_batches (page_size = self .page_size )
205-
206187 self ._cached_batches = []
207188 self ._batch_iter = None
208189 self ._all_data_loaded = False
209190
210- def _set_table_html (self ) -> None :
191+ def _set_table_html (self ):
211192 """Sets the current html data based on the current page and page size."""
212- if self ._error_message :
213- self .table_html = (
214- f"<div class='bigframes-error-message'>{ self ._error_message } </div>"
215- )
216- return
217-
218193 start = self .page * self .page_size
219194 end = start + self .page_size
220195
@@ -236,17 +211,13 @@ def _set_table_html(self) -> None:
236211 )
237212
238213 @traitlets .observe ("page" )
239- def _page_changed (self , _change : Dict [str , Any ]) -> None :
214+ def _page_changed (self , _change : Dict [str , Any ]):
240215 """Handler for when the page number is changed from the frontend."""
241- if not self ._initial_load_complete :
242- return
243216 self ._set_table_html ()
244217
245218 @traitlets .observe ("page_size" )
246- def _page_size_changed (self , _change : Dict [str , Any ]) -> None :
219+ def _page_size_changed (self , _change : Dict [str , Any ]):
247220 """Handler for when the page size is changed from the frontend."""
248- if not self ._initial_load_complete :
249- return
250221 # Reset the page to 0 when page size changes to avoid invalid page states
251222 self .page = 0
252223
0 commit comments