Skip to content

Commit 27459f3

Browse files
committed
feat(bigquery): add bigquery_query_external_bigtable sample to query Cloud Bigtable data as an external table
Problem: The BigQuery documentation (https://cloud.google.com/bigquery/docs/external-data-bigtable) needs a standalone, tested Python code sample demonstrating how to query Cloud Bigtable data as an external table (region tag `bigquery_query_external_bigtable`). Solution: - Add `query_external_bigtable(project_id: str, bigtable_instance_id: str, bigtable_table_id: str) -> None` in `bigquery/biglake/bigquery_query_external_bigtable.py` (Configure a temporary Cloud Bigtable external table mapping column families and qualifiers and query it from BigQuery.) - Add unit tests (`bigquery/biglake/bigquery_query_external_bigtable_test.py`) covering both the success path and API error handling (`GoogleAPICallError`)
1 parent 186be98 commit 27459f3

5 files changed

Lines changed: 193 additions & 0 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# [START bigquery_query_external_bigtable]
16+
import sys
17+
18+
from google.api_core import exceptions
19+
from google.cloud import bigquery
20+
21+
22+
def query_external_bigtable(
23+
project_id: str,
24+
bigtable_instance_id: str,
25+
bigtable_table_id: str,
26+
) -> None:
27+
"""Configures and queries a temporary BigQuery external table backed by Cloud Bigtable.
28+
29+
Args:
30+
project_id: Google Cloud project ID (for example, 'example-project').
31+
bigtable_instance_id: Cloud Bigtable instance ID hosting the source table
32+
(for example, 'example-bt-instance').
33+
bigtable_table_id: Cloud Bigtable table ID containing the column family
34+
(for example, 'example_bt_table').
35+
"""
36+
client = bigquery.Client(project=project_id)
37+
temp_table_name = "example_bigtable_temp"
38+
source_uri = (
39+
f"https://googleapis.com/bigtable/projects/{project_id}"
40+
f"/instances/{bigtable_instance_id}/tables/{bigtable_table_id}"
41+
)
42+
43+
# Map the Cloud Bigtable column family into a temporary table definition for a single query job.
44+
external_config = bigquery.ExternalConfig("BIGTABLE")
45+
external_config.source_uris = [source_uri]
46+
external_config.bigtable_options.read_rowkey_as_string = True
47+
family = bigquery.BigtableColumnFamily()
48+
family.family_id = "stats_summary"
49+
family.type_ = "STRING"
50+
family.encoding = "TEXT"
51+
external_config.bigtable_options.column_families = [family]
52+
53+
job_config = bigquery.QueryJobConfig(
54+
table_definitions={temp_table_name: external_config}
55+
)
56+
sql = f"""
57+
SELECT
58+
rowkey,
59+
stats_summary
60+
FROM `{temp_table_name}`
61+
ORDER BY rowkey ASC
62+
LIMIT 10;
63+
"""
64+
65+
try:
66+
print(f"Querying temporary Cloud Bigtable table mapped to {source_uri}")
67+
query_job = client.query(sql, job_config=job_config)
68+
rows = list(query_job.result())
69+
except exceptions.NotFound as err:
70+
print(
71+
f"error: Cloud Bigtable table '{bigtable_table_id}' in instance "
72+
f"'{bigtable_instance_id}' not found for project '{project_id}': {err.message}",
73+
file=sys.stderr,
74+
)
75+
return
76+
except exceptions.GoogleAPICallError as err:
77+
print(
78+
f"error: failed to query Cloud Bigtable table '{bigtable_table_id}' in "
79+
f"instance '{bigtable_instance_id}' for project '{project_id}': {err.message}",
80+
file=sys.stderr,
81+
)
82+
return
83+
84+
print(f"Resource ID: {source_uri}")
85+
print(f"Job ID: {query_job.job_id}")
86+
print(f"Rows Returned: {len(rows)}")
87+
print(f"Temporary Table: {temp_table_name}")
88+
89+
90+
# [END bigquery_query_external_bigtable]
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from unittest import mock
16+
17+
import bigquery_query_external_bigtable
18+
from google.api_core import exceptions
19+
import pytest
20+
21+
22+
def test_query_external_bigtable_success(
23+
monkeypatch: pytest.MonkeyPatch,
24+
capsys: pytest.CaptureFixture[str],
25+
) -> None:
26+
mock_client = mock.MagicMock()
27+
mock_job = mock.MagicMock()
28+
mock_job.job_id = "example-job-123"
29+
mock_row = mock.MagicMock()
30+
mock_row.rowkey = "row-001"
31+
mock_job.result.return_value = [mock_row]
32+
mock_client.query.return_value = mock_job
33+
monkeypatch.setattr(
34+
bigquery_query_external_bigtable.bigquery,
35+
"Client",
36+
lambda project=None: mock_client,
37+
)
38+
39+
bigquery_query_external_bigtable.query_external_bigtable(
40+
project_id="example-project",
41+
bigtable_instance_id="example-bt-instance",
42+
bigtable_table_id="example_bt_table",
43+
)
44+
45+
captured = capsys.readouterr()
46+
assert (
47+
"Resource ID: https://googleapis.com/bigtable/projects/example-project"
48+
in captured.out
49+
)
50+
assert "Rows Returned: 1" in captured.out
51+
assert "Temporary Table: example_bigtable_temp" in captured.out
52+
assert mock_client.query.called
53+
54+
55+
def test_query_external_bigtable_error(
56+
monkeypatch: pytest.MonkeyPatch,
57+
capsys: pytest.CaptureFixture[str],
58+
) -> None:
59+
mock_client = mock.MagicMock()
60+
mock_job = mock.MagicMock()
61+
mock_job.result.side_effect = exceptions.NotFound("resource error")
62+
mock_client.query.return_value = mock_job
63+
monkeypatch.setattr(
64+
bigquery_query_external_bigtable.bigquery,
65+
"Client",
66+
lambda project=None: mock_client,
67+
)
68+
69+
bigquery_query_external_bigtable.query_external_bigtable(
70+
project_id="example-project",
71+
bigtable_instance_id="example-bt-instance",
72+
bigtable_table_id="example_bt_table",
73+
)
74+
75+
captured = capsys.readouterr()
76+
assert "error:" in captured.err
77+
assert "example-bt-instance" in captured.err

‎bigquery/biglake/noxfile_config.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
TEST_CONFIG_OVERRIDE = {
16+
# You can opt out from the test for specific Python versions.
17+
"ignored_versions": ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11"],
18+
# Enforce type hints on new samples.
19+
"enforce_type_hints": True,
20+
"gcloud_project_env": "GOOGLE_CLOUD_PROJECT",
21+
"pip_version_override": None,
22+
"envs": {},
23+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest==9.0.3; python_version >= "3.10"

‎bigquery/biglake/requirements.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
google-cloud-bigquery==3.47.0
2+
google-cloud-bigquery-storage==2.43.0

0 commit comments

Comments
 (0)