109109logger = logging .getLogger (__name__ )
110110
111111
112+ class _ExecutionHistory (pandas .DataFrame ):
113+ @property
114+ def _constructor (self ):
115+ return _ExecutionHistory
116+
117+ def _repr_html_ (self ) -> str | None :
118+ try :
119+ import bigframes .formatting_helpers as formatter
120+
121+ if self .empty :
122+ return "<div>No executions found.</div>"
123+
124+ cols = ["job_id" , "status" , "total_bytes_processed" , "job_url" ]
125+ df_display = self [cols ].copy ()
126+ df_display ["total_bytes_processed" ] = df_display [
127+ "total_bytes_processed"
128+ ].apply (formatter .get_formatted_bytes )
129+
130+ def format_url (url ):
131+ return f'<a target="_blank" href="{ url } ">Open Job</a>' if url else ""
132+
133+ df_display ["job_url" ] = df_display ["job_url" ].apply (format_url )
134+
135+ # Rename job_id to query_id to match user expectations
136+ df_display = df_display .rename (columns = {"job_id" : "query_id" })
137+
138+ compact_html = df_display .to_html (escape = False , index = False )
139+
140+ return compact_html
141+ except Exception :
142+ return super ()._repr_html_ () # type: ignore
143+
144+
112145@log_adapter .class_logger
113146class Session (
114147 third_party_pandas_gbq .GBQIOMixin ,
@@ -233,6 +266,7 @@ def __init__(
233266 )
234267
235268 self ._metrics = metrics .ExecutionMetrics ()
269+ self ._publisher .subscribe (self ._metrics .on_event )
236270 self ._function_session = bff_session .FunctionSession ()
237271 self ._anon_dataset_manager = anonymous_dataset .AnonymousDatasetManager (
238272 self ._clients_provider .bqclient ,
@@ -371,6 +405,10 @@ def slot_millis_sum(self):
371405 """The sum of all slot time used by bigquery jobs in this session."""
372406 return self ._metrics .slot_millis
373407
408+ def execution_history (self ) -> pandas .DataFrame :
409+ """Returns a list of underlying BigQuery executions initiated by BigFrames in the current session."""
410+ return _ExecutionHistory ([job .__dict__ for job in self ._metrics .jobs ])
411+
374412 @property
375413 def _allows_ambiguity (self ) -> bool :
376414 return self ._allow_ambiguity
@@ -432,7 +470,8 @@ def read_gbq( # type: ignore[overload-overlap]
432470 col_order : Iterable [str ] = ...,
433471 dry_run : Literal [False ] = ...,
434472 allow_large_results : Optional [bool ] = ...,
435- ) -> dataframe .DataFrame : ...
473+ ) -> dataframe .DataFrame :
474+ ...
436475
437476 @overload
438477 def read_gbq (
@@ -448,7 +487,8 @@ def read_gbq(
448487 col_order : Iterable [str ] = ...,
449488 dry_run : Literal [True ] = ...,
450489 allow_large_results : Optional [bool ] = ...,
451- ) -> pandas .Series : ...
490+ ) -> pandas .Series :
491+ ...
452492
453493 def read_gbq (
454494 self ,
@@ -520,7 +560,8 @@ def _read_gbq_colab(
520560 * ,
521561 pyformat_args : Optional [Dict [str , Any ]] = None ,
522562 dry_run : Literal [False ] = ...,
523- ) -> dataframe .DataFrame : ...
563+ ) -> dataframe .DataFrame :
564+ ...
524565
525566 @overload
526567 def _read_gbq_colab (
@@ -529,7 +570,8 @@ def _read_gbq_colab(
529570 * ,
530571 pyformat_args : Optional [Dict [str , Any ]] = None ,
531572 dry_run : Literal [True ] = ...,
532- ) -> pandas .Series : ...
573+ ) -> pandas .Series :
574+ ...
533575
534576 @log_adapter .log_name_override ("read_gbq_colab" )
535577 def _read_gbq_colab (
@@ -590,7 +632,8 @@ def read_gbq_query( # type: ignore[overload-overlap]
590632 filters : third_party_pandas_gbq .FiltersType = ...,
591633 dry_run : Literal [False ] = ...,
592634 allow_large_results : Optional [bool ] = ...,
593- ) -> dataframe .DataFrame : ...
635+ ) -> dataframe .DataFrame :
636+ ...
594637
595638 @overload
596639 def read_gbq_query (
@@ -606,7 +649,8 @@ def read_gbq_query(
606649 filters : third_party_pandas_gbq .FiltersType = ...,
607650 dry_run : Literal [True ] = ...,
608651 allow_large_results : Optional [bool ] = ...,
609- ) -> pandas .Series : ...
652+ ) -> pandas .Series :
653+ ...
610654
611655 def read_gbq_query (
612656 self ,
@@ -753,7 +797,8 @@ def read_gbq_table( # type: ignore[overload-overlap]
753797 use_cache : bool = ...,
754798 col_order : Iterable [str ] = ...,
755799 dry_run : Literal [False ] = ...,
756- ) -> dataframe .DataFrame : ...
800+ ) -> dataframe .DataFrame :
801+ ...
757802
758803 @overload
759804 def read_gbq_table (
@@ -767,7 +812,8 @@ def read_gbq_table(
767812 use_cache : bool = ...,
768813 col_order : Iterable [str ] = ...,
769814 dry_run : Literal [True ] = ...,
770- ) -> pandas .Series : ...
815+ ) -> pandas .Series :
816+ ...
771817
772818 def read_gbq_table (
773819 self ,
@@ -918,23 +964,26 @@ def read_pandas(
918964 pandas_dataframe : pandas .Index ,
919965 * ,
920966 write_engine : constants .WriteEngineType = "default" ,
921- ) -> bigframes .core .indexes .Index : ...
967+ ) -> bigframes .core .indexes .Index :
968+ ...
922969
923970 @typing .overload
924971 def read_pandas (
925972 self ,
926973 pandas_dataframe : pandas .Series ,
927974 * ,
928975 write_engine : constants .WriteEngineType = "default" ,
929- ) -> bigframes .series .Series : ...
976+ ) -> bigframes .series .Series :
977+ ...
930978
931979 @typing .overload
932980 def read_pandas (
933981 self ,
934982 pandas_dataframe : pandas .DataFrame ,
935983 * ,
936984 write_engine : constants .WriteEngineType = "default" ,
937- ) -> dataframe .DataFrame : ...
985+ ) -> dataframe .DataFrame :
986+ ...
938987
939988 def read_pandas (
940989 self ,
0 commit comments