Skip to content

Commit 7db1330

Browse files
committed
feat: universal per-cell execution history filtering
1 parent eb548b6 commit 7db1330

7 files changed

Lines changed: 166 additions & 57 deletions

File tree

packages/bigframes/bigframes/core/events.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import datetime
2121
import threading
2222
import uuid
23-
from typing import Any, Callable, Literal, Set
23+
from typing import Any, Callable, Literal, Optional, Set
2424

2525
import google.cloud.bigquery._job_helpers
2626
import google.cloud.bigquery.job.query
@@ -129,6 +129,7 @@ class Event:
129129
class EventEnvelope:
130130
event: Event
131131
progress_bar: ProgressBarType = _DEFAULT
132+
cell_execution_count: Optional[int] = None
132133

133134

134135
@dataclasses.dataclass(frozen=True)

packages/bigframes/bigframes/session/_io/bigquery/__init__.py

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ def add_and_trim_labels(
263263
)
264264

265265

266-
def create_bq_event_callback(publisher):
266+
def create_bq_event_callback(publisher, cell_execution_count=None):
267267
event_map = {
268268
google.cloud.bigquery._job_helpers.QueryFinishedEvent: (
269269
bigframes.core.events.BigQueryFinishedEvent
@@ -286,7 +286,9 @@ def publish_bq_event(event):
286286
bf_event = bf_type.from_bqclient(event) # type: ignore
287287
break
288288
envelope = bigframes.core.events.EventEnvelope(
289-
event=bf_event, progress_bar=bigframes.core.events._DEFAULT
289+
event=bf_event,
290+
progress_bar=bigframes.core.events._DEFAULT,
291+
cell_execution_count=cell_execution_count,
290292
)
291293
publisher.publish(envelope)
292294

@@ -309,10 +311,21 @@ def start_query_with_job(
309311
job_retry: google.api_core.retry.Retry = (third_party_gcb_retry.DEFAULT_JOB_RETRY), # noqa: E501
310312
publisher: bigframes.core.events.Publisher,
311313
session=None,
314+
cell_execution_count: Optional[int] = None,
312315
) -> Tuple[google.cloud.bigquery.table.RowIterator, bigquery.QueryJob]:
313316
"""
314317
Starts query job and waits for results.
315318
"""
319+
if cell_execution_count is None:
320+
try:
321+
import IPython
322+
323+
ipy = IPython.get_ipython()
324+
if ipy is not None and hasattr(ipy, "execution_count"):
325+
cell_execution_count = ipy.execution_count
326+
except (ImportError, NameError):
327+
pass
328+
316329
# Note: Ensure no additional labels are added to job_config after this
317330
# point, as `add_and_trim_labels` ensures the label count does not
318331
# exceed MAX_LABELS_COUNT.
@@ -339,6 +352,7 @@ def start_query_with_job(
339352
sql=sql,
340353
publisher=publisher,
341354
metrics=metrics,
355+
cell_execution_count=cell_execution_count,
342356
)
343357
return results_iterator, query_job
344358

@@ -359,13 +373,24 @@ def start_query_job_optional(
359373
job_retry: google.api_core.retry.Retry = (third_party_gcb_retry.DEFAULT_JOB_RETRY), # noqa: E501
360374
publisher: bigframes.core.events.Publisher,
361375
session=None,
376+
cell_execution_count: Optional[int] = None,
362377
) -> google.cloud.bigquery.table.RowIterator:
363378
"""
364379
Run a bigquery query, with job optional.
365380
366381
See:
367382
https://docs.cloud.google.com/bigquery/docs/running-queries#optional-job-creation
368383
"""
384+
if cell_execution_count is None:
385+
try:
386+
import IPython
387+
388+
ipy = IPython.get_ipython()
389+
if ipy is not None and hasattr(ipy, "execution_count"):
390+
cell_execution_count = ipy.execution_count
391+
except (ImportError, NameError):
392+
pass
393+
369394
add_and_trim_labels(job_config, session=session)
370395
try:
371396
results_iterator = bq_client._query_and_wait_bigframes(
@@ -375,10 +400,14 @@ def start_query_job_optional(
375400
project=project,
376401
api_timeout=timeout,
377402
job_retry=job_retry,
378-
callback=create_bq_event_callback(publisher),
403+
callback=create_bq_event_callback(
404+
publisher, cell_execution_count=cell_execution_count
405+
),
379406
)
380407
if metrics is not None:
381-
metrics.count_job_stats(row_iterator=results_iterator)
408+
metrics.count_job_stats(
409+
row_iterator=results_iterator, cell_execution_count=cell_execution_count
410+
)
382411
return results_iterator
383412
except google.api_core.exceptions.Forbidden as ex:
384413
if "Drive credentials" in ex.message:
@@ -392,35 +421,45 @@ def _publish_events(
392421
total_rows: Optional[int],
393422
publisher: bigframes.core.events.Publisher,
394423
metrics: Optional[bigframes.session.metrics.ExecutionMetrics] = None,
424+
cell_execution_count: Optional[int] = None,
395425
):
396426
if not query_job.configuration.dry_run:
397427
publisher.publish(
398-
bigframes.core.events.BigQuerySentEvent(
399-
sql,
400-
billing_project=query_job.project,
401-
location=query_job.location,
402-
job_id=query_job.job_id,
403-
request_id=None,
428+
bigframes.core.events.EventEnvelope(
429+
event=bigframes.core.events.BigQuerySentEvent(
430+
sql,
431+
billing_project=query_job.project,
432+
location=query_job.location,
433+
job_id=query_job.job_id,
434+
request_id=None,
435+
),
436+
cell_execution_count=cell_execution_count,
404437
)
405438
)
406439
if not query_job.configuration.dry_run:
407440
publisher.publish(
408-
bigframes.core.events.BigQueryFinishedEvent(
409-
billing_project=query_job.project,
410-
location=query_job.location,
411-
job_id=query_job.job_id,
412-
destination=query_job.destination,
413-
total_rows=total_rows,
414-
total_bytes_processed=query_job.total_bytes_processed,
415-
slot_millis=query_job.slot_millis,
416-
created=query_job.created,
417-
started=query_job.started,
418-
ended=query_job.ended,
441+
bigframes.core.events.EventEnvelope(
442+
event=bigframes.core.events.BigQueryFinishedEvent(
443+
billing_project=query_job.project,
444+
location=query_job.location,
445+
query_id=query_job.query_id,
446+
job_id=query_job.job_id,
447+
destination=query_job.destination,
448+
total_rows=total_rows,
449+
total_bytes_processed=query_job.total_bytes_processed,
450+
slot_millis=query_job.slot_millis,
451+
created=query_job.created,
452+
started=query_job.started,
453+
ended=query_job.ended,
454+
),
455+
cell_execution_count=cell_execution_count,
419456
)
420457
)
421458

422459
if metrics is not None:
423-
metrics.count_job_stats(query_job=query_job)
460+
metrics.count_job_stats(
461+
query_job=query_job, cell_execution_count=cell_execution_count
462+
)
424463

425464

426465
def delete_tables_matching_session_id(

packages/bigframes/bigframes/session/bq_caching_executor.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,9 @@ async def _execute_async(
208208
execution_spec,
209209
)
210210
await self._publisher.publish_async(
211-
bigframes.core.events.ExecutionFinished(
212-
result=result,
211+
bigframes.core.events.EventEnvelope(
212+
event=bigframes.core.events.ExecutionFinished(result=result),
213+
cell_execution_count=execution_spec.cell_execution_count,
213214
)
214215
)
215216
return result
@@ -224,8 +225,11 @@ async def _try_execute_semi_executors(
224225
maybe_result = await exec.execute(plan, execution_spec)
225226
if maybe_result:
226227
await self._publisher.publish_async(
227-
bigframes.core.events.ExecutionFinished(
228-
result=maybe_result,
228+
bigframes.core.events.EventEnvelope(
229+
event=bigframes.core.events.ExecutionFinished(
230+
result=maybe_result,
231+
),
232+
cell_execution_count=execution_spec.cell_execution_count,
229233
)
230234
)
231235
return maybe_result

packages/bigframes/bigframes/session/direct_gbq_execution.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ async def execute(
109109
job_config=job_config,
110110
query_with_job=(not can_skip_job),
111111
session=plan.session,
112+
cell_execution_count=spec.cell_execution_count,
112113
)
113114
result_bq_data = None
114115
if query_job and query_job.destination:
@@ -158,6 +159,7 @@ def _run_execute_query(
158159
job_config: bq_job.QueryJobConfig,
159160
query_with_job: bool,
160161
session,
162+
cell_execution_count: Optional[int] = None,
161163
) -> Tuple[bq_table.RowIterator, Optional[bigquery.QueryJob]]:
162164
"""
163165
Starts BigQuery query job and waits for results.
@@ -171,6 +173,7 @@ def _run_execute_query(
171173
metrics=self._metrics,
172174
publisher=self._publisher,
173175
session=session,
176+
cell_execution_count=cell_execution_count,
174177
)
175178
else:
176179
return (
@@ -181,6 +184,7 @@ def _run_execute_query(
181184
metrics=self._metrics,
182185
publisher=self._publisher,
183186
session=session,
187+
cell_execution_count=cell_execution_count,
184188
),
185189
None,
186190
)

packages/bigframes/bigframes/session/execution_spec.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class ExecutionSpec:
6060

6161
# BigQuery specific options
6262
bigquery_config: Optional[BqComputeOptions] = None
63+
cell_execution_count: Optional[int] = None
6364

6465
def with_bq_labels(self, labels: Mapping[str, str]) -> ExecutionSpec:
6566
bq_config = self.bigquery_config or BqComputeOptions()
@@ -77,7 +78,23 @@ def with_compute_options(self, compute_options: ComputeOptions) -> ExecutionSpec
7778
new_bq_config = new_bq_config.push_labels(
7879
dict(self.bigquery_config.extra_query_labels)
7980
)
80-
return dataclasses.replace(self, bigquery_config=new_bq_config)
81+
82+
cell_execution_count = self.cell_execution_count
83+
if cell_execution_count is None:
84+
try:
85+
import IPython
86+
87+
ipy = IPython.get_ipython()
88+
if ipy is not None and hasattr(ipy, "execution_count"):
89+
cell_execution_count = ipy.execution_count
90+
except (ImportError, NameError):
91+
pass
92+
93+
return dataclasses.replace(
94+
self,
95+
bigquery_config=new_bq_config,
96+
cell_execution_count=cell_execution_count,
97+
)
8198

8299

83100
# Used internally by execution

0 commit comments

Comments
 (0)