Skip to content

Commit 70e7a09

Browse files
feat: update minimum google-cloud-bigquery (#16174)
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/google-cloud-python/issues) 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 890c687 commit 70e7a09

File tree

4 files changed

+136
-48
lines changed

4 files changed

+136
-48
lines changed

packages/bigquery-magics/bigquery_magics/bigquery.py

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -646,41 +646,26 @@ def _colab_node_expansion_callback(request: dict, params_str: str):
646646
MAX_GRAPH_VISUALIZATION_QUERY_RESULT_SIZE = 100_000
647647

648648

649-
def _get_graph_name(query_text: str):
650-
"""Returns the name of the graph queried.
651-
652-
Supports GRAPH only, not GRAPH_TABLE.
653-
654-
Args:
655-
query_text: The SQL query text.
656-
657-
Returns:
658-
A (dataset_id, graph_id) tuple, or None if the graph name cannot be determined.
659-
"""
660-
match = re.match(r"\s*GRAPH\s+(\S+)\.(\S+)", query_text, re.IGNORECASE)
661-
if match:
662-
(dataset_id, graph_id) = (match.group(1)), match.group(2)
663-
if "`" in dataset_id or "`" in graph_id:
664-
return None # Backticks in graph name not support for schema view
665-
return (dataset_id, graph_id)
666-
return None
667-
668-
669649
def _get_graph_schema(
670650
bq_client: bigquery.client.Client, query_text: str, query_job: bigquery.job.QueryJob
671651
):
672-
graph_name_result = _get_graph_name(query_text)
673-
if graph_name_result is None:
652+
property_graphs = query_job.referenced_property_graphs
653+
if len(property_graphs) != 1:
674654
return None
675-
dataset_id, graph_id = graph_name_result
655+
656+
graph_ref = property_graphs[0]
676657

677658
info_schema_query = f"""
678659
select PROPERTY_GRAPH_METADATA_JSON
679-
FROM `{query_job.configuration.destination.project}.{dataset_id}`.INFORMATION_SCHEMA.PROPERTY_GRAPHS
660+
FROM `{graph_ref.project}.{graph_ref.dataset_id}`.INFORMATION_SCHEMA.PROPERTY_GRAPHS
680661
WHERE PROPERTY_GRAPH_NAME = @graph_id
681662
"""
682663
job_config = bigquery.QueryJobConfig(
683-
query_parameters=[bigquery.ScalarQueryParameter("graph_id", "STRING", graph_id)]
664+
query_parameters=[
665+
bigquery.ScalarQueryParameter(
666+
"graph_id", "STRING", graph_ref.property_graph_id
667+
)
668+
]
684669
)
685670
job_config.use_legacy_sql = False
686671
try:

packages/bigquery-magics/setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@
2929
release_status = "Development Status :: 4 - Beta"
3030
dependencies = [
3131
"db-dtypes>=1.1.1,<2.0.0",
32-
"google-cloud-bigquery >= 3.13.0, <4.0.0",
32+
"google-cloud-bigquery >= 3.41.0, <4.0.0",
3333
"ipywidgets>=7.7.1",
3434
"ipython>=7.23.1",
3535
"ipykernel>=5.5.6",
36-
"packaging >= 20.0.0",
36+
"packaging >= 24.2.0",
3737
"pandas>=1.5.3",
3838
"pyarrow >= 12.0.0",
3939
"pydata-google-auth >=1.5.0",
@@ -45,7 +45,7 @@
4545
# moved back to optional due to bloat. See
4646
# https://github.com/googleapis/python-bigquery/issues/1196 for more background.
4747
"bqstorage": [
48-
"google-cloud-bigquery-storage >= 2.6.0, <3.0.0",
48+
"google-cloud-bigquery-storage >= 2.25.0, <3.0.0",
4949
# Due to an issue in pip's dependency resolver, the `grpc` extra is not
5050
# installed, even though `google-cloud-bigquery-storage` specifies it
5151
# as `google-api-core[grpc]`. We thus need to explicitly specify it here.

packages/bigquery-magics/testing/constraints-3.10.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
# Then this file should have foo==1.14.0
88
db-dtypes==1.1.1
99
geopandas==1.0.1
10-
google-cloud-bigquery==3.13.0
11-
google-cloud-bigquery-storage==2.6.0
10+
google-cloud-bigquery==3.41.0
11+
google-cloud-bigquery-storage==2.25.0
1212
ipywidgets==7.7.1
1313
ipython==7.23.1
1414
ipykernel==5.5.6
1515
numpy==1.26.4
16-
packaging==20.0.0
16+
packaging==24.2.0
1717
pandas==1.5.3
1818
pyarrow==12.0.0
1919
pydata-google-auth==1.5.0

packages/bigquery-magics/tests/unit/bigquery/test_bigquery.py

Lines changed: 120 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -241,23 +241,78 @@ 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-
251244
def test__get_graph_schema_exception():
252245
bq_client = mock.create_autospec(bigquery.Client, instance=True)
253246
bq_client.query.side_effect = Exception("error")
254247
query_text = "GRAPH foo.bar"
255248
query_job = mock.Mock()
256-
query_job.configuration.destination.project = "my-project"
249+
250+
graph_ref = mock.Mock()
251+
graph_ref.project = "my-project"
252+
graph_ref.dataset_id = "dataset"
253+
graph_ref.property_graph_id = "graph"
254+
query_job.referenced_property_graphs = [graph_ref]
257255

258256
assert magics._get_graph_schema(bq_client, query_text, query_job) is None
259257

260258

259+
def test__get_graph_schema_zero_references():
260+
bq_client = mock.create_autospec(bigquery.Client, instance=True)
261+
query_job = mock.Mock()
262+
query_job.referenced_property_graphs = []
263+
264+
assert magics._get_graph_schema(bq_client, "SELECT 1", query_job) is None
265+
266+
267+
def test__get_graph_schema_two_references():
268+
bq_client = mock.create_autospec(bigquery.Client, instance=True)
269+
query_job = mock.Mock()
270+
271+
ref1 = mock.Mock()
272+
ref2 = mock.Mock()
273+
query_job.referenced_property_graphs = [ref1, ref2]
274+
275+
assert magics._get_graph_schema(bq_client, "SELECT 1", query_job) is None
276+
277+
278+
def test__get_graph_schema_success():
279+
bq_client = mock.create_autospec(bigquery.Client, instance=True)
280+
query_job = mock.Mock()
281+
282+
graph_ref = mock.Mock()
283+
graph_ref.project = "my-project"
284+
graph_ref.dataset_id = "dataset"
285+
graph_ref.property_graph_id = "graph"
286+
query_job.referenced_property_graphs = [graph_ref]
287+
288+
mock_df = mock.MagicMock()
289+
mock_df.shape = (1, 1)
290+
mock_df.iloc.__getitem__.return_value = "schema_json"
291+
bq_client.query.return_value.to_dataframe.return_value = mock_df
292+
293+
with mock.patch(
294+
"bigquery_magics.bigquery.graph_server._convert_schema"
295+
) as convert_mock:
296+
convert_mock.return_value = {"nodes": [], "edges": []}
297+
298+
result = magics._get_graph_schema(bq_client, "SELECT 1", query_job)
299+
300+
assert result == {"nodes": [], "edges": []}
301+
convert_mock.assert_called_once_with("schema_json")
302+
303+
called_query = bq_client.query.call_args[0][0]
304+
assert (
305+
"FROM `my-project.dataset`.INFORMATION_SCHEMA.PROPERTY_GRAPHS"
306+
in called_query
307+
)
308+
309+
called_config = bq_client.query.call_args[1]["job_config"]
310+
called_params = called_config.query_parameters
311+
assert len(called_params) == 1
312+
assert called_params[0].name == "graph_id"
313+
assert called_params[0].value == "graph"
314+
315+
261316
@pytest.mark.skipif(
262317
bigquery_storage is None, reason="Requires `google-cloud-bigquery-storage`"
263318
)
@@ -417,6 +472,12 @@ def test_bigquery_magic_without_optional_arguments(monkeypatch):
417472
reason="Requires `spanner-graph-notebook` to be missing and `google-cloud-bigquery-storage` to be present",
418473
)
419474
def test_bigquery_graph_spanner_graph_notebook_missing(monkeypatch):
475+
"""If `spanner-graph-notebook` is not installed, the graph visualizer
476+
widget cannot be displayed.
477+
"""
478+
monkeypatch.setattr(
479+
"bigquery_magics.bigquery._get_graph_schema", lambda *args: None
480+
)
420481
globalipapp.start_ipython()
421482
ip = globalipapp.get_ipython()
422483
ip.extension_manager.load_extension("bigquery_magics")
@@ -468,6 +529,10 @@ def test_bigquery_graph_spanner_graph_notebook_missing(monkeypatch):
468529
reason="Requires `spanner-graph-notebook` and `google-cloud-bigquery-storage`",
469530
)
470531
def test_bigquery_graph_int_result(monkeypatch):
532+
"""Graph visualization of integer scalars is supported."""
533+
monkeypatch.setattr(
534+
"bigquery_magics.bigquery._get_graph_schema", lambda *args: None
535+
)
471536
globalipapp.start_ipython()
472537
ip = globalipapp.get_ipython()
473538
ip.extension_manager.load_extension("bigquery_magics")
@@ -519,6 +584,10 @@ def test_bigquery_graph_int_result(monkeypatch):
519584
reason="Requires `spanner-graph-notebook` and `google-cloud-bigquery-storage`",
520585
)
521586
def test_bigquery_graph_str_result(monkeypatch):
587+
"""Graph visualization of string scalars is supported."""
588+
monkeypatch.setattr(
589+
"bigquery_magics.bigquery._get_graph_schema", lambda *args: None
590+
)
522591
globalipapp.start_ipython()
523592
ip = globalipapp.get_ipython()
524593
ip.extension_manager.load_extension("bigquery_magics")
@@ -570,6 +639,10 @@ def test_bigquery_graph_str_result(monkeypatch):
570639
reason="Requires `spanner-graph-notebook` and `google-cloud-bigquery-storage`",
571640
)
572641
def test_bigquery_graph_json_json_result(monkeypatch):
642+
"""Graph visualization of JSON objects with valid JSON string fields is supported."""
643+
monkeypatch.setattr(
644+
"bigquery_magics.bigquery._get_graph_schema", lambda *args: None
645+
)
573646
globalipapp.start_ipython()
574647
ip = globalipapp.get_ipython()
575648
ip.extension_manager.load_extension("bigquery_magics")
@@ -639,6 +712,9 @@ def test_bigquery_graph_json_json_result(monkeypatch):
639712
reason="Requires `spanner-graph-notebook` and `google-cloud-bigquery-storage`",
640713
)
641714
def test_bigquery_graph_json_result(monkeypatch):
715+
monkeypatch.setattr(
716+
"bigquery_magics.bigquery._get_graph_schema", lambda *args: None
717+
)
642718
globalipapp.start_ipython()
643719
ip = globalipapp.get_ipython()
644720
ip.extension_manager.load_extension("bigquery_magics")
@@ -758,6 +834,9 @@ def test_bigquery_graph_json_result(monkeypatch):
758834
reason="Requires `spanner-graph-notebook` and `google-cloud-bigquery-storage`",
759835
)
760836
def test_bigquery_graph_size_exceeds_max(monkeypatch):
837+
monkeypatch.setattr(
838+
"bigquery_magics.bigquery._get_graph_schema", lambda *args: None
839+
)
761840
globalipapp.start_ipython()
762841
ip = globalipapp.get_ipython()
763842
ip.extension_manager.load_extension("bigquery_magics")
@@ -813,6 +892,9 @@ def test_bigquery_graph_size_exceeds_max(monkeypatch):
813892
reason="Requires `spanner-graph-notebook` and `google-cloud-bigquery-storage`",
814893
)
815894
def test_bigquery_graph_size_exceeds_query_result_max(monkeypatch):
895+
monkeypatch.setattr(
896+
"bigquery_magics.bigquery._get_graph_schema", lambda *args: None
897+
)
816898
globalipapp.start_ipython()
817899
ip = globalipapp.get_ipython()
818900
ip.extension_manager.load_extension("bigquery_magics")
@@ -869,6 +951,9 @@ def test_bigquery_graph_size_exceeds_query_result_max(monkeypatch):
869951
reason="Requires `spanner-graph-notebook` and `google-cloud-bigquery-storage`",
870952
)
871953
def test_bigquery_graph_with_args_serialization(monkeypatch):
954+
monkeypatch.setattr(
955+
"bigquery_magics.bigquery._get_graph_schema", lambda *args: None
956+
)
872957
globalipapp.start_ipython()
873958
ip = globalipapp.get_ipython()
874959
ip.extension_manager.load_extension("bigquery_magics")
@@ -938,6 +1023,9 @@ def test_bigquery_graph_with_args_serialization(monkeypatch):
9381023
reason="Requires `spanner-graph-notebook` and `google-cloud-bigquery-storage`",
9391024
)
9401025
def test_bigquery_graph_colab(monkeypatch):
1026+
monkeypatch.setattr(
1027+
"bigquery_magics.bigquery._get_graph_schema", lambda *args: None
1028+
)
9411029
# Mock the colab module so the code under test uses colab.register_callback(), rather than
9421030
# GraphServer.
9431031
sys.modules["google.colab"] = mock.Mock()
@@ -1073,6 +1161,9 @@ def test_colab_node_expansion_callback():
10731161
reason="Requires `spanner-graph-notebook` to be missing and `google-cloud-bigquery-storage` to be present",
10741162
)
10751163
def test_bigquery_graph_missing_spanner_deps(monkeypatch):
1164+
monkeypatch.setattr(
1165+
"bigquery_magics.bigquery._get_graph_schema", lambda *args: None
1166+
)
10761167
globalipapp.start_ipython()
10771168
ip = globalipapp.get_ipython()
10781169
ip.extension_manager.load_extension("bigquery_magics")
@@ -1142,11 +1233,17 @@ def test_add_graph_widget_with_schema(monkeypatch):
11421233
query_result = pandas.DataFrame([{"id": 1}], columns=["result"])
11431234
query_text = "GRAPH my_dataset.my_graph"
11441235

1145-
query_job = mock.create_autospec(bigquery.job.QueryJob, instance=True)
1236+
query_job = mock.Mock()
11461237
query_job.configuration.destination.project = "p"
11471238
query_job.configuration.destination.dataset_id = "d"
11481239
query_job.configuration.destination.table_id = "t"
11491240

1241+
graph_ref = mock.Mock()
1242+
graph_ref.project = "p"
1243+
graph_ref.dataset_id = "my_dataset"
1244+
graph_ref.property_graph_id = "my_graph"
1245+
query_job.referenced_property_graphs = [graph_ref]
1246+
11501247
args = mock.Mock()
11511248
args.bigquery_api_endpoint = "e"
11521249
args.project = "p"
@@ -1203,11 +1300,13 @@ def test_add_graph_widget_no_graph_name(monkeypatch):
12031300
query_result = pandas.DataFrame([{"id": 1}], columns=["result"])
12041301
query_text = "SELECT * FROM my_dataset.my_table"
12051302

1206-
query_job = mock.create_autospec(bigquery.job.QueryJob, instance=True)
1303+
query_job = mock.Mock()
12071304
query_job.configuration.destination.project = "p"
12081305
query_job.configuration.destination.dataset_id = "d"
12091306
query_job.configuration.destination.table_id = "t"
12101307

1308+
query_job.referenced_property_graphs = []
1309+
12111310
args = mock.Mock()
12121311
args.bigquery_api_endpoint = "e"
12131312
args.project = "p"
@@ -1244,11 +1343,17 @@ def test_add_graph_widget_schema_not_found(monkeypatch):
12441343
query_result = pandas.DataFrame([{"id": 1}], columns=["result"])
12451344
query_text = "GRAPH my_dataset.my_graph"
12461345

1247-
query_job = mock.create_autospec(bigquery.job.QueryJob, instance=True)
1346+
query_job = mock.Mock()
12481347
query_job.configuration.destination.project = "p"
12491348
query_job.configuration.destination.dataset_id = "d"
12501349
query_job.configuration.destination.table_id = "t"
12511350

1351+
graph_ref = mock.Mock()
1352+
graph_ref.project = "p"
1353+
graph_ref.dataset_id = "my_dataset"
1354+
graph_ref.property_graph_id = "my_graph"
1355+
query_job.referenced_property_graphs = [graph_ref]
1356+
12521357
args = mock.Mock()
12531358
args.bigquery_api_endpoint = "e"
12541359
args.project = "p"
@@ -1293,9 +1398,8 @@ def test_bigquery_magic_default_connection_user_agent():
12931398

12941399
client_info_arg = conn.call_args[1].get("client_info")
12951400
assert client_info_arg is not None
1296-
assert (
1297-
client_info_arg.user_agent
1298-
== f"ipython-{IPython.__version__} bigquery-magics/{bigquery_magics.__version__}"
1401+
assert client_info_arg.user_agent.startswith(
1402+
f"ipython-{IPython.__version__} bigquery-magics/{bigquery_magics.__version__}"
12991403
)
13001404

13011405

@@ -1611,9 +1715,8 @@ def warning_match(warning):
16111715
assert kwargs.get("credentials") is mock_credentials
16121716
client_info = kwargs.get("client_info")
16131717
assert client_info is not None
1614-
assert (
1615-
client_info.user_agent
1616-
== f"ipython-{IPython.__version__} bigquery-magics/{bigquery_magics.__version__}"
1718+
assert client_info.user_agent.startswith(
1719+
f"ipython-{IPython.__version__} bigquery-magics/{bigquery_magics.__version__}"
16171720
)
16181721

16191722
query_job_mock.to_dataframe.assert_called_once_with(

0 commit comments

Comments
 (0)