Skip to content

Commit e15efb1

Browse files
authored
Merge c896b7b into 8135e40
2 parents 8135e40 + c896b7b commit e15efb1

File tree

4 files changed

+89
-0
lines changed

4 files changed

+89
-0
lines changed

deepnote_toolkit/runtime_initialization.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import psycopg2.extensions
77
import psycopg2.extras
88

9+
from deepnote_toolkit.runtime_patches import apply_runtime_patches
10+
911
from .dataframe_utils import add_formatters
1012
from .execute_post_start_hooks import execute_post_start_hooks
1113
from .logging import LoggerManager
@@ -24,6 +26,11 @@ def init_deepnote_runtime():
2426

2527
logger.debug("Initializing Deepnote runtime environment started.")
2628

29+
try:
30+
apply_runtime_patches()
31+
except Exception as e:
32+
logger.error("Failed to apply runtime patches with a error: %s", e)
33+
2734
# Register sparksql magic
2835
try:
2936
IPython.get_ipython().register_magics(SparkSql)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from typing import Any, Optional, Union
2+
3+
from deepnote_toolkit.logging import LoggerManager
4+
5+
logger = LoggerManager().get_logger()
6+
7+
8+
# TODO(BLU-5171): Temporary hack to allow cancelling BigQuery jobs on KeyboardInterrupt (e.g. when user cancels cell execution)
9+
# Can be removed once
10+
# 1. https://github.com/googleapis/python-bigquery/pull/2331 is merged and released
11+
# 2. Dependencies updated for the toolkit. We don't depend on google-cloud-bigquery directly, but it's transitive
12+
# dependency through sqlalchemy-bigquery
13+
def _monkeypatch_bigquery_wait_or_cancel():
14+
try:
15+
import google.cloud.bigquery._job_helpers as _job_helpers
16+
from google.cloud.bigquery import job, table
17+
18+
def _wait_or_cancel(
19+
job_obj: job.QueryJob,
20+
api_timeout: Optional[float],
21+
wait_timeout: Optional[Union[object, float]],
22+
retry: Optional[Any],
23+
page_size: Optional[int],
24+
max_results: Optional[int],
25+
) -> table.RowIterator:
26+
try:
27+
return job_obj.result(
28+
page_size=page_size,
29+
max_results=max_results,
30+
retry=retry,
31+
timeout=wait_timeout,
32+
)
33+
except (KeyboardInterrupt, Exception):
34+
try:
35+
job_obj.cancel(retry=retry, timeout=api_timeout)
36+
except (KeyboardInterrupt, Exception):
37+
pass
38+
raise
39+
40+
_job_helpers._wait_or_cancel = _wait_or_cancel
41+
logger.debug(
42+
"Successfully monkeypatched google.cloud.bigquery._job_helpers._wait_or_cancel"
43+
)
44+
except ImportError:
45+
logger.warning(
46+
"Could not monkeypatch BigQuery _wait_or_cancel: google.cloud.bigquery not available"
47+
)
48+
except Exception as e:
49+
logger.warning("Failed to monkeypatch BigQuery _wait_or_cancel: %s", repr(e))
50+
51+
52+
def apply_runtime_patches():
53+
_monkeypatch_bigquery_wait_or_cancel()

tests/unit/conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
import pytest
88

99

10+
@pytest.fixture(autouse=True, scope="session")
11+
def apply_patches() -> None:
12+
"""Apply runtime patches once before any tests run."""
13+
from deepnote_toolkit.runtime_patches import apply_runtime_patches
14+
15+
apply_runtime_patches()
16+
17+
1018
@pytest.fixture(autouse=True)
1119
def clean_runtime_state() -> Generator[None, None, None]:
1220
"""Automatically clean in-memory env state and config cache before and after each test."""

tests/unit/test_sql_execution_internal.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,27 @@
99
from deepnote_toolkit.sql import sql_execution as se
1010

1111

12+
def test_bigquery_wait_or_cancel_handles_keyboard_interrupt():
13+
import google.cloud.bigquery._job_helpers as _job_helpers
14+
15+
mock_job = mock.Mock()
16+
mock_job.result.side_effect = KeyboardInterrupt("User interrupted")
17+
mock_job.cancel = mock.Mock()
18+
19+
with pytest.raises(KeyboardInterrupt):
20+
# _wait_or_cancel should be monkeypatched by `_monkeypatch_bigquery_wait_or_cancel`
21+
_job_helpers._wait_or_cancel(
22+
job_obj=mock_job,
23+
api_timeout=30.0,
24+
wait_timeout=60.0,
25+
retry=None,
26+
page_size=None,
27+
max_results=None,
28+
)
29+
30+
mock_job.cancel.assert_called_once_with(retry=None, timeout=30.0)
31+
32+
1233
def test_build_params_for_bigquery_oauth_ok():
1334
with mock.patch(
1435
"deepnote_toolkit.sql.sql_execution.bigquery.Client"

0 commit comments

Comments
 (0)