From 159a10b1ce99b2dff946734b079cb978ea805e66 Mon Sep 17 00:00:00 2001 From: Jennifer Davis Date: Tue, 3 Nov 2026 23:31:48 +0000 Subject: [PATCH] feat(bigquery): add bigquery_create_procedure sample to create a SQL stored procedure Problem: The BigQuery documentation (https://cloud.google.com/bigquery/docs/procedures) needs a standalone, tested Python code sample demonstrating how to create a SQL stored procedure (region tag `bigquery_create_procedure`). Solution: - Add `create_procedure(project_id: str, dataset_id: str) -> None` in `bigquery/stored_procedures/bigquery_create_procedure.py` (Create a reusable multi-statement GoogleSQL stored procedure inside a BigQuery dataset using CREATE OR REPLACE PROCEDURE.) - Add unit tests (`bigquery/stored_procedures/bigquery_create_procedure_test.py`) covering both the success path and API error handling (`GoogleAPICallError`) --- .../bigquery_create_procedure.py | 78 +++++++++++++++++++ .../bigquery_create_procedure_test.py | 70 +++++++++++++++++ bigquery/stored_procedures/noxfile_config.py | 23 ++++++ .../stored_procedures/requirements-test.txt | 1 + bigquery/stored_procedures/requirements.txt | 1 + 5 files changed, 173 insertions(+) create mode 100644 bigquery/stored_procedures/bigquery_create_procedure.py create mode 100644 bigquery/stored_procedures/bigquery_create_procedure_test.py create mode 100644 bigquery/stored_procedures/noxfile_config.py create mode 100644 bigquery/stored_procedures/requirements-test.txt create mode 100644 bigquery/stored_procedures/requirements.txt diff --git a/bigquery/stored_procedures/bigquery_create_procedure.py b/bigquery/stored_procedures/bigquery_create_procedure.py new file mode 100644 index 00000000000..5e71217704a --- /dev/null +++ b/bigquery/stored_procedures/bigquery_create_procedure.py @@ -0,0 +1,78 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START bigquery_create_procedure] +import sys + +from google.api_core import exceptions +from google.cloud import bigquery + + +def create_procedure( + project_id: str, + dataset_id: str, +) -> None: + """Creates a GoogleSQL stored procedure in a BigQuery dataset. + + Args: + project_id: Google Cloud project ID (for example, 'example-project'). + dataset_id: BigQuery dataset ID where the procedure is created + (for example, 'example_dataset'). + """ + client = bigquery.Client(project=project_id) + # Use a fixed procedure identifier inside the caller-supplied dataset. + # The procedure body queries the public Shakespeare dataset in bigquery-public-data + # (https://cloud.google.com/bigquery/public-data). + procedure_id = "example_procedure" + routine_ref = f"{project_id}.{dataset_id}.{procedure_id}" + sql = f""" + CREATE OR REPLACE PROCEDURE `{routine_ref}`( + target_corpus STRING, + OUT total_words INT64 + ) + BEGIN + SET total_words = ( + SELECT COALESCE(SUM(word_count), 0) + FROM `bigquery-public-data.samples.shakespeare` + WHERE corpus = target_corpus + ); + END; + """ + + try: + print(f"Creating stored procedure: {routine_ref}") + query_job = client.query(sql) + query_job.result() + except exceptions.NotFound as err: + print( + f"error: dataset '{dataset_id}' not found in project '{project_id}' " + f"when creating stored procedure '{procedure_id}': {err.message}", + file=sys.stderr, + ) + return + except exceptions.GoogleAPICallError as err: + print( + f"error: failed to create stored procedure '{procedure_id}' in dataset " + f"'{dataset_id}' for project '{project_id}': {err.message}", + file=sys.stderr, + ) + return + + print(f"Resource ID: {routine_ref}") + print(f"Job ID: {query_job.job_id}") + print(f"Procedure ID: {procedure_id}") + print(f"Statement Type: {query_job.statement_type}") + + +# [END bigquery_create_procedure] diff --git a/bigquery/stored_procedures/bigquery_create_procedure_test.py b/bigquery/stored_procedures/bigquery_create_procedure_test.py new file mode 100644 index 00000000000..2e0ad089dcf --- /dev/null +++ b/bigquery/stored_procedures/bigquery_create_procedure_test.py @@ -0,0 +1,70 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import mock + +import bigquery_create_procedure +from google.api_core import exceptions +import pytest + + +def test_create_procedure_success( + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + mock_client = mock.MagicMock() + mock_job = mock.MagicMock() + mock_job.job_id = "example-job-123" + mock_job.statement_type = "SCRIPT" + mock_job.result.return_value = [] + mock_client.query.return_value = mock_job + monkeypatch.setattr( + bigquery_create_procedure.bigquery, + "Client", + lambda project=None: mock_client, + ) + + bigquery_create_procedure.create_procedure( + project_id="example-project", + dataset_id="example_dataset", + ) + + captured = capsys.readouterr() + assert "Resource ID:" in captured.out + assert "example-project" in captured.out + assert mock_client.query.called + + +def test_create_procedure_error( + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + mock_client = mock.MagicMock() + mock_job = mock.MagicMock() + mock_job.result.side_effect = exceptions.NotFound("resource error") + mock_client.query.return_value = mock_job + monkeypatch.setattr( + bigquery_create_procedure.bigquery, + "Client", + lambda project=None: mock_client, + ) + + bigquery_create_procedure.create_procedure( + project_id="example-project", + dataset_id="example_dataset", + ) + + captured = capsys.readouterr() + assert "error:" in captured.err + assert "example-project" in captured.err diff --git a/bigquery/stored_procedures/noxfile_config.py b/bigquery/stored_procedures/noxfile_config.py new file mode 100644 index 00000000000..63902aa8c7f --- /dev/null +++ b/bigquery/stored_procedures/noxfile_config.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11"], + # Enforce type hints on new samples. + "enforce_type_hints": True, + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + "pip_version_override": None, + "envs": {}, +} diff --git a/bigquery/stored_procedures/requirements-test.txt b/bigquery/stored_procedures/requirements-test.txt new file mode 100644 index 00000000000..c9e154ba440 --- /dev/null +++ b/bigquery/stored_procedures/requirements-test.txt @@ -0,0 +1 @@ +pytest==9.0.3; python_version >= "3.10" diff --git a/bigquery/stored_procedures/requirements.txt b/bigquery/stored_procedures/requirements.txt new file mode 100644 index 00000000000..dc6cf8e4329 --- /dev/null +++ b/bigquery/stored_procedures/requirements.txt @@ -0,0 +1 @@ +google-cloud-bigquery==3.47.0