Skip to content

Commit c01648d

Browse files
committed
Use BQ Storage API by default in cell magic
1 parent f3b7a4f commit c01648d

File tree

2 files changed

+67
-92
lines changed

2 files changed

+67
-92
lines changed

google/cloud/bigquery/magics.py

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,9 @@
3939
Project to use for running the query. Defaults to the context
4040
:attr:`~google.cloud.bigquery.magics.Context.project`.
4141
* ``--use_bqstorage_api`` (optional, line argument):
42-
Downloads the DataFrame using the BigQuery Storage API. To use this
43-
option, install the ``google-cloud-bigquery-storage`` and ``fastavro``
44-
packages, and `enable the BigQuery Storage API
45-
<https://console.cloud.google.com/apis/library/bigquerystorage.googleapis.com>`_.
42+
[Deprecated] Not used anymore, as BigQuery Storage API is used by default.
43+
* ``--use_rest_api`` (optional, line argument):
44+
Use the BigQuery REST API instead of the Storage API.
4645
* ``--use_legacy_sql`` (optional, line argument):
4746
Runs the query using Legacy SQL syntax. Defaults to Standard SQL if
4847
this argument not used.
@@ -142,6 +141,7 @@
142141
import functools
143142
import sys
144143
import time
144+
import warnings
145145
from concurrent import futures
146146

147147
try:
@@ -174,7 +174,6 @@ def __init__(self):
174174
self._credentials = None
175175
self._project = None
176176
self._connection = None
177-
self._use_bqstorage_api = None
178177
self._default_query_job_config = bigquery.QueryJobConfig()
179178

180179
@property
@@ -237,21 +236,6 @@ def project(self):
237236
def project(self, value):
238237
self._project = value
239238

240-
@property
241-
def use_bqstorage_api(self):
242-
"""bool: [Beta] Set to True to use the BigQuery Storage API to
243-
download query results
244-
245-
To use this option, install the ``google-cloud-bigquery-storage`` and
246-
``fastavro`` packages, and `enable the BigQuery Storage API
247-
<https://console.cloud.google.com/apis/library/bigquerystorage.googleapis.com>`_.
248-
"""
249-
return self._use_bqstorage_api
250-
251-
@use_bqstorage_api.setter
252-
def use_bqstorage_api(self, value):
253-
self._use_bqstorage_api = value
254-
255239
@property
256240
def default_query_job_config(self):
257241
"""google.cloud.bigquery.job.QueryJobConfig: Default job
@@ -426,11 +410,21 @@ def _create_dataset_if_necessary(client, dataset_id):
426410
@magic_arguments.argument(
427411
"--use_bqstorage_api",
428412
action="store_true",
413+
default=None,
414+
help=(
415+
"[Deprecated] The BigQuery Storage API is already used by default to "
416+
"download large query results, and this option has no effect. "
417+
"If you want to switch to the classic REST API instead, use the "
418+
"--use_rest_api option."
419+
),
420+
)
421+
@magic_arguments.argument(
422+
"--use_rest_api",
423+
action="store_true",
429424
default=False,
430425
help=(
431-
"[Beta] Use the BigQuery Storage API to download large query results. "
432-
"To use this option, install the google-cloud-bigquery-storage and "
433-
"fastavro packages, and enable the BigQuery Storage API."
426+
"Use the classic REST API instead of the BigQuery Storage API to "
427+
"download query results."
434428
),
435429
)
436430
@magic_arguments.argument(
@@ -473,6 +467,14 @@ def _cell_magic(line, query):
473467
"""
474468
args = magic_arguments.parse_argstring(_cell_magic, line)
475469

470+
if args.use_bqstorage_api is not None:
471+
warnings.warn(
472+
"Deprecated option --use_bqstorage_api, the BigQuery "
473+
"Storage API is already used by default.",
474+
category=DeprecationWarning,
475+
)
476+
use_bqstorage_api = not args.use_rest_api
477+
476478
params = []
477479
if args.params is not None:
478480
try:
@@ -494,9 +496,7 @@ def _cell_magic(line, query):
494496
)
495497
if context._connection:
496498
client._connection = context._connection
497-
bqstorage_client = _make_bqstorage_client(
498-
args.use_bqstorage_api or context.use_bqstorage_api, context.credentials
499-
)
499+
bqstorage_client = _make_bqstorage_client(use_bqstorage_api, context.credentials)
500500

501501
close_transports = functools.partial(_close_transports, client, bqstorage_client)
502502

@@ -598,8 +598,10 @@ def _make_bqstorage_client(use_bqstorage_api, credentials):
598598
from google.cloud import bigquery_storage_v1beta1
599599
except ImportError as err:
600600
customized_error = ImportError(
601-
"Install the google-cloud-bigquery-storage and pyarrow packages "
602-
"to use the BigQuery Storage API."
601+
"The default BigQuery Storage API client cannot be used, install "
602+
"the missing google-cloud-bigquery-storage and pyarrow packages "
603+
"to use it. Alternatively, use the classic REST API by specifying "
604+
"the --use_rest_api magic option."
603605
)
604606
six.raise_from(customized_error, err)
605607

tests/unit/test_magics.py

Lines changed: 37 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import copy
1616
import re
1717
from concurrent import futures
18+
import warnings
1819

1920
import mock
2021
import pytest
@@ -386,13 +387,33 @@ def test_extension_load():
386387

387388
@pytest.mark.usefixtures("ipython_interactive")
388389
@pytest.mark.skipif(pandas is None, reason="Requires `pandas`")
389-
def test_bigquery_magic_without_optional_arguments(missing_bq_storage):
390+
@pytest.mark.skipif(
391+
bigquery_storage_v1beta1 is None, reason="Requires `google-cloud-bigquery-storage`"
392+
)
393+
def test_bigquery_magic_without_optional_arguments(monkeypatch):
390394
ip = IPython.get_ipython()
391395
ip.extension_manager.load_extension("google.cloud.bigquery")
392-
magics.context.credentials = mock.create_autospec(
396+
mock_credentials = mock.create_autospec(
393397
google.auth.credentials.Credentials, instance=True
394398
)
395399

400+
# Set up the context with monkeypatch so that it's reset for subsequent
401+
# tests.
402+
monkeypatch.setattr(magics.context, "credentials", mock_credentials)
403+
404+
# Mock out the BigQuery Storage API.
405+
bqstorage_mock = mock.create_autospec(
406+
bigquery_storage_v1beta1.BigQueryStorageClient
407+
)
408+
bqstorage_instance_mock = mock.create_autospec(
409+
bigquery_storage_v1beta1.BigQueryStorageClient, instance=True
410+
)
411+
bqstorage_instance_mock.transport = mock.Mock()
412+
bqstorage_mock.return_value = bqstorage_instance_mock
413+
bqstorage_client_patch = mock.patch(
414+
"google.cloud.bigquery_storage_v1beta1.BigQueryStorageClient", bqstorage_mock
415+
)
416+
396417
sql = "SELECT 17 AS num"
397418
result = pandas.DataFrame([17], columns=["num"])
398419
run_query_patch = mock.patch(
@@ -403,11 +424,11 @@ def test_bigquery_magic_without_optional_arguments(missing_bq_storage):
403424
)
404425
query_job_mock.to_dataframe.return_value = result
405426

406-
# Shouldn't fail when BigQuery Storage client isn't installed.
407-
with run_query_patch as run_query_mock, missing_bq_storage:
427+
with run_query_patch as run_query_mock, bqstorage_client_patch:
408428
run_query_mock.return_value = query_job_mock
409429
return_value = ip.run_cell_magic("bigquery", "", sql)
410430

431+
assert bqstorage_mock.called # BQ storage client was used
411432
assert isinstance(return_value, pandas.DataFrame)
412433
assert len(return_value) == len(result) # verify row count
413434
assert list(return_value) == list(result) # verify column names
@@ -542,7 +563,6 @@ def test_bigquery_magic_with_bqstorage_from_argument(monkeypatch):
542563
# Set up the context with monkeypatch so that it's reset for subsequent
543564
# tests.
544565
monkeypatch.setattr(magics.context, "credentials", mock_credentials)
545-
monkeypatch.setattr(magics.context, "use_bqstorage_api", False)
546566

547567
# Mock out the BigQuery Storage API.
548568
bqstorage_mock = mock.create_autospec(
@@ -566,67 +586,20 @@ def test_bigquery_magic_with_bqstorage_from_argument(monkeypatch):
566586
google.cloud.bigquery.job.QueryJob, instance=True
567587
)
568588
query_job_mock.to_dataframe.return_value = result
569-
with run_query_patch as run_query_mock, bqstorage_client_patch:
589+
with run_query_patch as run_query_mock, bqstorage_client_patch, warnings.catch_warnings(
590+
record=True
591+
) as warned:
570592
run_query_mock.return_value = query_job_mock
571593

572594
return_value = ip.run_cell_magic("bigquery", "--use_bqstorage_api", sql)
573595

574-
assert len(bqstorage_mock.call_args_list) == 1
575-
kwargs = bqstorage_mock.call_args_list[0].kwargs
576-
assert kwargs.get("credentials") is mock_credentials
577-
client_info = kwargs.get("client_info")
578-
assert client_info is not None
579-
assert client_info.user_agent == "ipython-" + IPython.__version__
580-
581-
query_job_mock.to_dataframe.assert_called_once_with(
582-
bqstorage_client=bqstorage_instance_mock
583-
)
584-
585-
assert isinstance(return_value, pandas.DataFrame)
586-
587-
588-
@pytest.mark.usefixtures("ipython_interactive")
589-
@pytest.mark.skipif(
590-
bigquery_storage_v1beta1 is None, reason="Requires `google-cloud-bigquery-storage`"
591-
)
592-
def test_bigquery_magic_with_bqstorage_from_context(monkeypatch):
593-
ip = IPython.get_ipython()
594-
ip.extension_manager.load_extension("google.cloud.bigquery")
595-
mock_credentials = mock.create_autospec(
596-
google.auth.credentials.Credentials, instance=True
597-
)
598-
599-
# Set up the context with monkeypatch so that it's reset for subsequent
600-
# tests.
601-
monkeypatch.setattr(magics.context, "credentials", mock_credentials)
602-
monkeypatch.setattr(magics.context, "use_bqstorage_api", True)
596+
# Deprecation warning should have been issued.
597+
def warning_match(warning):
598+
message = str(warning).lower()
599+
return "deprecated" in message and "use_bqstorage_api" in message
603600

604-
# Mock out the BigQuery Storage API.
605-
bqstorage_mock = mock.create_autospec(
606-
bigquery_storage_v1beta1.BigQueryStorageClient
607-
)
608-
bqstorage_instance_mock = mock.create_autospec(
609-
bigquery_storage_v1beta1.BigQueryStorageClient, instance=True
610-
)
611-
bqstorage_instance_mock.transport = mock.Mock()
612-
bqstorage_mock.return_value = bqstorage_instance_mock
613-
bqstorage_client_patch = mock.patch(
614-
"google.cloud.bigquery_storage_v1beta1.BigQueryStorageClient", bqstorage_mock
615-
)
616-
617-
sql = "SELECT 17 AS num"
618-
result = pandas.DataFrame([17], columns=["num"])
619-
run_query_patch = mock.patch(
620-
"google.cloud.bigquery.magics._run_query", autospec=True
621-
)
622-
query_job_mock = mock.create_autospec(
623-
google.cloud.bigquery.job.QueryJob, instance=True
624-
)
625-
query_job_mock.to_dataframe.return_value = result
626-
with run_query_patch as run_query_mock, bqstorage_client_patch:
627-
run_query_mock.return_value = query_job_mock
628-
629-
return_value = ip.run_cell_magic("bigquery", "", sql)
601+
expected_warnings = list(filter(warning_match, warned))
602+
assert len(expected_warnings) == 1
630603

631604
assert len(bqstorage_mock.call_args_list) == 1
632605
kwargs = bqstorage_mock.call_args_list[0].kwargs
@@ -646,7 +619,7 @@ def test_bigquery_magic_with_bqstorage_from_context(monkeypatch):
646619
@pytest.mark.skipif(
647620
bigquery_storage_v1beta1 is None, reason="Requires `google-cloud-bigquery-storage`"
648621
)
649-
def test_bigquery_magic_without_bqstorage(monkeypatch):
622+
def test_bigquery_magic_with_rest_client_requested(monkeypatch):
650623
ip = IPython.get_ipython()
651624
ip.extension_manager.load_extension("google.cloud.bigquery")
652625
mock_credentials = mock.create_autospec(
@@ -677,7 +650,7 @@ def test_bigquery_magic_without_bqstorage(monkeypatch):
677650
with run_query_patch as run_query_mock, bqstorage_client_patch:
678651
run_query_mock.return_value = query_job_mock
679652

680-
return_value = ip.run_cell_magic("bigquery", "", sql)
653+
return_value = ip.run_cell_magic("bigquery", "--use_rest_api", sql)
681654

682655
bqstorage_mock.assert_not_called()
683656
query_job_mock.to_dataframe.assert_called_once_with(bqstorage_client=None)
@@ -895,7 +868,7 @@ def test_bigquery_magic_w_table_id_and_bqstorage_client():
895868
with default_patch, client_patch as client_mock, bqstorage_client_patch:
896869
client_mock().list_rows.return_value = row_iterator_mock
897870

898-
ip.run_cell_magic("bigquery", "--use_bqstorage_api --max_results=5", table_id)
871+
ip.run_cell_magic("bigquery", "--max_results=5", table_id)
899872
row_iterator_mock.to_dataframe.assert_called_once_with(
900873
bqstorage_client=bqstorage_instance_mock
901874
)

0 commit comments

Comments
 (0)