From 0255797246f9f62b2a9387bb1c2de86abd7467b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Swe=C3=B1a?= Date: Thu, 12 Nov 2026 17:21:47 +0000 Subject: [PATCH] fix(sqlalchemy-bigquery): drop support for end-of-life Python 3.10 --- packages/sqlalchemy-bigquery/CONTRIBUTING.rst | 10 +++--- packages/sqlalchemy-bigquery/README.rst | 4 +-- packages/sqlalchemy-bigquery/docs/README.rst | 4 +-- packages/sqlalchemy-bigquery/noxfile.py | 32 ++++++++++-------- packages/sqlalchemy-bigquery/setup.py | 11 +++---- .../sqlalchemy_bigquery/__init__.py | 16 --------- .../sqlalchemy_bigquery/base.py | 9 +---- .../testing/constraints-3.10.txt | 15 --------- .../testing/constraints-3.11.txt | 16 ++++++++- .../tests/system/test__struct.py | 6 ---- .../tests/system/test_sqlalchemy_bigquery.py | 33 ++----------------- 11 files changed, 51 insertions(+), 105 deletions(-) delete mode 100644 packages/sqlalchemy-bigquery/testing/constraints-3.10.txt diff --git a/packages/sqlalchemy-bigquery/CONTRIBUTING.rst b/packages/sqlalchemy-bigquery/CONTRIBUTING.rst index 7db9f22985ff..91ae8e6a6b58 100644 --- a/packages/sqlalchemy-bigquery/CONTRIBUTING.rst +++ b/packages/sqlalchemy-bigquery/CONTRIBUTING.rst @@ -22,7 +22,7 @@ In order to add a feature: documentation. - The feature must work fully on the following CPython versions: - 3.10, 3.11, 3.12, 3.13 and 3.14 on both UNIX and Windows. + 3.11, 3.12, 3.13, 3.14 and 3.15 on both UNIX and Windows. - The feature must not add unnecessary dependencies (where "unnecessary" is of course subjective, but new dependencies should @@ -195,11 +195,11 @@ configure them just like the System Tests. # Run all tests in a folder $ cd samples/snippets - $ nox -s py-3.10 + $ nox -s py-3.14 # Run a single sample test $ cd samples/snippets - $ nox -s py-3.10 -- -k + $ nox -s py-3.14 -- -k ******************************************** Note About ``README`` as it pertains to PyPI @@ -221,17 +221,17 @@ Supported Python Versions We support: -- `Python 3.10`_ - `Python 3.11`_ - `Python 3.12`_ - `Python 3.13`_ - `Python 3.14`_ +- `Python 3.15`_ -.. _Python 3.10: https://docs.python.org/3.10/ .. _Python 3.11: https://docs.python.org/3.11/ .. _Python 3.12: https://docs.python.org/3.12/ .. _Python 3.13: https://docs.python.org/3.13/ .. _Python 3.14: https://docs.python.org/3.14/ +.. _Python 3.15: https://docs.python.org/3.15/ Supported versions can be found in our ``noxfile.py`` `config`_. diff --git a/packages/sqlalchemy-bigquery/README.rst b/packages/sqlalchemy-bigquery/README.rst index 84f456d4b5be..781b71867deb 100644 --- a/packages/sqlalchemy-bigquery/README.rst +++ b/packages/sqlalchemy-bigquery/README.rst @@ -51,11 +51,11 @@ dependencies. Supported Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^ -Python >= 3.10, <= 3.14 +Python >= 3.11, <= 3.15 Unsupported Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Python <= 3.9 +Python <= 3.10 Mac/Linux diff --git a/packages/sqlalchemy-bigquery/docs/README.rst b/packages/sqlalchemy-bigquery/docs/README.rst index 84f456d4b5be..781b71867deb 100644 --- a/packages/sqlalchemy-bigquery/docs/README.rst +++ b/packages/sqlalchemy-bigquery/docs/README.rst @@ -51,11 +51,11 @@ dependencies. Supported Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^ -Python >= 3.10, <= 3.14 +Python >= 3.11, <= 3.15 Unsupported Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Python <= 3.9 +Python <= 3.10 Mac/Linux diff --git a/packages/sqlalchemy-bigquery/noxfile.py b/packages/sqlalchemy-bigquery/noxfile.py index ccc59a494b32..c94c62c97b94 100644 --- a/packages/sqlalchemy-bigquery/noxfile.py +++ b/packages/sqlalchemy-bigquery/noxfile.py @@ -44,7 +44,7 @@ DEFAULT_PYTHON_VERSION = "3.14" -UNIT_TEST_PYTHON_VERSIONS: List[str] = [ +ALL_PYTHON: List[str] = [ "3.10", "3.11", "3.12", @@ -52,7 +52,13 @@ "3.14", "3.15", ] -ALL_PYTHON = list(UNIT_TEST_PYTHON_VERSIONS) +UNIT_TEST_PYTHON_VERSIONS: List[str] = [ + "3.11", + "3.12", + "3.13", + "3.14", + "3.15", +] UNIT_TEST_STANDARD_DEPENDENCIES = [ "mock", "asyncmock", @@ -94,7 +100,7 @@ ], } -SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ALL_PYTHON +SYSTEM_TEST_PYTHON_VERSIONS: List[str] = UNIT_TEST_PYTHON_VERSIONS SYSTEM_TEST_STANDARD_DEPENDENCIES: List[str] = [ "mock", "pytest", @@ -302,16 +308,16 @@ def install_unittest_dependencies(session, *constraints): @_calculate_duration def unit(session, protobuf_implementation, install_extras=True): # Install all test dependencies, then install this package in-place. + if session.python not in UNIT_TEST_PYTHON_VERSIONS: + session.skip(f"Python {session.python} is not supported.") constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) install_unittest_dependencies(session, "-c", constraints_path) - if install_extras and session.python in ["3.11", "3.12", "3.13", "3.14", "3.15"]: + if install_extras: install_target = ".[geography,alembic,tests,bqstorage]" - elif install_extras: - install_target = ".[all]" else: install_target = "." session.install("-e", install_target, "-c", constraints_path) @@ -372,6 +378,9 @@ def install_systemtest_dependencies(session, use_extras=True, *constraints): def _run_system_test_logic(session, test_type): """Shared logic for system, system_noextras, and compliance tests.""" + if session.python not in SYSTEM_TEST_PYTHON_VERSIONS: + session.skip(f"Python {session.python} is not supported.") + constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) @@ -466,7 +475,7 @@ def system(session, test_type): _run_system_test_logic(session, test_type) -@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS, venv_backend=VENV_BACKEND) +@nox.session(python=ALL_PYTHON, venv_backend=VENV_BACKEND) @_calculate_duration def system_noextras(session): """Run the system test suite without extras.""" @@ -494,7 +503,7 @@ def cover(session): session.run("coverage", "erase") -@nox.session(python="3.10", venv_backend=VENV_BACKEND) +@nox.session(python="3.11") @_calculate_duration def docs(session): """Build the docs for this library.""" @@ -532,7 +541,7 @@ def docs(session): ) -@nox.session(python="3.10", venv_backend=VENV_BACKEND) +@nox.session(python="3.11") @_calculate_duration def docfx(session): """Build the docfx yaml files for this library.""" @@ -721,10 +730,7 @@ def core_deps_from_source(session, protobuf_implementation): install_unittest_dependencies(session, "-c", constraints_path) # Mimic unit session install target - if session.python in ["3.11", "3.12", "3.13", "3.14", "3.15"]: - install_target = ".[geography,alembic,tests,bqstorage]" - else: - install_target = ".[all]" + install_target = ".[geography,alembic,tests,bqstorage]" session.install("-e", install_target, "-c", constraints_path) core_dependencies_from_source = [ diff --git a/packages/sqlalchemy-bigquery/setup.py b/packages/sqlalchemy-bigquery/setup.py index 7ce867b7e264..a6ea1ac6e57c 100644 --- a/packages/sqlalchemy-bigquery/setup.py +++ b/packages/sqlalchemy-bigquery/setup.py @@ -49,7 +49,7 @@ def readme(): extras = { "geography": ["GeoAlchemy2", "shapely"], - "alembic": ["alembic"], + "alembic": ["alembic >= 1.7.7"], "tests": ["packaging", "pytz"], # Keep the no-op bqstorage extra for backward compatibility. # See: https://github.com/googleapis/python-bigquery/issues/757 @@ -63,7 +63,7 @@ def readme(): # https://github.com/grpc/grpc/pull/15254 "grpcio >= 1.59.0, < 2.0.0", "grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'", - "pyarrow >= 6.0.0", + "pyarrow >= 10.0.1", ], } @@ -100,7 +100,6 @@ def readme(): "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", @@ -113,12 +112,12 @@ def readme(): install_requires=[ "google-api-core >= 2.25.0, <3.0.0", "google-auth>=2.14.1,<3.0.0", - "google-cloud-bigquery>=3.20.0,<4.0.0", + "google-cloud-bigquery>=3.20.1,<4.0.0", "packaging", - "sqlalchemy>=1.4.16,<3.0.0", + "sqlalchemy>=1.4.41,<3.0.0", ], extras_require=extras, - python_requires=">=3.10", + python_requires=">=3.11", tests_require=["packaging", "pytz"], entry_points={ "sqlalchemy.dialects": ["bigquery = sqlalchemy_bigquery:BigQueryDialect"] diff --git a/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/__init__.py b/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/__init__.py index 97c9476699d8..131726317592 100644 --- a/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/__init__.py +++ b/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/__init__.py @@ -20,9 +20,6 @@ SQLAlchemy dialect for Google BigQuery """ -import sys -import warnings - from ._types import ( ARRAY, BIGNUMERIC, @@ -45,19 +42,6 @@ from .base import BigQueryDialect, SafeCast, dialect, safe_cast from .version import __version__ -# Now that support for Python 3.7, 3.8 and 3.9 has been removed, we don't expect the -# following check to succeed. The warning is only included for robustness. -if sys.version_info < (3, 10): # pragma: NO COVER - warnings.warn( - "The python-bigquery-sqlalchemy library no longer supports Python 3.7, " - "3.8 and 3.9. " - f"Your Python version is {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}. We " - "recommend that you update soon to ensure ongoing support. For " - "more details, see: [Google Cloud Client Libraries Supported Python Versions policy](https://cloud.google.com/python/docs/supported-python-versions)", - FutureWarning, - ) - - __all__ = [ "__version__", "dialect", diff --git a/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/base.py b/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/base.py index 40b56bfbb715..317b6eea105d 100644 --- a/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/base.py +++ b/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/base.py @@ -28,7 +28,6 @@ from typing import Any import google.api_core.exceptions -import packaging.version import sqlalchemy import sqlalchemy.sql.expression import sqlalchemy.sql.functions @@ -368,14 +367,10 @@ def group_by_clause(self, select, **kw): # no way to tell sqlalchemy that, so it works harder than # necessary and makes us do the same. - __sqlalchemy_version_info = packaging.version.parse(sqlalchemy.__version__) - __expanding_text = "POSTCOMPILE" # https://github.com/sqlalchemy/sqlalchemy/commit/f79df12bd6d99b8f6f09d4bf07722638c4b4c159 - __expanding_conflict = ( - "" if __sqlalchemy_version_info < packaging.version.parse("1.4.27") else "__" - ) + __expanding_conflict = "__" __in_expanding_bind = _helpers.substitute_string_re_method( rf""" @@ -553,8 +548,6 @@ def visit_bindparam( if bindparam.expanding: # pragma: NO COVER assert_(self.__expanded_param(param), f"Unexpected param: {param}") - if self.__sqlalchemy_version_info < packaging.version.parse("1.4.27"): - param = param.replace(")", f":{bq_type})") else: m = self.__placeholder(param) diff --git a/packages/sqlalchemy-bigquery/testing/constraints-3.10.txt b/packages/sqlalchemy-bigquery/testing/constraints-3.10.txt deleted file mode 100644 index d9be58463170..000000000000 --- a/packages/sqlalchemy-bigquery/testing/constraints-3.10.txt +++ /dev/null @@ -1,15 +0,0 @@ -# This constraints file is used to check that lower bounds -# are correct in setup.py -# List *all* library dependencies and extras in this file. -# Pin the version to the lower bound. -# -# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", -sqlalchemy==1.4.16 -google-auth==2.14.1 -google-cloud-bigquery==3.20.0 -google-cloud-bigquery-storage==2.29.0 -google-api-core==2.25.0 -grpcio==1.59.0 -numpy==1.26.4 -alembic==1.7.7 -pyarrow==6.0.0 diff --git a/packages/sqlalchemy-bigquery/testing/constraints-3.11.txt b/packages/sqlalchemy-bigquery/testing/constraints-3.11.txt index 5b82150f19e2..dfa211651db3 100644 --- a/packages/sqlalchemy-bigquery/testing/constraints-3.11.txt +++ b/packages/sqlalchemy-bigquery/testing/constraints-3.11.txt @@ -1 +1,15 @@ -numpy>=1.23 +# This constraints file is used to check that lower bounds +# are correct in setup.py +# List *all* library dependencies and extras in this file. +# Pin the version to the lower bound. +# +# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", +sqlalchemy==1.4.41 +google-auth==2.14.1 +google-cloud-bigquery==3.20.1 +google-cloud-bigquery-storage==2.29.0 +google-api-core==2.25.0 +grpcio==1.59.0 +numpy==1.26.4 +alembic==1.7.7 +pyarrow==10.0.1 diff --git a/packages/sqlalchemy-bigquery/tests/system/test__struct.py b/packages/sqlalchemy-bigquery/tests/system/test__struct.py index 1fd9fc80fd12..fc36e0948f1f 100644 --- a/packages/sqlalchemy-bigquery/tests/system/test__struct.py +++ b/packages/sqlalchemy-bigquery/tests/system/test__struct.py @@ -19,8 +19,6 @@ import datetime -import packaging.version -import pytest import sqlalchemy import sqlalchemy_bigquery @@ -127,10 +125,6 @@ def test_complex_literals_pr_67(engine, bigquery_dataset, metadata): # test_unnest_and_struct_access_233 below :) -@pytest.mark.skipif( - packaging.version.parse(sqlalchemy.__version__) < packaging.version.parse("1.4"), - reason="unnest (and other table-valued-function) support required version 1.4", -) def test_unnest_and_struct_access_233(engine, bigquery_dataset, metadata): # https://github.com/googleapis/python-bigquery-sqlalchemy/issues/233 diff --git a/packages/sqlalchemy-bigquery/tests/system/test_sqlalchemy_bigquery.py b/packages/sqlalchemy-bigquery/tests/system/test_sqlalchemy_bigquery.py index 6947e61707ae..090c42673d9f 100644 --- a/packages/sqlalchemy-bigquery/tests/system/test_sqlalchemy_bigquery.py +++ b/packages/sqlalchemy-bigquery/tests/system/test_sqlalchemy_bigquery.py @@ -21,7 +21,6 @@ import datetime import decimal -import packaging.version import pytest import sqlalchemy from google.cloud.bigquery import TimePartitioning @@ -45,8 +44,6 @@ import sqlalchemy_bigquery -SQLALCHEMY_VERSION = packaging.version.parse(sqlalchemy.__version__) - ONE_ROW_CONTENTS = [ 588, datetime.datetime(2013, 10, 10, 11, 27, 16, tzinfo=datetime.timezone.utc), @@ -487,10 +484,7 @@ def test_compiled_query_literal_binds( q = query(table) compiled = q.compile(engine, compile_kwargs={"literal_binds": True}) with engine.connect() as conn: - if hasattr(conn, "exec_driver_sql"): - result = conn.exec_driver_sql(str(compiled)).fetchall() - else: - result = conn.execute(compiled).fetchall() + result = conn.exec_driver_sql(str(compiled)).fetchall() assert len(result) > 0 q = query(table_using_test_dataset) @@ -498,10 +492,7 @@ def test_compiled_query_literal_binds( engine_using_test_dataset, compile_kwargs={"literal_binds": True} ) with engine_using_test_dataset.connect() as conn: - if hasattr(conn, "exec_driver_sql"): - result = conn.exec_driver_sql(str(compiled)).fetchall() - else: - result = conn.execute(compiled).fetchall() + result = conn.exec_driver_sql(str(compiled)).fetchall() assert len(result) > 0 @@ -803,10 +794,6 @@ class MyTable(Base): assert sorted(db.query(sqlalchemy.distinct(MyTable.my_column)).all()) == expected -@pytest.mark.skipif( - packaging.version.parse(sqlalchemy.__version__) < packaging.version.parse("1.4"), - reason="requires sqlalchemy 1.4 or higher", -) def test_huge_in(): engine = sqlalchemy.create_engine("bigquery://") conn = engine.connect() @@ -824,10 +811,6 @@ def test_huge_in(): assert not error, "execution failed" -@pytest.mark.skipif( - packaging.version.parse(sqlalchemy.__version__) < packaging.version.parse("1.4"), - reason="unnest (and other table-valued-function) support required version 1.4", -) def test_unnest(engine, bigquery_dataset): from sqlalchemy import String, func, select @@ -855,10 +838,6 @@ def test_unnest(engine, bigquery_dataset): assert sorted(r[0] for r in conn.execute(query)) == ["a", "b", "c", "x", "y"] -@pytest.mark.skipif( - packaging.version.parse(sqlalchemy.__version__) < packaging.version.parse("1.4"), - reason="unnest (and other table-valued-function) support required version 1.4", -) def test_unnest_with_cte(engine, bigquery_dataset): from sqlalchemy import String, func, select @@ -902,10 +881,6 @@ def test_unnest_with_cte(engine, bigquery_dataset): ] -@pytest.mark.skipif( - packaging.version.parse(sqlalchemy.__version__) < packaging.version.parse("1.4"), - reason="regexp_match support requires version 1.4 or higher", -) def test_regexp_match(session, table): results = ( session.query(table.c.string) @@ -917,10 +892,6 @@ def test_regexp_match(session, table): assert len(results) == number_of_52st_records -@pytest.mark.skipif( - packaging.version.parse(sqlalchemy.__version__) < packaging.version.parse("1.4"), - reason="regexp_match support requires version 1.4 or higher", -) def test_not_regexp_match(session, table): results = ( session.query(table.c.string)