Skip to content

Commit ee0f70a

Browse files
shubha-rajanplamut
authored andcommitted
BigQuery: Add ability to pass in a table ID instead of a query to the %%bigquery magic. (googleapis#9170)
* cell magic accepts table_ids instead of queries added default patch to unit tests * simplified error handling to return from exception * added comment and updated to use strip instead of rstrip * blacken/lint * reformatted return statement * removed trailing whitespace
1 parent 9023477 commit ee0f70a

File tree

2 files changed

+138
-11
lines changed

2 files changed

+138
-11
lines changed

bigquery/google/cloud/bigquery/magics.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129

130130
from __future__ import print_function
131131

132+
import re
132133
import ast
133134
import sys
134135
import time
@@ -266,6 +267,15 @@ def default_query_job_config(self, value):
266267
context = Context()
267268

268269

270+
def _print_error(error, destination_var=None):
271+
if destination_var:
272+
print(
273+
"Could not save output to variable '{}'.".format(destination_var),
274+
file=sys.stderr,
275+
)
276+
print("\nERROR:\n", error, file=sys.stderr)
277+
278+
269279
def _run_query(client, query, job_config=None):
270280
"""Runs a query while printing status updates
271281
@@ -434,6 +444,24 @@ def _cell_magic(line, query):
434444
else:
435445
max_results = None
436446

447+
query = query.strip()
448+
449+
# Any query that does not contain whitespace (aside from leading and trailing whitespace)
450+
# is assumed to be a table id
451+
if not re.search(r"\s", query):
452+
try:
453+
rows = client.list_rows(query, max_results=max_results)
454+
except Exception as ex:
455+
_print_error(str(ex), args.destination_var)
456+
return
457+
458+
result = rows.to_dataframe(bqstorage_client=bqstorage_client)
459+
if args.destination_var:
460+
IPython.get_ipython().push({args.destination_var: result})
461+
return
462+
else:
463+
return result
464+
437465
job_config = bigquery.job.QueryJobConfig()
438466
job_config.query_parameters = params
439467
job_config.use_legacy_sql = args.use_legacy_sql
@@ -445,24 +473,15 @@ def _cell_magic(line, query):
445473
value = int(args.maximum_bytes_billed)
446474
job_config.maximum_bytes_billed = value
447475

448-
error = None
449476
try:
450477
query_job = _run_query(client, query, job_config=job_config)
451478
except Exception as ex:
452-
error = str(ex)
479+
_print_error(str(ex), args.destination_var)
480+
return
453481

454482
if not args.verbose:
455483
display.clear_output()
456484

457-
if error:
458-
if args.destination_var:
459-
print(
460-
"Could not save output to variable '{}'.".format(args.destination_var),
461-
file=sys.stderr,
462-
)
463-
print("\nERROR:\n", error, file=sys.stderr)
464-
return
465-
466485
if args.dry_run and args.destination_var:
467486
IPython.get_ipython().push({args.destination_var: query_job})
468487
return

bigquery/tests/unit/test_magics.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,114 @@ def test_bigquery_magic_w_max_results_valid_calls_queryjob_result():
696696
query_job_mock.result.assert_called_with(max_results=5)
697697

698698

699+
def test_bigquery_magic_w_table_id_invalid():
700+
ip = IPython.get_ipython()
701+
ip.extension_manager.load_extension("google.cloud.bigquery")
702+
magics.context._project = None
703+
704+
credentials_mock = mock.create_autospec(
705+
google.auth.credentials.Credentials, instance=True
706+
)
707+
default_patch = mock.patch(
708+
"google.auth.default", return_value=(credentials_mock, "general-project")
709+
)
710+
711+
list_rows_patch = mock.patch(
712+
"google.cloud.bigquery.magics.bigquery.Client.list_rows",
713+
autospec=True,
714+
side_effect=exceptions.BadRequest("Not a valid table ID"),
715+
)
716+
717+
table_id = "not-a-real-table"
718+
719+
with list_rows_patch, default_patch, io.capture_output() as captured_io:
720+
ip.run_cell_magic("bigquery", "df", table_id)
721+
722+
output = captured_io.stderr
723+
assert "Could not save output to variable" in output
724+
assert "400 Not a valid table ID" in output
725+
assert "Traceback (most recent call last)" not in output
726+
727+
728+
@pytest.mark.usefixtures("ipython_interactive")
729+
def test_bigquery_magic_w_table_id_and_destination_var():
730+
ip = IPython.get_ipython()
731+
ip.extension_manager.load_extension("google.cloud.bigquery")
732+
magics.context._project = None
733+
734+
credentials_mock = mock.create_autospec(
735+
google.auth.credentials.Credentials, instance=True
736+
)
737+
default_patch = mock.patch(
738+
"google.auth.default", return_value=(credentials_mock, "general-project")
739+
)
740+
741+
row_iterator_mock = mock.create_autospec(
742+
google.cloud.bigquery.table.RowIterator, instance=True
743+
)
744+
745+
client_patch = mock.patch(
746+
"google.cloud.bigquery.magics.bigquery.Client", autospec=True
747+
)
748+
749+
table_id = "bigquery-public-data.samples.shakespeare"
750+
result = pandas.DataFrame([17], columns=["num"])
751+
752+
with client_patch as client_mock, default_patch:
753+
client_mock().list_rows.return_value = row_iterator_mock
754+
row_iterator_mock.to_dataframe.return_value = result
755+
756+
ip.run_cell_magic("bigquery", "df", table_id)
757+
758+
assert "df" in ip.user_ns
759+
df = ip.user_ns["df"]
760+
761+
assert isinstance(df, pandas.DataFrame)
762+
763+
764+
@pytest.mark.usefixtures("ipython_interactive")
765+
def test_bigquery_magic_w_table_id_and_bqstorage_client():
766+
ip = IPython.get_ipython()
767+
ip.extension_manager.load_extension("google.cloud.bigquery")
768+
magics.context._project = None
769+
770+
credentials_mock = mock.create_autospec(
771+
google.auth.credentials.Credentials, instance=True
772+
)
773+
default_patch = mock.patch(
774+
"google.auth.default", return_value=(credentials_mock, "general-project")
775+
)
776+
777+
row_iterator_mock = mock.create_autospec(
778+
google.cloud.bigquery.table.RowIterator, instance=True
779+
)
780+
781+
client_patch = mock.patch(
782+
"google.cloud.bigquery.magics.bigquery.Client", autospec=True
783+
)
784+
785+
bqstorage_mock = mock.create_autospec(
786+
bigquery_storage_v1beta1.BigQueryStorageClient
787+
)
788+
bqstorage_instance_mock = mock.create_autospec(
789+
bigquery_storage_v1beta1.BigQueryStorageClient, instance=True
790+
)
791+
bqstorage_mock.return_value = bqstorage_instance_mock
792+
bqstorage_client_patch = mock.patch(
793+
"google.cloud.bigquery_storage_v1beta1.BigQueryStorageClient", bqstorage_mock
794+
)
795+
796+
table_id = "bigquery-public-data.samples.shakespeare"
797+
798+
with default_patch, client_patch as client_mock, bqstorage_client_patch:
799+
client_mock().list_rows.return_value = row_iterator_mock
800+
801+
ip.run_cell_magic("bigquery", "--use_bqstorage_api --max_results=5", table_id)
802+
row_iterator_mock.to_dataframe.assert_called_once_with(
803+
bqstorage_client=bqstorage_instance_mock
804+
)
805+
806+
699807
@pytest.mark.usefixtures("ipython_interactive")
700808
def test_bigquery_magic_dryrun_option_sets_job_config():
701809
ip = IPython.get_ipython()

0 commit comments

Comments
 (0)