2222import itertools
2323import numbers
2424import textwrap
25+ import traceback
2526import typing
2627from typing import (
2728 Any ,
4849import pyarrow as pa
4950import typing_extensions
5051
52+ import bigframes ._config .display_options as display_options
5153import bigframes .core
5254from bigframes .core import agg_expressions , groupby , log_adapter
5355import bigframes .core .block_transforms as block_ops
@@ -568,6 +570,106 @@ def reset_index(
568570 block = block .assign_label (self ._value_column , name )
569571 return bigframes .dataframe .DataFrame (block )
570572
573+ def _get_anywidget_bundle (
574+ self , include = None , exclude = None
575+ ) -> tuple [dict [str , Any ], dict [str , Any ]]:
576+ """
577+ Helper method to create and return the anywidget mimebundle for Series.
578+ """
579+ from bigframes import display
580+
581+ # Convert Series to DataFrame for TableWidget
582+ series_df = self .to_frame ()
583+
584+ # Create and display the widget
585+ widget = display .TableWidget (series_df )
586+ widget_repr_result = widget ._repr_mimebundle_ (include = include , exclude = exclude )
587+
588+ # Handle both tuple (data, metadata) and dict returns
589+ if isinstance (widget_repr_result , tuple ):
590+ widget_repr , widget_metadata = widget_repr_result
591+ else :
592+ widget_repr = widget_repr_result
593+ widget_metadata = {}
594+
595+ widget_repr = dict (widget_repr )
596+
597+ # Add text representation
598+ widget_repr ["text/plain" ] = self ._create_text_representation (
599+ widget ._cached_data , widget .row_count
600+ )
601+
602+ return widget_repr , widget_metadata
603+
604+ def _create_text_representation (
605+ self , pandas_df : pandas .DataFrame , total_rows : typing .Optional [int ]
606+ ) -> str :
607+ """Create a text representation of the Series."""
608+ opts = bigframes .options .display
609+ with display_options .pandas_repr (opts ):
610+ import pandas .io .formats
611+
612+ # safe to mutate this, this dict is owned by this code, and does not affect global config
613+ to_string_kwargs = (
614+ pandas .io .formats .format .get_series_repr_params () # type: ignore
615+ )
616+ if len (self ._block .index_columns ) == 0 :
617+ to_string_kwargs .update ({"index" : False })
618+ # Get the first column since Series DataFrame has only one column
619+ pd_series = pandas_df .iloc [:, 0 ]
620+ repr_string = pd_series .to_string (** to_string_kwargs )
621+
622+ lines = repr_string .split ("\n " )
623+
624+ if total_rows is not None and total_rows > len (pd_series ):
625+ lines .append ("..." )
626+
627+ lines .append ("" )
628+ lines .append (f"[{ total_rows } rows]" )
629+ return "\n " .join (lines )
630+
631+ def _repr_mimebundle_ (self , include = None , exclude = None ):
632+ """
633+ Custom display method for IPython/Jupyter environments.
634+ This is called by IPython's display system when the object is displayed.
635+ """
636+ opts = bigframes .options .display
637+
638+ # Only handle widget display in anywidget mode
639+ if opts .repr_mode == "anywidget" :
640+ try :
641+ return self ._get_anywidget_bundle (include = include , exclude = exclude )
642+
643+ except ImportError :
644+ # Anywidget is an optional dependency, so warn rather than fail.
645+ warnings .warn (
646+ "Anywidget mode is not available. "
647+ "Please `pip install anywidget traitlets` or `pip install 'bigframes[anywidget]'` to use interactive tables. "
648+ f"Falling back to static HTML. Error: { traceback .format_exc ()} "
649+ )
650+ # Fall back to regular HTML representation
651+ pass
652+
653+ # Continue with regular HTML rendering for non-anywidget modes
654+ self ._cached ()
655+ pandas_df , row_count , query_job = self ._block .retrieve_repr_request_results (
656+ opts .max_rows
657+ )
658+ self ._set_internal_query_job (query_job )
659+
660+ pd_series = pandas_df .iloc [:, 0 ]
661+
662+ # Use pandas Series _repr_html_ if available, otherwise create basic HTML
663+ try :
664+ html_string = pd_series ._repr_html_ ()
665+ except AttributeError :
666+ # Fallback for pandas versions without _repr_html_
667+ html_string = f"<pre>{ pd_series .to_string ()} </pre>"
668+
669+ text_representation = self ._create_text_representation (pandas_df , row_count )
670+
671+ return {"text/html" : html_string , "text/plain" : text_representation }
672+
571673 def __repr__ (self ) -> str :
572674 # Protect against errors with uninitialized Series. See:
573675 # https://github.com/googleapis/python-bigquery-dataframes/issues/728
@@ -582,24 +684,16 @@ def __repr__(self) -> str:
582684 max_results = opts .max_rows
583685 # anywdiget mode uses the same display logic as the "deferred" mode
584686 # for faster execution
585- if opts .repr_mode in ( "deferred" , "anywidget" ) :
687+ if opts .repr_mode == "deferred" :
586688 return formatter .repr_query_job (self ._compute_dry_run ())
587689
588690 self ._cached ()
589- pandas_df , _ , query_job = self ._block .retrieve_repr_request_results (max_results )
691+ pandas_df , row_count , query_job = self ._block .retrieve_repr_request_results (
692+ max_results
693+ )
590694 self ._set_internal_query_job (query_job )
591695
592- pd_series = pandas_df .iloc [:, 0 ]
593-
594- import pandas .io .formats
595-
596- # safe to mutate this, this dict is owned by this code, and does not affect global config
597- to_string_kwargs = pandas .io .formats .format .get_series_repr_params () # type: ignore
598- if len (self ._block .index_columns ) == 0 :
599- to_string_kwargs .update ({"index" : False })
600- repr_string = pd_series .to_string (** to_string_kwargs )
601-
602- return repr_string
696+ return self ._create_text_representation (pandas_df , row_count )
603697
604698 def astype (
605699 self ,
0 commit comments