From d2ccdf69559fa28b0447dde2cbb16df690d81fe3 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 06:20:20 -0400 Subject: [PATCH 01/21] ci: remove Python 3.7 from run_single_test.sh --- ci/run_single_test.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ci/run_single_test.sh b/ci/run_single_test.sh index f7d6a53e7edd..a01d92e55b15 100755 --- a/ci/run_single_test.sh +++ b/ci/run_single_test.sh @@ -16,7 +16,7 @@ # This script requires the following environment variables to be set: # `TEST_TYPE` should be one of ["lint", "lint_setup_py", "docs", "docfx", "prerelease"] -# `PY_VERSION` should be one of ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] +# `PY_VERSION` should be one of ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] # This script is called by the `ci/run_conditional_tests.sh` script. # A specific `nox` session will be run, depending on the value of @@ -70,10 +70,6 @@ case ${TEST_TYPE} in ;; unit) case ${PY_VERSION} in - "3.7") - nox -s unit-3.7 - retval=$? - ;; "3.8") nox -s unit-3.8 retval=$? From 09ad9e1a799663480a6a6bc3e6b7469d42457937 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 06:20:42 -0400 Subject: [PATCH 02/21] chore(templates): update python version in mypy.ini.j2 to 3.8 --- packages/gapic-generator/gapic/ads-templates/mypy.ini.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gapic-generator/gapic/ads-templates/mypy.ini.j2 b/packages/gapic-generator/gapic/ads-templates/mypy.ini.j2 index 574c5aed394b..beaa679a8d2b 100644 --- a/packages/gapic-generator/gapic/ads-templates/mypy.ini.j2 +++ b/packages/gapic-generator/gapic/ads-templates/mypy.ini.j2 @@ -1,3 +1,3 @@ [mypy] -python_version = 3.7 +python_version = 3.8 namespace_packages = True From ea3801ee1ff5268995ecfb4ee16ac75b1a08776f Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 06:21:39 -0400 Subject: [PATCH 03/21] docs(gapic-generator): update Python version in index.rst to 3.8+ --- packages/gapic-generator/docs/getting-started/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gapic-generator/docs/getting-started/index.rst b/packages/gapic-generator/docs/getting-started/index.rst index 6f288f6bb5c2..1215c6867ebf 100644 --- a/packages/gapic-generator/docs/getting-started/index.rst +++ b/packages/gapic-generator/docs/getting-started/index.rst @@ -2,7 +2,7 @@ Getting Started --------------- This code generator is implemented as a plugin to ``protoc``, the compiler -for `protocol buffers`_, and will run in any environment that Python 3.7+ and +for `protocol buffers`_, and will run in any environment that Python 3.8+ and protocol buffers do. It is recommended to install the tool locally and run it through ``protoc``. From e57206dd29be351f41d6e4af6d05af2e68c030de Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 06:22:03 -0400 Subject: [PATCH 04/21] docs(gapic-generator): update Python version in bazel.rst to 3.8+ --- packages/gapic-generator/docs/getting-started/bazel.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gapic-generator/docs/getting-started/bazel.rst b/packages/gapic-generator/docs/getting-started/bazel.rst index 21af6d43fb42..3d8cea77bde6 100644 --- a/packages/gapic-generator/docs/getting-started/bazel.rst +++ b/packages/gapic-generator/docs/getting-started/bazel.rst @@ -29,7 +29,7 @@ install it is simply downloading the binary and making it executable: Python and Dependencies ~~~~~~~~~~~~~~~~~~~~~~~ Bazel build is mostly hermetic, with a few exceptions for Python generator. -Specifically it expects Python 3.7+ with the python dev packages to be installed. +Specifically it expects Python 3.8+ with the python dev packages to be installed. On Linux, to install those, simply run: From 8279e27e3c57bedc4c898474202b0398a94f6c29 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 06:22:22 -0400 Subject: [PATCH 05/21] docs(gapic-generator): update Python version in _verifying.rst to 3.8 --- packages/gapic-generator/docs/getting-started/_verifying.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gapic-generator/docs/getting-started/_verifying.rst b/packages/gapic-generator/docs/getting-started/_verifying.rst index 325090ed1aa3..5cc7e26a2d44 100644 --- a/packages/gapic-generator/docs/getting-started/_verifying.rst +++ b/packages/gapic-generator/docs/getting-started/_verifying.rst @@ -8,7 +8,7 @@ Create a virtual environment for the library: .. code-block:: shell - $ virtualenv ~/.local/client-lib --python=`which python3.7` + $ virtualenv ~/.local/client-lib --python=`which python3.8` $ source ~/.local/client-lib/bin/activate Next, install the library: From f377a01c54fbd124f5573c4c0657f46e622d0c32 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 06:36:08 -0400 Subject: [PATCH 06/21] chore(templates): update Python version requirement and clean up __init__.py.j2 --- .../gapic/ads-templates/%namespace/%name/__init__.py.j2 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/gapic-generator/gapic/ads-templates/%namespace/%name/__init__.py.j2 b/packages/gapic-generator/gapic/ads-templates/%namespace/%name/__init__.py.j2 index abdafe27172a..eca52e4449b4 100644 --- a/packages/gapic-generator/gapic/ads-templates/%namespace/%name/__init__.py.j2 +++ b/packages/gapic-generator/gapic/ads-templates/%namespace/%name/__init__.py.j2 @@ -6,8 +6,8 @@ import importlib import sys -if sys.version_info < (3, 7): - raise ImportError('This module requires Python 3.7 or later.') +if sys.version_info < (3, 8): + raise ImportError('This module requires Python 3.8 or later.') _lazy_type_to_package_map = { @@ -30,14 +30,14 @@ _lazy_type_to_package_map = { # Background on how this behaves: https://www.python.org/dev/peps/pep-0562/ -def __getattr__(name): # Requires Python >= 3.7 +def __getattr__(name): if name == '__all__': all_names = globals()['__all__'] = sorted(_lazy_type_to_package_map) return all_names elif name in _lazy_type_to_package_map: module = importlib.import_module(f'{_lazy_type_to_package_map[name]}') klass = getattr(module, name) - {# new_klass = type(name, (klass,), {'__doc__': klass.__doc__}) #} + globals()[name] = klass return klass else: From 042e3ef0320360bfe23ed7acca7218f05578f712 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 06:36:11 -0400 Subject: [PATCH 07/21] chore(templates): update Python version requirement and clean up versioned __init__.py.j2 --- .../%namespace/%name/%version/__init__.py.j2 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/gapic-generator/gapic/ads-templates/%namespace/%name/%version/__init__.py.j2 b/packages/gapic-generator/gapic/ads-templates/%namespace/%name/%version/__init__.py.j2 index 5a8d6d6bacbc..00353bc6b47f 100644 --- a/packages/gapic-generator/gapic/ads-templates/%namespace/%name/%version/__init__.py.j2 +++ b/packages/gapic-generator/gapic/ads-templates/%namespace/%name/%version/__init__.py.j2 @@ -11,8 +11,8 @@ from {{package_path}} import gapic_version as package_version __version__ = package_version.__version__ -if sys.version_info < (3, 7): - raise ImportError('This module requires Python 3.7 or later.') +if sys.version_info < (3, 8): + raise ImportError('This module requires Python 3.8 or later.') _lazy_type_to_package_map = { @@ -37,14 +37,14 @@ _lazy_type_to_package_map = { # Background on how this behaves: https://www.python.org/dev/peps/pep-0562/ -def __getattr__(name): # Requires Python >= 3.7 +def __getattr__(name): if name == '__all__': all_names = globals()['__all__'] = sorted(_lazy_type_to_package_map) return all_names elif name in _lazy_type_to_package_map: module = importlib.import_module(f'{_lazy_type_to_package_map[name]}') klass = getattr(module, name) - {# new_klass = type(name, (klass,), {'__doc__': klass.__doc__}) #} + globals()[name] = klass return klass else: From e75c0f68a8002060b78f551cb52b1a343b41a747 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 07:51:39 -0400 Subject: [PATCH 08/21] chore(gapic-generator): remove outdated comment in options.py --- packages/gapic-generator/gapic/utils/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gapic-generator/gapic/utils/options.py b/packages/gapic-generator/gapic/utils/options.py index 30184f12c0a9..953972c543c0 100644 --- a/packages/gapic-generator/gapic/utils/options.py +++ b/packages/gapic-generator/gapic/utils/options.py @@ -57,7 +57,7 @@ class Options: OPT_FLAGS: FrozenSet[str] = frozenset( ( "add-iam-methods", # microgenerator implementation for `reroute_to_grpc_interface` - "lazy-import", # requires >= 3.7 + "lazy-import", "metadata", # generate GAPIC metadata JSON file "old-naming", # TODO(dovs): Come up with a better comment "retry-config", # takes a path From 0eb3b09d35aac3247e244b6b322e9c0f8c58168b Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 13:16:36 -0400 Subject: [PATCH 09/21] chore(gapic): update Python version requirement to 3.10 in setup.py and noxfile.py --- packages/gapic-generator/noxfile.py | 5 ++--- packages/gapic-generator/setup.py | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/gapic-generator/noxfile.py b/packages/gapic-generator/noxfile.py index d931a5258814..e170072d4696 100644 --- a/packages/gapic-generator/noxfile.py +++ b/packages/gapic-generator/noxfile.py @@ -44,7 +44,6 @@ RUFF_EXCLUDES = "*golden*,*pb2.py,*pb2.pyi" ALL_PYTHON = ( - "3.9", "3.10", "3.11", "3.12", @@ -181,7 +180,7 @@ def fragment(session, use_ads_templates=False): session.install("-e", ".") # The specific failure is `Plugin output is unparseable` - if session.python in ("3.9", "3.10"): + if session.python == "3.10": session.install("google-api-core<2.28") frag_files = ( @@ -251,7 +250,7 @@ def showcase_library( # Warnings emitted from google-api-core starting in 2.28 # appear to cause issues when running protoc. # The specific failure is `Plugin output is unparseable` - if session.python in ("3.9", "3.10"): + if session.python == "3.10": session.install("google-api-core<2.28") # Install a client library for Showcase. diff --git a/packages/gapic-generator/setup.py b/packages/gapic-generator/setup.py index 6bd32bc0caff..95c587d165ae 100644 --- a/packages/gapic-generator/setup.py +++ b/packages/gapic-generator/setup.py @@ -70,7 +70,6 @@ "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -81,7 +80,7 @@ "Topic :: Software Development :: Libraries :: Python Modules", ], platforms="Posix; MacOS X", - python_requires=">=3.9", + python_requires=">=3.10", install_requires=dependencies, include_package_data=True, zip_safe=False, From 830c66f0eb14d5bf7be2a25de7a80b90df56c526 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 13:16:47 -0400 Subject: [PATCH 10/21] chore(gapic): use functools.cache instead of lru_cache in wrappers.py --- packages/gapic-generator/gapic/schema/wrappers.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/gapic-generator/gapic/schema/wrappers.py b/packages/gapic-generator/gapic/schema/wrappers.py index f654d0a82d18..085132b4bf4e 100644 --- a/packages/gapic-generator/gapic/schema/wrappers.py +++ b/packages/gapic-generator/gapic/schema/wrappers.py @@ -1284,19 +1284,13 @@ def _to_regex(self, path_template: str) -> Pattern: return re.compile(f"^{self._convert_to_regex(path_template)}$") # Use caching to avoid repeated computation - # TODO(https://github.com/googleapis/gapic-generator-python/issues/2161): - # Use `@functools.cache` instead of `@functools.lru_cache` once python 3.8 is dropped. - # https://docs.python.org/3/library/functools.html#functools.cache - @functools.lru_cache(maxsize=None) + @functools.cache def to_regex(self) -> Pattern: return self._to_regex(self.path_template) @property # Use caching to avoid repeated computation - # TODO(https://github.com/googleapis/gapic-generator-python/issues/2161): - # Use `@functools.cache` instead of `@functools.lru_cache` once python 3.8 is dropped. - # https://docs.python.org/3/library/functools.html#functools.cache - @functools.lru_cache(maxsize=None) + @functools.cache def key(self) -> Union[str, None]: if self.path_template == "": return self.field From eb0105071fbf225267e0ef0406037455928d5281 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 13:16:52 -0400 Subject: [PATCH 11/21] chore(gapic): update Python version in setup and mypy templates --- packages/gapic-generator/gapic/ads-templates/mypy.ini.j2 | 2 +- packages/gapic-generator/gapic/ads-templates/setup.py.j2 | 3 +-- packages/gapic-generator/gapic/templates/setup.py.j2 | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/gapic-generator/gapic/ads-templates/mypy.ini.j2 b/packages/gapic-generator/gapic/ads-templates/mypy.ini.j2 index beaa679a8d2b..cb397f571128 100644 --- a/packages/gapic-generator/gapic/ads-templates/mypy.ini.j2 +++ b/packages/gapic-generator/gapic/ads-templates/mypy.ini.j2 @@ -1,3 +1,3 @@ [mypy] -python_version = 3.8 +python_version = 3.10 namespace_packages = True diff --git a/packages/gapic-generator/gapic/ads-templates/setup.py.j2 b/packages/gapic-generator/gapic/ads-templates/setup.py.j2 index b0230a674f4b..1684c2de1a61 100644 --- a/packages/gapic-generator/gapic/ads-templates/setup.py.j2 +++ b/packages/gapic-generator/gapic/ads-templates/setup.py.j2 @@ -64,7 +64,6 @@ setuptools.setup( "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -75,7 +74,7 @@ setuptools.setup( ], platforms="Posix; MacOS X; Windows", packages=packages, - python_requires=">=3.9", + python_requires=">=3.10", install_requires=dependencies, include_package_data=True, zip_safe=False, diff --git a/packages/gapic-generator/gapic/templates/setup.py.j2 b/packages/gapic-generator/gapic/templates/setup.py.j2 index 2a725f7a1c5e..b803e1b6df96 100644 --- a/packages/gapic-generator/gapic/templates/setup.py.j2 +++ b/packages/gapic-generator/gapic/templates/setup.py.j2 @@ -89,7 +89,6 @@ setuptools.setup( "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -100,7 +99,7 @@ setuptools.setup( ], platforms="Posix; MacOS X; Windows", packages=packages, - python_requires=">=3.9", + python_requires=">=3.10", install_requires=dependencies, extras_require=extras, include_package_data=True, From 9a6decef3be36cd6a4b9611d2211fda246a24155 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 13:17:02 -0400 Subject: [PATCH 12/21] chore(gapic): clean up Python version guards and imports in templates --- .../%namespace/%name/%version/__init__.py.j2 | 4 ++-- .../%namespace/%name/__init__.py.j2 | 4 ++-- .../%name_%version/%sub/__init__.py.j2 | 19 +++---------------- .../%name_%version/%sub/test_%service.py.j2 | 8 ++------ 4 files changed, 9 insertions(+), 26 deletions(-) diff --git a/packages/gapic-generator/gapic/ads-templates/%namespace/%name/%version/__init__.py.j2 b/packages/gapic-generator/gapic/ads-templates/%namespace/%name/%version/__init__.py.j2 index 00353bc6b47f..8bb1a4d8fc37 100644 --- a/packages/gapic-generator/gapic/ads-templates/%namespace/%name/%version/__init__.py.j2 +++ b/packages/gapic-generator/gapic/ads-templates/%namespace/%name/%version/__init__.py.j2 @@ -11,8 +11,8 @@ from {{package_path}} import gapic_version as package_version __version__ = package_version.__version__ -if sys.version_info < (3, 8): - raise ImportError('This module requires Python 3.8 or later.') +if sys.version_info < (3, 10): + raise ImportError('This module requires Python 3.10 or later.') _lazy_type_to_package_map = { diff --git a/packages/gapic-generator/gapic/ads-templates/%namespace/%name/__init__.py.j2 b/packages/gapic-generator/gapic/ads-templates/%namespace/%name/__init__.py.j2 index eca52e4449b4..6f8f58eb8b4f 100644 --- a/packages/gapic-generator/gapic/ads-templates/%namespace/%name/__init__.py.j2 +++ b/packages/gapic-generator/gapic/ads-templates/%namespace/%name/__init__.py.j2 @@ -6,8 +6,8 @@ import importlib import sys -if sys.version_info < (3, 8): - raise ImportError('This module requires Python 3.8 or later.') +if sys.version_info < (3, 10): + raise ImportError('This module requires Python 3.10 or later.') _lazy_type_to_package_map = { diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/__init__.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/__init__.py.j2 index 9eb968dd011b..8b80bec9281a 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/__init__.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/__init__.py.j2 @@ -10,12 +10,7 @@ import sys __version__ = package_version.__version__ -if sys.version_info >= (3, 8): # pragma: NO COVER - from importlib import metadata -else: # pragma: NO COVER - # TODO(https://github.com/googleapis/python-api-core/issues/835): Remove - # this code path once we drop support for Python 3.7 - import importlib_metadata as metadata +from importlib import metadata {# Import subpackages. -#} {% for subpackage, _ in api.subpackages|dictsort %} @@ -65,20 +60,12 @@ else: # pragma: NO COVER _py_version_str = sys.version.split()[0] _package_label = "{{package_path}}" - if sys.version_info < (3, 9): + if sys.version_info < (3, 10): warnings.warn("You are using a non-supported Python version " + f"({_py_version_str}). Google will not post any further " + f"updates to {_package_label} supporting this Python version. " + "Please upgrade to the latest Python version, or at " + - f"least to Python 3.9, and then update {_package_label}.", - FutureWarning) - if sys.version_info[:2] == (3, 9): - warnings.warn(f"You are using a Python version ({_py_version_str}) " + - f"which Google will stop supporting in {_package_label} in " + - "January 2026. Please " + - "upgrade to the latest Python version, or at " + - "least to Python 3.10, before then, and " + - f"then update {_package_label}.", + f"least to Python 3.10, and then update {_package_label}.", FutureWarning) def parse_version_to_tuple(version_string: str): diff --git a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 index 0a48dd6e1887..cb5f9c4d77df 100644 --- a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 +++ b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 @@ -10,12 +10,8 @@ import os {% if api.all_method_settings.values()|map(attribute="auto_populated_fields", default=[])|list %} import re {% endif %} -# try/except added for compatibility with python < 3.8 -try: - from unittest import mock - from unittest.mock import AsyncMock # pragma: NO COVER -except ImportError: # pragma: NO COVER - import mock +from unittest import mock +from unittest.mock import AsyncMock import grpc from grpc.experimental import aio From 3f6a95ceb4053737d58d51f69413a18646027609 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 06:26:56 -0400 Subject: [PATCH 13/21] ci: update Python version comment in run_conditional_tests.sh --- ci/run_conditional_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/run_conditional_tests.sh b/ci/run_conditional_tests.sh index 35505a059f65..afdb605b960d 100755 --- a/ci/run_conditional_tests.sh +++ b/ci/run_conditional_tests.sh @@ -17,7 +17,7 @@ # `BUILD_TYPE` should be one of ["presubmit", "continuous"] # `TEST_TYPE` should be one of ["docs", "docfx", "prerelease", "unit"] # or match the name of the nox session that you want to run. -# `PY_VERSION` should be one of ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] +# `PY_VERSION` should be one of ["3.8", "3.9", "3.10", "3.11", "3.12"] # `TEST_TYPE` and `PY_VERSION` are required by the script `ci/run_single_test.sh` From 37f8f6a24d966e80db88985e48a6435049177544 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 14:08:12 -0400 Subject: [PATCH 14/21] chore(gapic): drop Python 3.8 and 3.9 from CI shell scripts --- ci/run_conditional_tests.sh | 2 +- ci/run_single_test.sh | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/ci/run_conditional_tests.sh b/ci/run_conditional_tests.sh index afdb605b960d..9b8eaee52e5b 100755 --- a/ci/run_conditional_tests.sh +++ b/ci/run_conditional_tests.sh @@ -17,7 +17,7 @@ # `BUILD_TYPE` should be one of ["presubmit", "continuous"] # `TEST_TYPE` should be one of ["docs", "docfx", "prerelease", "unit"] # or match the name of the nox session that you want to run. -# `PY_VERSION` should be one of ["3.8", "3.9", "3.10", "3.11", "3.12"] +# `PY_VERSION` should be one of ["3.10", "3.11", "3.12"] # `TEST_TYPE` and `PY_VERSION` are required by the script `ci/run_single_test.sh` diff --git a/ci/run_single_test.sh b/ci/run_single_test.sh index a01d92e55b15..376660a58b2a 100755 --- a/ci/run_single_test.sh +++ b/ci/run_single_test.sh @@ -16,7 +16,7 @@ # This script requires the following environment variables to be set: # `TEST_TYPE` should be one of ["lint", "lint_setup_py", "docs", "docfx", "prerelease"] -# `PY_VERSION` should be one of ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] +# `PY_VERSION` should be one of ["3.10", "3.11", "3.12", "3.13"] # This script is called by the `ci/run_conditional_tests.sh` script. # A specific `nox` session will be run, depending on the value of @@ -70,14 +70,6 @@ case ${TEST_TYPE} in ;; unit) case ${PY_VERSION} in - "3.8") - nox -s unit-3.8 - retval=$? - ;; - "3.9") - nox -s unit-3.9 - retval=$? - ;; "3.10") nox -s unit-3.10 retval=$? From 9770d830a695e701d2928d6a32f487153850e741 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 14:08:23 -0400 Subject: [PATCH 15/21] chore(gapic): remove Python 3.9 from generated noxfile templates --- packages/gapic-generator/gapic/ads-templates/noxfile.py.j2 | 1 - packages/gapic-generator/gapic/templates/noxfile.py.j2 | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/gapic-generator/gapic/ads-templates/noxfile.py.j2 b/packages/gapic-generator/gapic/ads-templates/noxfile.py.j2 index 34c35ed0f3b1..0a42cd6e4fa0 100644 --- a/packages/gapic-generator/gapic/ads-templates/noxfile.py.j2 +++ b/packages/gapic-generator/gapic/ads-templates/noxfile.py.j2 @@ -11,7 +11,6 @@ import nox # type: ignore # Add tests for Python 3.15 alpha1 # https://peps.python.org/pep-0790/ ALL_PYTHON = [ - "3.9", "3.10", "3.11", "3.12", diff --git a/packages/gapic-generator/gapic/templates/noxfile.py.j2 b/packages/gapic-generator/gapic/templates/noxfile.py.j2 index c628aad47051..244173b4b42d 100644 --- a/packages/gapic-generator/gapic/templates/noxfile.py.j2 +++ b/packages/gapic-generator/gapic/templates/noxfile.py.j2 @@ -25,7 +25,6 @@ if os.path.isdir("samples"): LINT_PATHS.append("samples") ALL_PYTHON = [ - "3.9", "3.10", "3.11", "3.12", From 8017018a534bebb0aa164a5bb700c751726b423d Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 14:08:31 -0400 Subject: [PATCH 16/21] chore(gapic): update target version in generator and clean up test macros --- .generator/cli.py | 2 +- .generator/test_cli.py | 2 +- .../tests/unit/gapic/%name_%version/%sub/test_macros.j2 | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.generator/cli.py b/.generator/cli.py index 9415e0ca2ea3..0f5078204c44 100644 --- a/.generator/cli.py +++ b/.generator/cli.py @@ -354,7 +354,7 @@ def _run_post_processor(output: str, library_id: str, is_mono_repo: bool): # TODO(https://github.com/googleapis/google-cloud-python/issues/15538): # Investigate if a `target_version needs to be maintained # or can be eliminated. - target_version = "py39" + target_version = "py310" common_args = [ f"--target-version={target_version}", "--line-length=88", diff --git a/.generator/test_cli.py b/.generator/test_cli.py index 68d61343f145..4ccd9a3b1359 100644 --- a/.generator/test_cli.py +++ b/.generator/test_cli.py @@ -741,7 +741,7 @@ def test_run_individual_session_success(mocker, caplog, is_mono_repo): "cli.subprocess.run", return_value=MagicMock(returncode=0) ) - test_session = "unit-3.9" + test_session = "unit-3.10" test_library_id = "test-library" repo = "repo" _run_individual_session(test_session, test_library_id, repo, is_mono_repo) diff --git a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 index 2ab58f5cb881..ff6a15014e10 100644 --- a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 +++ b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 @@ -1005,9 +1005,7 @@ async def test_{{ method_name }}_async_pages(): {% endif %} ) pages = [] - # Workaround issue in python 3.9 related to code coverage by adding `# pragma: no branch` - # See https://github.com/googleapis/gapic-generator-python/pull/1174#issuecomment-1025132372 - async for page_ in ( # pragma: no branch + async for page_ in ( await client.{{ method_name }}(request={}) ).pages: pages.append(page_) From e7a2039f724786c25757c518826aec4c9754fb1d Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 14:14:11 -0400 Subject: [PATCH 17/21] docs(gapic-generator): update Python version to 3.10 in getting-started docs --- packages/gapic-generator/docs/getting-started/_verifying.rst | 2 +- packages/gapic-generator/docs/getting-started/bazel.rst | 2 +- packages/gapic-generator/docs/getting-started/index.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/gapic-generator/docs/getting-started/_verifying.rst b/packages/gapic-generator/docs/getting-started/_verifying.rst index 5cc7e26a2d44..7603047a4c4b 100644 --- a/packages/gapic-generator/docs/getting-started/_verifying.rst +++ b/packages/gapic-generator/docs/getting-started/_verifying.rst @@ -8,7 +8,7 @@ Create a virtual environment for the library: .. code-block:: shell - $ virtualenv ~/.local/client-lib --python=`which python3.8` + $ virtualenv ~/.local/client-lib --python=`which python3.10` $ source ~/.local/client-lib/bin/activate Next, install the library: diff --git a/packages/gapic-generator/docs/getting-started/bazel.rst b/packages/gapic-generator/docs/getting-started/bazel.rst index 3d8cea77bde6..6b6e2702dbf8 100644 --- a/packages/gapic-generator/docs/getting-started/bazel.rst +++ b/packages/gapic-generator/docs/getting-started/bazel.rst @@ -29,7 +29,7 @@ install it is simply downloading the binary and making it executable: Python and Dependencies ~~~~~~~~~~~~~~~~~~~~~~~ Bazel build is mostly hermetic, with a few exceptions for Python generator. -Specifically it expects Python 3.8+ with the python dev packages to be installed. +Specifically it expects Python 3.10+ with the python dev packages to be installed. On Linux, to install those, simply run: diff --git a/packages/gapic-generator/docs/getting-started/index.rst b/packages/gapic-generator/docs/getting-started/index.rst index 1215c6867ebf..a82fddbefa83 100644 --- a/packages/gapic-generator/docs/getting-started/index.rst +++ b/packages/gapic-generator/docs/getting-started/index.rst @@ -2,7 +2,7 @@ Getting Started --------------- This code generator is implemented as a plugin to ``protoc``, the compiler -for `protocol buffers`_, and will run in any environment that Python 3.8+ and +for `protocol buffers`_, and will run in any environment that Python 3.10+ and protocol buffers do. It is recommended to install the tool locally and run it through ``protoc``. From c4951ded9c6352434f812863dcd028f6b8c7a992 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 14:18:44 -0400 Subject: [PATCH 18/21] chore(gapic): clean up imports in ads-templates test template --- .../unit/gapic/%name_%version/%sub/test_%service.py.j2 | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/gapic-generator/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 b/packages/gapic-generator/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 index 39d5fa7d35e7..6ef2d20d14f8 100644 --- a/packages/gapic-generator/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 +++ b/packages/gapic-generator/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 @@ -7,12 +7,8 @@ import os {% if api.all_method_settings.values()|map(attribute="auto_populated_fields", default=[])|list %} import re {% endif %} -# try/except added for compatibility with python < 3.8 -try: - from unittest import mock - from unittest.mock import AsyncMock # pragma: NO COVER -except ImportError: # pragma: NO COVER - import mock +from unittest import mock +from unittest.mock import AsyncMock import grpc from grpc.experimental import aio From 8917093b3cd61ed54374a4e8530de21d859c5159 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 14:28:05 -0400 Subject: [PATCH 19/21] chore(gapic): add ignore pragmas for false positives --- .generator/Dockerfile | 2 +- .pre-commit-config.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.generator/Dockerfile b/.generator/Dockerfile index 2c8a0674bac0..2bfa0e2defdf 100644 --- a/.generator/Dockerfile +++ b/.generator/Dockerfile @@ -66,7 +66,7 @@ RUN unzip protoc-25.3-linux-x86_64.zip -d protoc # Download/extract pandoc # Pandoc is required by gapic-generator-python for parsing documentation -ENV PANDOC_VERSION=3.8.2 +ENV PANDOC_VERSION=3.8.2 # version-scanner: ignore RUN mkdir pandoc-binary RUN wget https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-linux-amd64.tar.gz RUN tar -xvf pandoc-${PANDOC_VERSION}-linux-amd64.tar.gz -C pandoc-binary --strip-components=1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5405cc8ff1f3..427359befa73 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,6 +26,6 @@ repos: hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 3.9.2 + rev: 3.9.2 # version-scanner: ignore hooks: - id: flake8 From 57281c339cf7ca7e404f121f02b8f5c9f34b3502 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 14:33:29 -0400 Subject: [PATCH 20/21] chore(gapic): use ignore-next-line pragma in Dockerfile --- .generator/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.generator/Dockerfile b/.generator/Dockerfile index 2bfa0e2defdf..fc626f4b3481 100644 --- a/.generator/Dockerfile +++ b/.generator/Dockerfile @@ -66,7 +66,8 @@ RUN unzip protoc-25.3-linux-x86_64.zip -d protoc # Download/extract pandoc # Pandoc is required by gapic-generator-python for parsing documentation -ENV PANDOC_VERSION=3.8.2 # version-scanner: ignore +# version-scanner: ignore-next-line +ENV PANDOC_VERSION=3.8.2 RUN mkdir pandoc-binary RUN wget https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-linux-amd64.tar.gz RUN tar -xvf pandoc-${PANDOC_VERSION}-linux-amd64.tar.gz -C pandoc-binary --strip-components=1 From 9ae1839b0fb59027e62592ba3d16987682872495 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 1 May 2026 15:14:27 -0400 Subject: [PATCH 21/21] chore(gapic): remove Python 3.9 templates and update workflow matrix --- .github/workflows/gapic-generator-tests.yml | 2 +- .../testing/constraints-3.9-async-rest.txt.j2 | 24 ------------------- .../templates/testing/constraints-3.9.txt.j2 | 21 ---------------- 3 files changed, 1 insertion(+), 46 deletions(-) delete mode 100644 packages/gapic-generator/gapic/templates/testing/constraints-3.9-async-rest.txt.j2 delete mode 100644 packages/gapic-generator/gapic/templates/testing/constraints-3.9.txt.j2 diff --git a/.github/workflows/gapic-generator-tests.yml b/.github/workflows/gapic-generator-tests.yml index 204e110a0b72..3909f860ab98 100644 --- a/.github/workflows/gapic-generator-tests.yml +++ b/.github/workflows/gapic-generator-tests.yml @@ -18,7 +18,7 @@ env: SHOWCASE_VERSION: 0.35.0 PROTOC_VERSION: 3.20.2 LATEST_STABLE_PYTHON: 3.14 - ALL_PYTHON: "['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']" + ALL_PYTHON: "['3.10', '3.11', '3.12', '3.13', '3.14']" jobs: check_changes: diff --git a/packages/gapic-generator/gapic/templates/testing/constraints-3.9-async-rest.txt.j2 b/packages/gapic-generator/gapic/templates/testing/constraints-3.9-async-rest.txt.j2 deleted file mode 100644 index 3a1222949a04..000000000000 --- a/packages/gapic-generator/gapic/templates/testing/constraints-3.9-async-rest.txt.j2 +++ /dev/null @@ -1,24 +0,0 @@ -{% set rest_async_io_enabled = api.all_library_settings[api.naming.proto_package].python_settings.experimental_features.rest_async_io_enabled %} -{% if rest_async_io_enabled %} -# -*- coding: utf-8 -*- -{% from '_pypi_packages.j2' import pypi_packages %} -# This constraints file is used to check that lower bounds -# are correct in setup.py -# List all library dependencies and extras in this file, -# pinning their versions to their lower bounds. -# For example, if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0", -# then this file should have google-cloud-foo==1.14.0 -google-api-core==2.21.0 -google-auth==2.35.0 -grpcio==1.44.0 -proto-plus==1.22.3 -protobuf==4.25.8 -{% for package_tuple, package_info in pypi_packages.items() %} -{# Quick check to make sure `package_info.package_name` is not the package being generated so we don't circularly include this package in its own constraints file. #} -{% if api.naming.warehouse_package_name != package_info.package_name %} -{% if api.requires_package(package_tuple) %} -{{ package_info.package_name }}=={{ package_info.lower_bound }} -{% endif %} -{% endif %} -{% endfor %} -{% endif %} diff --git a/packages/gapic-generator/gapic/templates/testing/constraints-3.9.txt.j2 b/packages/gapic-generator/gapic/templates/testing/constraints-3.9.txt.j2 deleted file mode 100644 index 95510412186f..000000000000 --- a/packages/gapic-generator/gapic/templates/testing/constraints-3.9.txt.j2 +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -{% from '_pypi_packages.j2' import pypi_packages %} -# This constraints file is used to check that lower bounds -# are correct in setup.py -# List all library dependencies and extras in this file, -# pinning their versions to their lower bounds. -# For example, if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0", -# then this file should have google-cloud-foo==1.14.0 -google-api-core==2.17.1 -google-auth==2.14.1 -grpcio==1.44.0 -proto-plus==1.22.3 -protobuf==4.25.8 -{% for package_tuple, package_info in pypi_packages.items() %} -{# Quick check to make sure `package_info.package_name` is not the package being generated so we don't circularly include this package in its own constraints file. #} -{% if api.naming.warehouse_package_name != package_info.package_name %} -{% if api.requires_package(package_tuple) %} -{{ package_info.package_name }}=={{ package_info.lower_bound }} -{% endif %} -{% endif %} -{% endfor %}