Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions bigquery/bigframes/query_dry_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 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
#
# http://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_bigframes_query_dry_run]
from typing import Tuple

import bigframes.pandas as bpd
import pandas as pd

# Set partial ordering mode as the default configuration for BigQuery
# DataFrames.
bpd.options.bigquery.ordering_mode = "partial"


def query_dry_run(
project_id: str = "your-project-id",
) -> Tuple[pd.Series, pd.Series]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The dry run operations in BigQuery DataFrames return metadata objects (or dict-like objects) rather than a pandas.Series. Update the return type annotation to Tuple[dict, dict] to accurately reflect the returned types.

Suggested change
) -> Tuple[pd.Series, pd.Series]:
) -> Tuple[dict, dict]:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BigFrames does return a panda Series.

"""Estimates the bytes processed by a SQL query and a DataFrame operation."""
bpd.options.bigquery.project = project_id

sql = """
SELECT name, SUM(number) AS total_people
FROM `bigquery-public-data.usa_names.usa_1910_current`
WHERE state = 'TX'
GROUP BY name
"""

# Perform a dry run on a SQL query without executing it.
sql_stats = bpd.read_gbq(sql, use_cache=False, dry_run=True)
print(f"SQL query will process {sql_stats['totalBytesProcessed']} bytes.")

# Perform a dry run on BigQuery DataFrames operations before downloading
# results to an in-memory pandas DataFrame.
df = bpd.read_gbq("bigquery-public-data.usa_names.usa_1910_current")
df_tx = df[df["state"] == "TX"].groupby("name", as_index=False)["number"].sum()
df_stats = df_tx.to_pandas(dry_run=True)
print(f"DataFrame operation will process {df_stats['totalBytesProcessed']} bytes.")

return sql_stats, df_stats


# [END bigquery_bigframes_query_dry_run]


if __name__ == "__main__":
import os

project = os.environ.get("GOOGLE_CLOUD_PROJECT", "your-project-id")
query_dry_run(project_id=project)
29 changes: 29 additions & 0 deletions bigquery/bigframes/query_dry_run_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 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
#
# http://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.

import bigframes.pandas as bpd
import pytest

import query_dry_run


def test_query_dry_run(project_id: str, capsys: pytest.CaptureFixture) -> None:
bpd.close_session()

sql_stats, df_stats = query_dry_run.query_dry_run(project_id=project_id)
out, _ = capsys.readouterr()

assert sql_stats["totalBytesProcessed"] > 0
assert df_stats["totalBytesProcessed"] > 0
assert "bytes" in out
52 changes: 52 additions & 0 deletions bigquery/pandas-gbq-migration/query_dry_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 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
#
# http://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_migration_pandas_gbq_query_dry_run]
import pandas as pd
import pandas_gbq


def query_dry_run(project_id: str = "your-project-id") -> pd.Series:
"""Estimates the bytes processed by a SQL query using a dry run."""
sql = """
SELECT name, SUM(number) AS total_people
FROM `bigquery-public-data.usa_names.usa_1910_current`
WHERE state = 'TX'
GROUP BY name
"""

# Perform a dry run to estimate bytes processed without running the query.
query_config = {
"query": {
"useQueryCache": False,
}
}
stats = pandas_gbq.read_gbq(
sql,
project_id=project_id,
configuration=query_config,
dry_run=True,
)
print(f"This query will process {stats['totalBytesProcessed']} bytes.")
return stats


# [END bigquery_migration_pandas_gbq_query_dry_run]


if __name__ == "__main__":
import os

project = os.environ.get("GOOGLE_CLOUD_PROJECT", "your-project-id")
query_dry_run(project_id=project)
32 changes: 32 additions & 0 deletions bigquery/pandas-gbq-migration/query_dry_run_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 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
#
# http://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.

import os

import pytest

import query_dry_run


@pytest.fixture(scope="session")
def project_id() -> str:
return os.environ["GOOGLE_CLOUD_PROJECT"]


def test_query_dry_run(project_id: str, capsys: pytest.CaptureFixture) -> None:
stats = query_dry_run.query_dry_run(project_id=project_id)
out, _ = capsys.readouterr()

assert stats["totalBytesProcessed"] > 0
assert "bytes" in out
8 changes: 5 additions & 3 deletions bigquery/pandas-gbq-migration/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
google-cloud-bigquery==3.27.0
google-cloud-bigquery-storage==2.27.0
pandas==2.0.3; python_version == '3.8'
pandas==2.2.3; python_version > '3.8'
pandas-gbq==0.24.0
pandas==2.2.3; python_version < '3.11'
pandas==3.1.0; python_version >= '3.11'
pandas-gbq==0.35.3
grpcio==1.70.0; python_version == '3.8'
grpcio==1.74.0; python_version > '3.8'
grpcio==1.74.0; python_version == '3.9'
grpcio==1.85.1; python_version >= '3.10'
Comment on lines 3 to +9

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Python versions below 3.10 are no longer supported. Please remove the fallback dependency versions and environment markers for Python 3.8 and 3.9, and simplify the requirements accordingly.

pandas==2.2.3; python_version < '3.11'
pandas==3.1.0; python_version >= '3.11'
pandas-gbq==0.35.3
grpcio==1.85.1
References
  1. Do not specify fallback dependency versions or environment markers for Python versions older than 3.10, as Python versions below 3.10 are no longer supported.

pyarrow==24.0.0
4 changes: 4 additions & 0 deletions bigquery/polars/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# polars-bigquery samples

This directory contains samples used to demonstrate use of the `polars-bigquery`
package.
22 changes: 22 additions & 0 deletions bigquery/polars/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2021 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.

import os

import pytest


@pytest.fixture(scope="session")
def project_id() -> str:
return os.environ["GOOGLE_CLOUD_PROJECT"]
39 changes: 39 additions & 0 deletions bigquery/polars/noxfile_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2021 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
#
# http://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.

# Default TEST_CONFIG_OVERRIDE for python repos.

# You can copy this file into your directory, then it will be imported from
# the noxfile.py.

# The source of truth:
# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py

TEST_CONFIG_OVERRIDE = {
# You can opt out from the test for specific Python versions.
# Skipping for Python 3.9 due to pyarrow compilation failure.
"ignored_versions": ["3.8", "3.9", "3.11", "3.12", "3.13"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To adhere to the testing policy of only testing the oldest and newest supported Python releases, we should test Python 3.10 (oldest) and Python 3.13 (newest). Please remove Python 3.13 from the ignored versions list so that it is included in the test suite.

Suggested change
"ignored_versions": ["3.8", "3.9", "3.11", "3.12", "3.13"],
"ignored_versions": ["3.8", "3.9", "3.11", "3.12"],
References
  1. Only test the oldest and newest supported Python releases in test configurations (such as noxfile_config.py), ignoring intermediate versions.

# Old samples are opted out of enforcing Python type hints
# All new samples should feature them
"enforce_type_hints": False,
# An envvar key for determining the project id to use. Change it
# to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a
# build specific Cloud project. You can also use your own string
# to use your own Cloud project.
"gcloud_project_env": "GOOGLE_CLOUD_PROJECT",
# 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT',
# A dictionary you want to inject into your test. Don't put any
# secrets here. These values will override predefined values.
"envs": {},
}
51 changes: 51 additions & 0 deletions bigquery/polars/query_bqstorage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 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
#
# http://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_polars_query_bqstorage]
import polars
import polars_bigquery


def construct_client(project_id: str = "your-project-id") -> polars_bigquery.Client:
return polars_bigquery.Client(
quota_project_id=project_id,
)


def query_bqstorage(plbq: polars_bigquery.Client) -> polars.DataFrame:
sql = """
SELECT year, name, SUM(number)
FROM `bigquery-public-data.usa_names.usa_1910_current`
GROUP BY year, name
"""

# Run a query and immediately download the results. polars-biqquery
# automatically uses the BigQuery Storage Read API to download results that
# don't fit in a single response message.
df = plbq.read_query(
sql,
# For the best performance, set maintain_order=False if you don't care
# about the order of the rows in the result. This allows polars-bigquery
# to create multiple BigQuery Storage Read API read streams if needed.
maintain_order=False,
)
return df
# [END bigquery_polars_query_bqstorage]


if __name__ == "__main__":
import os

project = os.environ.get("GOOGLE_CLOUD_PROJECT", "your-project-id")
print(query_bqstorage(construct_client(project)))
21 changes: 21 additions & 0 deletions bigquery/polars/query_bqstorage_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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
#
# http://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.

import query_bqstorage


def test_query_bqstorage(project_id: str) -> None:
plbq = query_bqstorage.construct_client(project_id=project_id)
pandas_df = query_bqstorage.query_bqstorage(plbq)
assert pandas_df is not None
57 changes: 57 additions & 0 deletions bigquery/polars/query_dry_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# 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
#
# http://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_polars_query_dry_run]
import polars
import polars_bigquery


def construct_client(
project_id: str = "your-project-id",
) -> polars_bigquery.Client:
"""Constructs a Polars BigQuery client."""
return polars_bigquery.Client(
quota_project_id=project_id,
)


def query_dry_run(plbq: polars_bigquery.Client) -> polars.DataFrame:
"""Estimates the bytes processed by a SQL query using a dry run."""
sql = """
SELECT name, SUM(number) AS total_people
FROM `bigquery-public-data.usa_names.usa_1910_current`
WHERE state = 'TX'
GROUP BY name
"""

# Perform a dry run to validate the query and estimate bytes processed
# without executing the query.
stats = plbq.read_query(
sql,
dry_run=True,
use_query_cache=False,
)
total_bytes = stats.item(0, "totalBytesProcessed")
print(f"This query will process {total_bytes} bytes.")
return stats


# [END bigquery_polars_query_dry_run]


if __name__ == "__main__":
import os

project = os.environ.get("GOOGLE_CLOUD_PROJECT", "your-project-id")
query_dry_run(construct_client(project_id=project))
Loading
Loading