@@ -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 :
0 commit comments