Skip to content

Commit 7655ada

Browse files
fix: usability improvement to the graph visualizer (#215)
1) Disallow backticks in the graph name, as the code doesn't handle them properly 2) Add try/catch so a failed information_schema query only disables schema view, not the entire visualizer 3) Remove the default table view when the graph visualizer is used. Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/python-bigquery-magics/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [ ] Appropriate docs were updated (if necessary) Fixes #<issue_number_goes_here> 🦕
1 parent 215e53a commit 7655ada

File tree

2 files changed

+41
-16
lines changed

2 files changed

+41
-16
lines changed

bigquery_magics/bigquery.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,10 @@ def _get_graph_name(query_text: str):
648648
"""
649649
match = re.match(r"\s*GRAPH\s+(\S+)\.(\S+)", query_text, re.IGNORECASE)
650650
if match:
651-
return (match.group(1), match.group(2))
651+
(dataset_id, graph_id) = (match.group(1)), match.group(2)
652+
if "`" in dataset_id or "`" in graph_id:
653+
return None # Backticks in graph name not support for schema view
654+
return (dataset_id, graph_id)
652655
return None
653656

654657

@@ -668,10 +671,15 @@ def _get_graph_schema(
668671
job_config = bigquery.QueryJobConfig(
669672
query_parameters=[bigquery.ScalarQueryParameter("graph_id", "STRING", graph_id)]
670673
)
671-
info_schema_results = bq_client.query(
672-
info_schema_query, job_config=job_config
673-
).to_dataframe()
674-
674+
job_config.use_legacy_sql = False
675+
try:
676+
info_schema_results = bq_client.query(
677+
info_schema_query, job_config=job_config
678+
).to_dataframe()
679+
except Exception:
680+
# If the INFORMATION_SCHEMA query fails for some reason, disable only schema
681+
# view, not the entire visualizer.
682+
return None
675683
if info_schema_results.shape == (1, 1):
676684
return graph_server._convert_schema(info_schema_results.iloc[0, 0])
677685
return None
@@ -733,7 +741,7 @@ def _add_graph_widget(
733741
"<big><b>Error:</b> The query result is too large for graph visualization.</big>"
734742
)
735743
)
736-
return
744+
return False
737745

738746
schema = _get_graph_schema(bq_client, query_text, query_job)
739747

@@ -764,6 +772,7 @@ def _add_graph_widget(
764772
'"bigquery.graph_visualization.NodeExpansion"',
765773
)
766774
IPython.display.display(IPython.core.display.HTML(html_content))
775+
return True
767776

768777

769778
def _is_valid_json(s: str):
@@ -870,7 +879,12 @@ def _make_bq_query(
870879
result = result.to_dataframe(**dataframe_kwargs)
871880

872881
if args.graph and _supports_graph_widget(result):
873-
_add_graph_widget(bq_client, result, query, query_job, args)
882+
if _add_graph_widget(bq_client, result, query, query_job, args):
883+
# Invoke _handle_result() in case the result is saved to a variable,
884+
# but return None to suppress the default table view, which is redundant
885+
# with the table view in the graph visualizer.
886+
_handle_result(result, args)
887+
return None
874888
return _handle_result(result, args)
875889

876890

tests/unit/bigquery/test_bigquery.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,23 @@ def test__run_query_dry_run_without_errors_is_silent():
241241
assert len(captured.stdout) == 0
242242

243243

244+
def test__get_graph_name():
245+
assert magics._get_graph_name("GRAPH foo.bar") == ("foo", "bar")
246+
assert magics._get_graph_name("GRAPH `foo.bar`") is None
247+
assert magics._get_graph_name("GRAPH `foo`.bar") is None
248+
assert magics._get_graph_name("SELECT 1") is None
249+
250+
251+
def test__get_graph_schema_exception():
252+
bq_client = mock.create_autospec(bigquery.Client, instance=True)
253+
bq_client.query.side_effect = Exception("error")
254+
query_text = "GRAPH foo.bar"
255+
query_job = mock.Mock()
256+
query_job.configuration.destination.project = "my-project"
257+
258+
assert magics._get_graph_schema(bq_client, query_text, query_job) is None
259+
260+
244261
@pytest.mark.skipif(
245262
bigquery_storage is None, reason="Requires `google-cloud-bigquery-storage`"
246263
)
@@ -614,9 +631,7 @@ def test_bigquery_graph_json_json_result(monkeypatch):
614631
display_mock.assert_called()
615632

616633
assert bqstorage_mock.called # BQ storage client was used
617-
assert isinstance(return_value, pandas.DataFrame)
618-
assert len(return_value) == len(result) # verify row count
619-
assert list(return_value) == list(result) # verify column names
634+
assert return_value is None
620635

621636

622637
@pytest.mark.skipif(
@@ -735,9 +750,7 @@ def test_bigquery_graph_json_result(monkeypatch):
735750
assert '\\"location\\": null' in html_content
736751

737752
assert bqstorage_mock.called # BQ storage client was used
738-
assert isinstance(return_value, pandas.DataFrame)
739-
assert len(return_value) == len(result) # verify row count
740-
assert list(return_value) == list(result) # verify column names
753+
assert return_value is None
741754

742755

743756
@pytest.mark.skipif(
@@ -1015,9 +1028,7 @@ def test_bigquery_graph_colab(monkeypatch):
10151028
assert sys.modules["google.colab"].output.register_callback.called
10161029

10171030
assert bqstorage_mock.called # BQ storage client was used
1018-
assert isinstance(return_value, pandas.DataFrame)
1019-
assert len(return_value) == len(result) # verify row count
1020-
assert list(return_value) == list(result) # verify column names
1031+
assert return_value is None
10211032

10221033

10231034
@pytest.mark.skipif(

0 commit comments

Comments
 (0)