From d93c06f55d5a0fe548f185fac0fa3ee3e1074d11 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Tue, 12 May 2026 07:51:31 -0400 Subject: [PATCH 1/8] feat(pandas-gbq): add ruff format session and implement mypy Added Ruff-based format session and implemented mypy session based on the gold standard template. --- packages/pandas-gbq/noxfile.py | 48 +++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/packages/pandas-gbq/noxfile.py b/packages/pandas-gbq/noxfile.py index b850a2a74e0a..8e58d09628bc 100644 --- a/packages/pandas-gbq/noxfile.py +++ b/packages/pandas-gbq/noxfile.py @@ -18,18 +18,19 @@ from __future__ import absolute_import -from functools import wraps import os import pathlib import re import shutil import time import warnings +from functools import wraps import nox BLACK_VERSION = "black==23.7.0" ISORT_VERSION = "isort==5.10.1" +RUFF_VERSION = "ruff==0.14.14" LINT_PATHS = ["docs", "pandas_gbq", "tests", "noxfile.py", "setup.py"] DEFAULT_PYTHON_VERSION = "3.14" @@ -147,19 +148,29 @@ def blacken(session): @_calculate_duration def format(session): """ - Run isort to sort imports. Then run black - to format code to uniform standard. + Run ruff to sort imports and format code. """ - session.install(BLACK_VERSION, ISORT_VERSION) - # Use the --fss option to sort imports using strict alphabetical order. - # See https://pycqa.github.io/isort/docs/configuration/options.html#force-sort-within-sections + # 1. Install ruff (skipped automatically if you run with --no-venv) + session.install(RUFF_VERSION) + + # 2. Run Ruff to fix imports session.run( - "isort", - "--fss", + "ruff", + "check", + "--select", + "I", + "--fix", + f"--target-version=py{UNIT_TEST_PYTHON_VERSIONS[0].replace('.', '')}", + "--line-length=88", *LINT_PATHS, ) + + # 3. Run Ruff to format code session.run( - "black", + "ruff", + "format", + f"--target-version=py{UNIT_TEST_PYTHON_VERSIONS[0].replace('.', '')}", + "--line-length=88", *LINT_PATHS, ) @@ -519,11 +530,24 @@ def docfx(session): @nox.session(python=DEFAULT_PYTHON_VERSION) +@_calculate_duration def mypy(session): """Run the type checker.""" - # TODO(https://github.com/googleapis/google-cloud-python/issues/16014): - # Add mypy tests - session.skip("mypy tests are not yet supported") + session.install( + "mypy<1.16.0", + "types-requests", + "types-protobuf", + "pandas-stubs", + "types-tqdm", + "types-psutil", + ) + session.install(".") + session.run( + "mypy", + "pandas_gbq", + "--check-untyped-defs", + *session.posargs, + ) @nox.session(python=DEFAULT_PYTHON_VERSION) From 26005caa5f45524a1afa2a29ccfee6968c1e9b13 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Tue, 12 May 2026 07:51:35 -0400 Subject: [PATCH 2/8] chore(pandas-gbq): add type ignores and descriptive comments for mypy/flake8 Added type ignores for untyped imports and descriptive comments to explain them to junior engineers. --- packages/pandas-gbq/pandas_gbq/auth.py | 6 ++- packages/pandas-gbq/pandas_gbq/core/read.py | 8 ++-- packages/pandas-gbq/pandas_gbq/gbq.py | 44 +++++++++++-------- packages/pandas-gbq/pandas_gbq/load/core.py | 7 +-- .../pandas_gbq/schema/pandas_to_bigquery.py | 7 +-- 5 files changed, 43 insertions(+), 29 deletions(-) diff --git a/packages/pandas-gbq/pandas_gbq/auth.py b/packages/pandas-gbq/pandas_gbq/auth.py index 704f15be1c0c..1d4de91264ff 100644 --- a/packages/pandas-gbq/pandas_gbq/auth.py +++ b/packages/pandas-gbq/pandas_gbq/auth.py @@ -23,7 +23,8 @@ def get_credentials( client_id=None, client_secret=None, ): - import pydata_google_auth + # pydata-google-auth does not have type hints nor stubs that mypy uses for type checking. + import pydata_google_auth # type: ignore[import-untyped] if private_key: raise NotImplementedError( @@ -48,7 +49,8 @@ def get_credentials( def get_credentials_cache(reauth): - import pydata_google_auth.cache + # pydata-google-auth does not have type hints nor stubs that mypy uses for type checking. + import pydata_google_auth.cache # type: ignore[import-untyped] if reauth: return pydata_google_auth.cache.WriteOnlyCredentialsCache( diff --git a/packages/pandas-gbq/pandas_gbq/core/read.py b/packages/pandas-gbq/pandas_gbq/core/read.py index ecd4e00cd7f4..f4e28e7079ef 100644 --- a/packages/pandas-gbq/pandas_gbq/core/read.py +++ b/packages/pandas-gbq/pandas_gbq/core/read.py @@ -5,8 +5,8 @@ from __future__ import annotations import typing -from typing import Any, Dict, Optional, Sequence import warnings +from typing import Any, Dict, Optional, Sequence import google.cloud.bigquery import google.cloud.bigquery.table @@ -34,7 +34,8 @@ def _bqschema_to_nullsafe_dtypes(schema_fields): See: http://pandas.pydata.org/pandas-docs/dev/missing_data.html #missing-data-casting-rules-and-indexing """ - import db_dtypes + # db-dtypes does not have type hints nor stubs that mypy uses for type checking. + import db_dtypes # type: ignore[import-untyped] # If you update this mapping, also update the table at # `docs/reading.rst`. @@ -79,7 +80,8 @@ def _finalize_dtypes( 1970. See: https://github.com/googleapis/python-bigquery-pandas/issues/365 """ - import db_dtypes + # db-dtypes does not have type hints nor stubs that mypy uses for type checking. + import db_dtypes # type: ignore[import-untyped] import pandas.api.types # If you update this mapping, also update the table at diff --git a/packages/pandas-gbq/pandas_gbq/gbq.py b/packages/pandas-gbq/pandas_gbq/gbq.py index 6bdf475e7bcb..05b70d3af0a7 100644 --- a/packages/pandas-gbq/pandas_gbq/gbq.py +++ b/packages/pandas-gbq/pandas_gbq/gbq.py @@ -3,32 +3,36 @@ # license that can be found in the LICENSE file. import copy -from datetime import datetime import logging import re import typing import warnings +from datetime import datetime import pandas -from pandas_gbq.contexts import Context # noqa - backward compatible export -from pandas_gbq.contexts import context -from pandas_gbq.exceptions import ( # noqa - backward compatible export +import pandas_gbq.schema +import pandas_gbq.schema.pandas_to_bigquery +from pandas_gbq.contexts import ( # noqa: F401 + Context, # noqa: F401 - imported solely to support a backwards compatible export + context, +) +from pandas_gbq.exceptions import ( # noqa: F401 - imported solely to support a backwards compatible export DatasetCreationError, GenericGBQException, InvalidColumnOrder, InvalidIndexColumn, + InvalidPageToken, # noqa: F401 - imported solely to support a backwards compatible export + InvalidSchema, # noqa: F401 - imported solely to support a backwards compatible export NotFoundException, + QueryTimeout, # noqa: F401 - imported solely to support a backwards compatible export TableCreationError, ) -from pandas_gbq.exceptions import InvalidPageToken # noqa - backward compatible export -from pandas_gbq.exceptions import InvalidSchema # noqa - backward compatible export -from pandas_gbq.exceptions import QueryTimeout # noqa - backward compatible export from pandas_gbq.features import FEATURES -from pandas_gbq.gbq_connector import GbqConnector # noqa - backward compatible export -from pandas_gbq.gbq_connector import _get_client # noqa - backward compatible export -import pandas_gbq.schema -import pandas_gbq.schema.pandas_to_bigquery +from pandas_gbq.gbq_connector import ( + GbqConnector, # noqa: F401 - imported solely to support a backwards compatible export + _get_client, # noqa: F401 - imported solely to support a backwards compatible export +) logger = logging.getLogger(__name__) @@ -40,17 +44,23 @@ def _test_google_api_imports(): raise ImportError("pandas-gbq requires db-dtypes") from ex try: - import db_dtypes # noqa + # db-dtypes does not have type hints nor stubs that mypy uses for type checking. + # This import is solely to test if the package is installed, so we ignore the "unused import" warning. + import db_dtypes # type: ignore[import-untyped] # noqa: F401 except ImportError as ex: # pragma: NO COVER raise ImportError("pandas-gbq requires db-dtypes") from ex try: - import pydata_google_auth # noqa + # pydata-google-auth does not have type hints nor stubs that mypy uses for type checking. + # This import is solely to test if the package is installed, so we ignore the "unused import" warning. + import pydata_google_auth # type: ignore[import-untyped] # noqa: F401 except ImportError as ex: # pragma: NO COVER raise ImportError("pandas-gbq requires pydata-google-auth") from ex try: - from google_auth_oauthlib.flow import InstalledAppFlow # noqa + # google-auth-oauthlib does not have type hints nor stubs that mypy uses for type checking. + # This import is solely to test if the package is installed, so we ignore the "unused import" warning. + from google_auth_oauthlib.flow import InstalledAppFlow # type: ignore[import-untyped] # noqa: F401 except ImportError as ex: # pragma: NO COVER raise ImportError("pandas-gbq requires google-auth-oauthlib") from ex @@ -686,7 +696,7 @@ def generate_bq_schema(df, default_type="STRING"): """ # deprecation TimeSeries, #11121 warnings.warn( - "generate_bq_schema is deprecated and will be removed in " "a future version", + "generate_bq_schema is deprecated and will be removed in a future version", FutureWarning, stacklevel=2, ) @@ -927,9 +937,7 @@ def create(self, dataset_id): from google.cloud.bigquery import Dataset if self.exists(dataset_id): - raise DatasetCreationError( - "Dataset {0} already " "exists".format(dataset_id) - ) + raise DatasetCreationError("Dataset {0} already exists".format(dataset_id)) dataset = Dataset(self._dataset_ref(dataset_id)) diff --git a/packages/pandas-gbq/pandas_gbq/load/core.py b/packages/pandas-gbq/pandas_gbq/load/core.py index 553e56f47158..f438ff4effa9 100644 --- a/packages/pandas-gbq/pandas_gbq/load/core.py +++ b/packages/pandas-gbq/pandas_gbq/load/core.py @@ -8,15 +8,16 @@ import io from typing import Any, Callable, Dict, List, Optional -import db_dtypes -from google.cloud import bigquery +# db-dtypes does not have type hints nor stubs that mypy uses for type checking. +import db_dtypes # type: ignore[import-untyped] import pandas import pyarrow.lib +from google.cloud import bigquery -from pandas_gbq import exceptions import pandas_gbq.schema import pandas_gbq.schema.bigquery import pandas_gbq.schema.pandas_to_bigquery +from pandas_gbq import exceptions def encode_chunk(dataframe): diff --git a/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py b/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py index f2a56f527e3d..f15650af4033 100644 --- a/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py +++ b/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py @@ -4,13 +4,14 @@ import collections.abc import datetime -from typing import Any, Optional, Tuple import warnings +from typing import Any, Optional, Tuple -import db_dtypes -from google.cloud.bigquery import schema +# db-dtypes does not have type hints nor stubs that mypy uses for type checking. +import db_dtypes # type: ignore[import-untyped] import pandas import pyarrow +from google.cloud.bigquery import schema import pandas_gbq.core.pandas import pandas_gbq.schema.bigquery From b5f60c247506e021ce5fac29be3ea3bdb0b58289 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Tue, 12 May 2026 07:51:38 -0400 Subject: [PATCH 3/8] style(pandas-gbq): format files with ruff and black Formatted files using Ruff and Black to resolve linting failures. --- packages/pandas-gbq/pandas_gbq/dry_runs.py | 2 +- packages/pandas-gbq/pandas_gbq/gbq_connector.py | 8 ++++---- .../pandas_gbq/schema/pyarrow_to_bigquery.py | 2 +- packages/pandas-gbq/tests/system/conftest.py | 2 +- packages/pandas-gbq/tests/system/test_gbq.py | 8 +++----- packages/pandas-gbq/tests/system/test_read_gbq.py | 2 +- .../tests/unit/schema/test_pandas_to_bigquery.py | 2 +- .../tests/unit/schema/test_pyarrow_to_bigquery.py | 2 +- packages/pandas-gbq/tests/unit/test_dry_runs.py | 2 +- packages/pandas-gbq/tests/unit/test_gbq.py | 6 +++--- packages/pandas-gbq/tests/unit/test_load.py | 2 +- packages/pandas-gbq/tests/unit/test_query.py | 14 ++++++++------ packages/pandas-gbq/tests/unit/test_to_gbq.py | 2 +- 13 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/pandas-gbq/pandas_gbq/dry_runs.py b/packages/pandas-gbq/pandas_gbq/dry_runs.py index 7168dd9767f7..87c33f71125b 100644 --- a/packages/pandas-gbq/pandas_gbq/dry_runs.py +++ b/packages/pandas-gbq/pandas_gbq/dry_runs.py @@ -7,8 +7,8 @@ import copy from typing import Any, List -from google.cloud import bigquery import pandas +from google.cloud import bigquery def get_query_stats( diff --git a/packages/pandas-gbq/pandas_gbq/gbq_connector.py b/packages/pandas-gbq/pandas_gbq/gbq_connector.py index dec1a00cff93..5940efb1ac2f 100644 --- a/packages/pandas-gbq/pandas_gbq/gbq_connector.py +++ b/packages/pandas-gbq/pandas_gbq/gbq_connector.py @@ -7,8 +7,8 @@ import logging import time import typing -from typing import Any, Dict, Optional, Union import warnings +from typing import Any, Dict, Optional, Union # Only import at module-level at type checking time to avoid circular # dependencies in the pandas package, which has an optional dependency on @@ -16,15 +16,15 @@ if typing.TYPE_CHECKING: # pragma: NO COVER import pandas -from pandas_gbq import dry_runs import pandas_gbq.constants -from pandas_gbq.contexts import context import pandas_gbq.core.read import pandas_gbq.environment as environment import pandas_gbq.exceptions +import pandas_gbq.query +from pandas_gbq import dry_runs +from pandas_gbq.contexts import context from pandas_gbq.exceptions import QueryTimeout from pandas_gbq.features import FEATURES -import pandas_gbq.query try: import tqdm # noqa diff --git a/packages/pandas-gbq/pandas_gbq/schema/pyarrow_to_bigquery.py b/packages/pandas-gbq/pandas_gbq/schema/pyarrow_to_bigquery.py index f13dbc0076be..7c0ff649bfd9 100644 --- a/packages/pandas-gbq/pandas_gbq/schema/pyarrow_to_bigquery.py +++ b/packages/pandas-gbq/pandas_gbq/schema/pyarrow_to_bigquery.py @@ -6,9 +6,9 @@ from typing import Optional, cast -from google.cloud.bigquery import schema import pyarrow import pyarrow.types +from google.cloud.bigquery import schema _ARROW_SCALAR_IDS_TO_BQ = { # https://arrow.apache.org/docs/python/api/datatypes.html#type-classes diff --git a/packages/pandas-gbq/tests/system/conftest.py b/packages/pandas-gbq/tests/system/conftest.py index c761276d190e..e6a4ec6844ea 100644 --- a/packages/pandas-gbq/tests/system/conftest.py +++ b/packages/pandas-gbq/tests/system/conftest.py @@ -6,9 +6,9 @@ import os import pathlib -from google.cloud import bigquery import pytest import test_utils.prefixer +from google.cloud import bigquery prefixer = test_utils.prefixer.Prefixer("python-bigquery-pandas", "tests/system") diff --git a/packages/pandas-gbq/tests/system/test_gbq.py b/packages/pandas-gbq/tests/system/test_gbq.py index 4cdb3ebec72f..ad6accbfc272 100644 --- a/packages/pandas-gbq/tests/system/test_gbq.py +++ b/packages/pandas-gbq/tests/system/test_gbq.py @@ -11,13 +11,13 @@ import numpy as np import packaging.version import pandas -from pandas import DataFrame import pandas.api.types import pandas.testing as tm import pytest +from pandas import DataFrame -from pandas_gbq import gbq import pandas_gbq.schema +from pandas_gbq import gbq TABLE_ID = "new_test" PANDAS_VERSION = packaging.version.parse(pandas.__version__) @@ -333,9 +333,7 @@ def test_legacy_sql(self, project_id): assert len(df.drop_duplicates()) == 10 def test_standard_sql(self, project_id): - standard_sql = ( - "SELECT DISTINCT id FROM " "`publicdata.samples.wikipedia` LIMIT 10" - ) + standard_sql = "SELECT DISTINCT id FROM `publicdata.samples.wikipedia` LIMIT 10" # Test that a standard sql statement fails when using # the legacy SQL dialect. diff --git a/packages/pandas-gbq/tests/system/test_read_gbq.py b/packages/pandas-gbq/tests/system/test_read_gbq.py index 946da668ba81..333ff2090019 100644 --- a/packages/pandas-gbq/tests/system/test_read_gbq.py +++ b/packages/pandas-gbq/tests/system/test_read_gbq.py @@ -8,11 +8,11 @@ import random import db_dtypes -from google.cloud import bigquery import packaging.version import pandas import pandas.testing import pytest +from google.cloud import bigquery from pandas_gbq.features import FEATURES diff --git a/packages/pandas-gbq/tests/unit/schema/test_pandas_to_bigquery.py b/packages/pandas-gbq/tests/unit/schema/test_pandas_to_bigquery.py index 4f3be85c2746..5557f2f0a402 100644 --- a/packages/pandas-gbq/tests/unit/schema/test_pandas_to_bigquery.py +++ b/packages/pandas-gbq/tests/unit/schema/test_pandas_to_bigquery.py @@ -7,9 +7,9 @@ import decimal import operator -from google.cloud.bigquery import schema import pandas import pytest +from google.cloud.bigquery import schema @pytest.fixture diff --git a/packages/pandas-gbq/tests/unit/schema/test_pyarrow_to_bigquery.py b/packages/pandas-gbq/tests/unit/schema/test_pyarrow_to_bigquery.py index dc5504f99e12..71755b4be368 100644 --- a/packages/pandas-gbq/tests/unit/schema/test_pyarrow_to_bigquery.py +++ b/packages/pandas-gbq/tests/unit/schema/test_pyarrow_to_bigquery.py @@ -2,9 +2,9 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -from google.cloud import bigquery import pyarrow import pytest +from google.cloud import bigquery from pandas_gbq.schema import pyarrow_to_bigquery diff --git a/packages/pandas-gbq/tests/unit/test_dry_runs.py b/packages/pandas-gbq/tests/unit/test_dry_runs.py index 8d72066e9c8a..2ea6a91576b7 100644 --- a/packages/pandas-gbq/tests/unit/test_dry_runs.py +++ b/packages/pandas-gbq/tests/unit/test_dry_runs.py @@ -4,9 +4,9 @@ from unittest import mock -from google.cloud import bigquery import pandas import pandas.testing +from google.cloud import bigquery from pandas_gbq import dry_runs diff --git a/packages/pandas-gbq/tests/unit/test_gbq.py b/packages/pandas-gbq/tests/unit/test_gbq.py index 6af42c4725ed..5de78f890e9f 100644 --- a/packages/pandas-gbq/tests/unit/test_gbq.py +++ b/packages/pandas-gbq/tests/unit/test_gbq.py @@ -7,8 +7,8 @@ import copy import datetime import re -from unittest import mock import warnings +from unittest import mock import google.api_core.exceptions import google.cloud.bigquery @@ -16,14 +16,14 @@ import numpy import packaging.version import pandas -from pandas import DataFrame import pytest +from pandas import DataFrame -from pandas_gbq import gbq import pandas_gbq.constants import pandas_gbq.core.read import pandas_gbq.exceptions import pandas_gbq.features +from pandas_gbq import gbq from pandas_gbq.features import FEATURES pytestmark = pytest.mark.filterwarnings("ignore:credentials from Google Cloud SDK") diff --git a/packages/pandas-gbq/tests/unit/test_load.py b/packages/pandas-gbq/tests/unit/test_load.py index bb6117817b1d..c222bc35d0eb 100644 --- a/packages/pandas-gbq/tests/unit/test_load.py +++ b/packages/pandas-gbq/tests/unit/test_load.py @@ -6,8 +6,8 @@ import datetime import decimal -from io import StringIO import textwrap +from io import StringIO import db_dtypes import numpy diff --git a/packages/pandas-gbq/tests/unit/test_query.py b/packages/pandas-gbq/tests/unit/test_query.py index 1ab7e54f6c79..25e19dbc8e3d 100644 --- a/packages/pandas-gbq/tests/unit/test_query.py +++ b/packages/pandas-gbq/tests/unit/test_query.py @@ -121,9 +121,10 @@ def test_query_and_wait_via_client_library_timeout_raises_querytimeout( "fake timeout" ) - with freezegun.freeze_time( - "2020-01-01 00:00:00", auto_tick_seconds=15 - ), pytest.raises(pandas_gbq.exceptions.QueryTimeout): + with ( + freezegun.freeze_time("2020-01-01 00:00:00", auto_tick_seconds=15), + pytest.raises(pandas_gbq.exceptions.QueryTimeout), + ): module_under_test.query_and_wait_via_client_library( connector, mock_bigquery_client, @@ -199,9 +200,10 @@ def test__wait_for_query_job_cancels_after_timeout(mock_bigquery_client): mock_query.state = "RUNNING" mock_query.result.side_effect = concurrent.futures.TimeoutError("fake timeout") - with freezegun.freeze_time( - "2020-01-01 00:00:00", auto_tick_seconds=15 - ), pytest.raises(pandas_gbq.exceptions.QueryTimeout): + with ( + freezegun.freeze_time("2020-01-01 00:00:00", auto_tick_seconds=15), + pytest.raises(pandas_gbq.exceptions.QueryTimeout), + ): module_under_test._wait_for_query_job( connector, mock_bigquery_client, mock_query, 60 ) diff --git a/packages/pandas-gbq/tests/unit/test_to_gbq.py b/packages/pandas-gbq/tests/unit/test_to_gbq.py index 1736dab5d1bc..b62db6dbc821 100644 --- a/packages/pandas-gbq/tests/unit/test_to_gbq.py +++ b/packages/pandas-gbq/tests/unit/test_to_gbq.py @@ -11,8 +11,8 @@ import google.api_core.exceptions import google.cloud.bigquery import pandas as pd -from pandas import DataFrame import pytest +from pandas import DataFrame from pandas_gbq import gbq From 37ca159a7c2db5807afaa750526ae5463f1077f9 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Tue, 12 May 2026 07:55:37 -0400 Subject: [PATCH 4/8] fix(pandas-gbq): restore missing noqa pragma in gbq.py Restored the missing # noqa: F401 on multi-line import in gbq.py to fix flake8 failure. --- packages/pandas-gbq/pandas_gbq/gbq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pandas-gbq/pandas_gbq/gbq.py b/packages/pandas-gbq/pandas_gbq/gbq.py index 05b70d3af0a7..ef0989fc348b 100644 --- a/packages/pandas-gbq/pandas_gbq/gbq.py +++ b/packages/pandas-gbq/pandas_gbq/gbq.py @@ -29,7 +29,7 @@ TableCreationError, ) from pandas_gbq.features import FEATURES -from pandas_gbq.gbq_connector import ( +from pandas_gbq.gbq_connector import ( # noqa: F401 GbqConnector, # noqa: F401 - imported solely to support a backwards compatible export _get_client, # noqa: F401 - imported solely to support a backwards compatible export ) From 0bdaa06a8a4776409f16127ed4ad08f988189ead Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Tue, 12 May 2026 08:03:44 -0400 Subject: [PATCH 5/8] fix(pandas-gbq): resolve mypy type errors in core, schema, load, and connector Resolved 11 mypy errors by adding assertions, checking for None, using Any for tqdm, and converting tuple to list. --- packages/pandas-gbq/pandas_gbq/core/pandas.py | 1 + packages/pandas-gbq/pandas_gbq/gbq_connector.py | 5 ++++- packages/pandas-gbq/pandas_gbq/load/core.py | 10 +++++----- .../pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/pandas-gbq/pandas_gbq/core/pandas.py b/packages/pandas-gbq/pandas_gbq/core/pandas.py index 37557adf8b70..3f264984c871 100644 --- a/packages/pandas-gbq/pandas_gbq/core/pandas.py +++ b/packages/pandas-gbq/pandas_gbq/core/pandas.py @@ -22,6 +22,7 @@ def list_columns_and_indexes(dataframe, index=True): if isinstance(dataframe.index, pandas.MultiIndex): for name in dataframe.index.names: if name and name not in column_names: + assert isinstance(name, (str, int)) values = dataframe.index.get_level_values(name) columns_and_indexes.append((name, values.dtype)) else: diff --git a/packages/pandas-gbq/pandas_gbq/gbq_connector.py b/packages/pandas-gbq/pandas_gbq/gbq_connector.py index 5940efb1ac2f..6f25fbe1e4c4 100644 --- a/packages/pandas-gbq/pandas_gbq/gbq_connector.py +++ b/packages/pandas-gbq/pandas_gbq/gbq_connector.py @@ -26,10 +26,11 @@ from pandas_gbq.exceptions import QueryTimeout from pandas_gbq.features import FEATURES +tqdm: Any = None try: import tqdm # noqa except ImportError: - tqdm = None + pass logger = logging.getLogger(__name__) @@ -204,6 +205,7 @@ def run_query( ].get("timeoutMs") if timeout_ms: + assert isinstance(timeout_ms, (str, int, float)) timeout_ms = int(timeout_ms) # Having too small a timeout_ms results in individual # API calls timing out before they can finish. @@ -220,6 +222,7 @@ def run_query( self._start_timer() job_config = bigquery.QueryJobConfig.from_api_repr(job_config_dict) + assert isinstance(job_config, bigquery.QueryJobConfig) job_config.dry_run = dry_run if FEATURES.bigquery_has_query_and_wait: diff --git a/packages/pandas-gbq/pandas_gbq/load/core.py b/packages/pandas-gbq/pandas_gbq/load/core.py index f438ff4effa9..55845d02e5b1 100644 --- a/packages/pandas-gbq/pandas_gbq/load/core.py +++ b/packages/pandas-gbq/pandas_gbq/load/core.py @@ -39,8 +39,8 @@ def encode_chunk(dataframe): # Convert to a BytesIO buffer so that unicode text is properly handled. # See: https://github.com/pydata/pandas-gbq/issues/106 body = csv_buffer.getvalue() - body = body.encode("utf-8") - return io.BytesIO(body) + body_bytes = body.encode("utf-8") + return io.BytesIO(body_bytes) def split_dataframe(dataframe, chunksize=None): @@ -69,7 +69,7 @@ def cast_dataframe_for_parquet( See: https://github.com/googleapis/python-bigquery-pandas/issues/421 """ - columns = schema.get("fields", []) + columns = schema.get("fields", []) if schema is not None else [] # Protect against an explicit None in the dictionary. columns = columns if columns is not None else [] @@ -131,7 +131,7 @@ def cast_dataframe_for_csv( ) -> pandas.DataFrame: """Cast columns to needed dtype when writing CSV files.""" - columns = schema.get("fields", []) + columns = schema.get("fields", []) if schema is not None else [] # Protect against an explicit None in the dictionary. columns = columns if columns is not None else [] @@ -281,7 +281,7 @@ def load_chunk(chunk, job_config): finally: chunk_buffer.close() - return load_csv(dataframe, write_disposition, chunksize, bq_schema, load_chunk) + return load_csv(dataframe, write_disposition, chunksize, list(bq_schema), load_chunk) def load_chunks( diff --git a/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py b/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py index f15650af4033..ed5e680322ce 100644 --- a/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py +++ b/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py @@ -210,7 +210,7 @@ def value_to_bigquery_field( # Set the SchemaField datatype to the given default_type if the value # being assessed is None. if value is None: - return schema.SchemaField(name, default_type) + return schema.SchemaField(name, default_type or "STRING") # Map from Python types to BigQuery types. This isn't super exhaustive # because we rely more on pyarrow, which can check more than one value to From 7c74456259fcf984d569e688f6a847518d819b42 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Tue, 12 May 2026 08:11:44 -0400 Subject: [PATCH 6/8] fix(pandas-gbq): remove asserts from src code per team policy Replaced asserts with typing.cast and explicit TypeError checks to follow team policy. --- packages/pandas-gbq/pandas_gbq/core/pandas.py | 4 ++-- packages/pandas-gbq/pandas_gbq/gbq_connector.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/pandas-gbq/pandas_gbq/core/pandas.py b/packages/pandas-gbq/pandas_gbq/core/pandas.py index 3f264984c871..8ba22dd879ca 100644 --- a/packages/pandas-gbq/pandas_gbq/core/pandas.py +++ b/packages/pandas-gbq/pandas_gbq/core/pandas.py @@ -5,6 +5,7 @@ import itertools import pandas +import typing def list_columns_and_indexes(dataframe, index=True): @@ -22,8 +23,7 @@ def list_columns_and_indexes(dataframe, index=True): if isinstance(dataframe.index, pandas.MultiIndex): for name in dataframe.index.names: if name and name not in column_names: - assert isinstance(name, (str, int)) - values = dataframe.index.get_level_values(name) + values = dataframe.index.get_level_values(typing.cast(typing.Union[str, int], name)) columns_and_indexes.append((name, values.dtype)) else: if dataframe.index.name and dataframe.index.name not in column_names: diff --git a/packages/pandas-gbq/pandas_gbq/gbq_connector.py b/packages/pandas-gbq/pandas_gbq/gbq_connector.py index 6f25fbe1e4c4..68d5aaad5aa7 100644 --- a/packages/pandas-gbq/pandas_gbq/gbq_connector.py +++ b/packages/pandas-gbq/pandas_gbq/gbq_connector.py @@ -205,7 +205,8 @@ def run_query( ].get("timeoutMs") if timeout_ms: - assert isinstance(timeout_ms, (str, int, float)) + if not isinstance(timeout_ms, (str, int, float)): + raise TypeError(f"Expected str, int or float, got {type(timeout_ms)}") timeout_ms = int(timeout_ms) # Having too small a timeout_ms results in individual # API calls timing out before they can finish. @@ -222,7 +223,7 @@ def run_query( self._start_timer() job_config = bigquery.QueryJobConfig.from_api_repr(job_config_dict) - assert isinstance(job_config, bigquery.QueryJobConfig) + job_config = typing.cast(bigquery.QueryJobConfig, job_config) job_config.dry_run = dry_run if FEATURES.bigquery_has_query_and_wait: From 1a3912da52e2f1ad49a19ab1e89c7c7d0edc8cf1 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Tue, 12 May 2026 08:34:35 -0400 Subject: [PATCH 7/8] style(pandas-gbq): format files with black Formatted pandas.py and load/core.py with Black to resolve linting failures. --- packages/pandas-gbq/pandas_gbq/core/pandas.py | 4 +++- packages/pandas-gbq/pandas_gbq/load/core.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pandas-gbq/pandas_gbq/core/pandas.py b/packages/pandas-gbq/pandas_gbq/core/pandas.py index 8ba22dd879ca..aceaa29b5b91 100644 --- a/packages/pandas-gbq/pandas_gbq/core/pandas.py +++ b/packages/pandas-gbq/pandas_gbq/core/pandas.py @@ -23,7 +23,9 @@ def list_columns_and_indexes(dataframe, index=True): if isinstance(dataframe.index, pandas.MultiIndex): for name in dataframe.index.names: if name and name not in column_names: - values = dataframe.index.get_level_values(typing.cast(typing.Union[str, int], name)) + values = dataframe.index.get_level_values( + typing.cast(typing.Union[str, int], name) + ) columns_and_indexes.append((name, values.dtype)) else: if dataframe.index.name and dataframe.index.name not in column_names: diff --git a/packages/pandas-gbq/pandas_gbq/load/core.py b/packages/pandas-gbq/pandas_gbq/load/core.py index 55845d02e5b1..429867dad841 100644 --- a/packages/pandas-gbq/pandas_gbq/load/core.py +++ b/packages/pandas-gbq/pandas_gbq/load/core.py @@ -281,7 +281,9 @@ def load_chunk(chunk, job_config): finally: chunk_buffer.close() - return load_csv(dataframe, write_disposition, chunksize, list(bq_schema), load_chunk) + return load_csv( + dataframe, write_disposition, chunksize, list(bq_schema), load_chunk + ) def load_chunks( From 96c7645f3e836f15a616d14c71b5887d2b3db33f Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Tue, 12 May 2026 13:21:05 -0400 Subject: [PATCH 8/8] chore: adds additional comments referencing a follow-on feature request to add typehints to google dependencies --- packages/pandas-gbq/pandas_gbq/auth.py | 4 ++++ packages/pandas-gbq/pandas_gbq/core/read.py | 4 ++++ packages/pandas-gbq/pandas_gbq/gbq.py | 6 ++++++ packages/pandas-gbq/pandas_gbq/load/core.py | 2 ++ packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py | 2 ++ 5 files changed, 18 insertions(+) diff --git a/packages/pandas-gbq/pandas_gbq/auth.py b/packages/pandas-gbq/pandas_gbq/auth.py index 1d4de91264ff..d5bc8e396e97 100644 --- a/packages/pandas-gbq/pandas_gbq/auth.py +++ b/packages/pandas-gbq/pandas_gbq/auth.py @@ -24,6 +24,8 @@ def get_credentials( client_secret=None, ): # pydata-google-auth does not have type hints nor stubs that mypy uses for type checking. + # Remove this comment and the ignore pragma upon completing: + # https://github.com/googleapis/google-cloud-python/issues/17045 import pydata_google_auth # type: ignore[import-untyped] if private_key: @@ -50,6 +52,8 @@ def get_credentials( def get_credentials_cache(reauth): # pydata-google-auth does not have type hints nor stubs that mypy uses for type checking. + # Remove this comment and the ignore pragma upon completing: + # https://github.com/googleapis/google-cloud-python/issues/17045 import pydata_google_auth.cache # type: ignore[import-untyped] if reauth: diff --git a/packages/pandas-gbq/pandas_gbq/core/read.py b/packages/pandas-gbq/pandas_gbq/core/read.py index f4e28e7079ef..a4695f21c821 100644 --- a/packages/pandas-gbq/pandas_gbq/core/read.py +++ b/packages/pandas-gbq/pandas_gbq/core/read.py @@ -35,6 +35,8 @@ def _bqschema_to_nullsafe_dtypes(schema_fields): #missing-data-casting-rules-and-indexing """ # db-dtypes does not have type hints nor stubs that mypy uses for type checking. + # Remove this comment and the ignore pragma upon completing: + # https://github.com/googleapis/google-cloud-python/issues/17045 import db_dtypes # type: ignore[import-untyped] # If you update this mapping, also update the table at @@ -81,6 +83,8 @@ def _finalize_dtypes( https://github.com/googleapis/python-bigquery-pandas/issues/365 """ # db-dtypes does not have type hints nor stubs that mypy uses for type checking. + # Remove this comment and the ignore pragma upon completing: + # https://github.com/googleapis/google-cloud-python/issues/17045 import db_dtypes # type: ignore[import-untyped] import pandas.api.types diff --git a/packages/pandas-gbq/pandas_gbq/gbq.py b/packages/pandas-gbq/pandas_gbq/gbq.py index ef0989fc348b..ec6a7e9308aa 100644 --- a/packages/pandas-gbq/pandas_gbq/gbq.py +++ b/packages/pandas-gbq/pandas_gbq/gbq.py @@ -46,6 +46,8 @@ def _test_google_api_imports(): try: # db-dtypes does not have type hints nor stubs that mypy uses for type checking. # This import is solely to test if the package is installed, so we ignore the "unused import" warning. + # Remove this comment and the ignore pragma upon completing: + # https://github.com/googleapis/google-cloud-python/issues/17045 import db_dtypes # type: ignore[import-untyped] # noqa: F401 except ImportError as ex: # pragma: NO COVER raise ImportError("pandas-gbq requires db-dtypes") from ex @@ -53,6 +55,8 @@ def _test_google_api_imports(): try: # pydata-google-auth does not have type hints nor stubs that mypy uses for type checking. # This import is solely to test if the package is installed, so we ignore the "unused import" warning. + # Remove this comment and the ignore pragma upon completing: + # https://github.com/googleapis/google-cloud-python/issues/17045 import pydata_google_auth # type: ignore[import-untyped] # noqa: F401 except ImportError as ex: # pragma: NO COVER raise ImportError("pandas-gbq requires pydata-google-auth") from ex @@ -60,6 +64,8 @@ def _test_google_api_imports(): try: # google-auth-oauthlib does not have type hints nor stubs that mypy uses for type checking. # This import is solely to test if the package is installed, so we ignore the "unused import" warning. + # Remove this comment and the ignore pragma upon completing: + # https://github.com/googleapis/google-cloud-python/issues/17045 from google_auth_oauthlib.flow import InstalledAppFlow # type: ignore[import-untyped] # noqa: F401 except ImportError as ex: # pragma: NO COVER raise ImportError("pandas-gbq requires google-auth-oauthlib") from ex diff --git a/packages/pandas-gbq/pandas_gbq/load/core.py b/packages/pandas-gbq/pandas_gbq/load/core.py index 429867dad841..e6cb1e40f2d8 100644 --- a/packages/pandas-gbq/pandas_gbq/load/core.py +++ b/packages/pandas-gbq/pandas_gbq/load/core.py @@ -9,6 +9,8 @@ from typing import Any, Callable, Dict, List, Optional # db-dtypes does not have type hints nor stubs that mypy uses for type checking. +# Remove this comment and the ignore pragma upon completing: +# https://github.com/googleapis/google-cloud-python/issues/17045 import db_dtypes # type: ignore[import-untyped] import pandas import pyarrow.lib diff --git a/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py b/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py index ed5e680322ce..6ea8fc5b512a 100644 --- a/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py +++ b/packages/pandas-gbq/pandas_gbq/schema/pandas_to_bigquery.py @@ -8,6 +8,8 @@ from typing import Any, Optional, Tuple # db-dtypes does not have type hints nor stubs that mypy uses for type checking. +# Remove this comment and the ignore pragma upon completing: +# https://github.com/googleapis/google-cloud-python/issues/17045 import db_dtypes # type: ignore[import-untyped] import pandas import pyarrow