Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c1c1ab1
feat(api-core): update pyproject.toml to require Python 3.10+
chalmerlowe May 7, 2026
d7b5ddc
docs(api-core): update README for Python 3.10+ support
chalmerlowe May 7, 2026
0c736a6
test(api-core): remove Python < 3.10 polyfills and skips
chalmerlowe May 7, 2026
821322b
docs(api-core): update docstring in grpc_helpers_async.py for Python …
chalmerlowe May 7, 2026
87e885e
chore(api-core): remove Python 3.9 from Nox sessions
chalmerlowe May 7, 2026
283a60e
docs(api-core): update CONTRIBUTING.rst examples to use Python 3.10
chalmerlowe May 7, 2026
c62b645
chore(api-core): use match statement in noxfile.py
chalmerlowe May 7, 2026
12c90ca
refactor(api-core): remove Python < 3.10 polyfill in _python_version_…
chalmerlowe May 7, 2026
ea68a46
docs(api-core): update example Python version in client_info.py
chalmerlowe May 7, 2026
a7b1663
refactor(api-core): remove Python 3.9 from tracked versions list
chalmerlowe May 7, 2026
0652eb3
docs(api-core): update example Python version in gapic_v1/client_info.py
chalmerlowe May 7, 2026
2d87e66
test(api-core): update tests to use Python 3.10 instead of 3.9
chalmerlowe May 7, 2026
2ccd85e
chore(api-core): apply black formatting
chalmerlowe May 7, 2026
10181b2
chore(api-core): apply manual updates and add 3.10 constraints files
chalmerlowe May 7, 2026
fcb7965
Apply suggestion from @chalmerlowe
chalmerlowe May 7, 2026
10db3bd
chore: update google-auth and aiohttp dependencies
chalmerlowe May 8, 2026
09950c6
chore: remove unnecessary polyfills for aiter/anext in tests
chalmerlowe May 8, 2026
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
8 changes: 3 additions & 5 deletions packages/google-api-core/CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ In order to add a feature:
documentation.

- The feature must work fully on the following CPython versions:
3.9, 3.10, 3.11, 3.12, 3.13 and 3.14 on both UNIX and Windows.
3.10, 3.11, 3.12, 3.13 and 3.14 on both UNIX and Windows.

- The feature must not add unnecessary dependencies (where
"unnecessary" is of course subjective, but new dependencies should
Expand Down Expand Up @@ -171,11 +171,11 @@ configure them just like the System Tests.

# Run all tests in a folder
$ cd samples/snippets
$ nox -s py-3.8
$ nox -s py-3.10

# Run a single sample test
$ cd samples/snippets
$ nox -s py-3.8 -- -k <name of test>
$ nox -s py-3.10 -- -k <name of test>

********************************************
Note About ``README`` as it pertains to PyPI
Expand All @@ -197,14 +197,12 @@ Supported Python Versions

We support:

- `Python 3.9`_
- `Python 3.10`_
- `Python 3.11`_
- `Python 3.12`_
- `Python 3.13`_
- `Python 3.14`_

.. _Python 3.9: https://docs.python.org/3.9/
.. _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/
Expand Down
8 changes: 5 additions & 3 deletions packages/google-api-core/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ common helpers used by all Google API clients. For more information, see the

Supported Python Versions
-------------------------
Python >= 3.9
Python >= 3.10


Unsupported Python Versions
---------------------------

Python == 2.7, Python == 3.5, Python == 3.6, Python == 3.7, Python == 3.8.
Python <= 3.9

The last version of this library compatible with Python 2.7 and 3.5 is
`google-api-core==1.31.1`.
Expand All @@ -32,3 +31,6 @@ The last version of this library compatible with Python 3.6 is

The last version of this library compatible with Python 3.7 and 3.8 is
`google-api-core==2.29.0`.

The last version of this library compatible with Python 3.9 is
`google-api-core==2.30.3`.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import datetime
import enum
import functools
from importlib import metadata
import logging
import warnings
import sys
Expand Down Expand Up @@ -71,13 +72,6 @@ class VersionInfo(NamedTuple):

PYTHON_VERSIONS: List[VersionInfo] = [
# Refer to https://devguide.python.org/versions/ and the PEPs linked therefrom.
VersionInfo(
version="3.9",
python_beta=datetime.date(2020, 5, 18),
python_start=datetime.date(2020, 10, 5),
python_eol=datetime.date(2025, 10, 5),
gapic_end=datetime.date(2025, 10, 5) + datetime.timedelta(days=90),
),
VersionInfo(
version="3.10",
python_beta=datetime.date(2021, 5, 3),
Expand Down Expand Up @@ -140,38 +134,26 @@ def _flatten_message(text: str) -> str:
return " ".join(textwrap.dedent(text).strip().split())


# TODO(https://github.com/googleapis/python-api-core/issues/835):
# Remove once we no longer support Python 3.9.
# `importlib.metadata.packages_distributions()` is only supported in Python 3.10 and newer
# https://docs.python.org/3/library/importlib.metadata.html#importlib.metadata.packages_distributions
if sys.version_info < (3, 10):

def _get_pypi_package_name(module_name): # pragma: NO COVER
"""Determine the PyPI package name for a given module name."""
return None
@functools.cache
def _cached_packages_distributions():
return metadata.packages_distributions()

else:
from importlib import metadata

@functools.cache
def _cached_packages_distributions():
return metadata.packages_distributions()
def _get_pypi_package_name(module_name):
"""Determine the PyPI package name for a given module name."""
try:
module_to_distributions = _cached_packages_distributions()

def _get_pypi_package_name(module_name):
"""Determine the PyPI package name for a given module name."""
try:
module_to_distributions = _cached_packages_distributions()

if module_name in module_to_distributions: # pragma: NO COVER
return module_to_distributions[module_name][0]
except Exception as e: # pragma: NO COVER
_LOGGER.info(
"An error occurred while determining PyPI package name for %s: %s",
module_name,
e,
)
if module_name in module_to_distributions: # pragma: NO COVER
return module_to_distributions[module_name][0]
except Exception as e: # pragma: NO COVER
_LOGGER.info(
"An error occurred while determining PyPI package name for %s: %s",
module_name,
e,
)

return None
return None


def _get_distribution_and_import_packages(import_package: str) -> Tuple[str, Any]:
Expand Down
2 changes: 1 addition & 1 deletion packages/google-api-core/google/api_core/client_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class ClientInfo(object):
Args:
python_version (str): The Python interpreter version, for example,
``'3.9.6'``.
``'3.10.0'``.
grpc_version (Optional[str]): The gRPC library version.
api_core_version (str): The google-api-core library version.
gapic_version (Optional[str]): The version of gapic-generated client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class ClientInfo(client_info.ClientInfo):
Args:
python_version (str): The Python interpreter version, for example,
``'3.9.6'``.
``'3.10.0'``.
grpc_version (Optional[str]): The gRPC library version.
api_core_version (str): The google-api-core library version.
gapic_version (Optional[str]): The version of gapic-generated client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""AsyncIO helpers for :mod:`grpc` supporting 3.7+.
"""AsyncIO helpers for :mod:`grpc` supporting 3.10+.
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.

nit: Do we need to include the version here?

Suggested change
"""AsyncIO helpers for :mod:`grpc` supporting 3.10+.
"""AsyncIO helpers for :mod:`grpc`.

Please combine more detailed docstring in grpc_helpers.py to use following
functions. This module is implementing the same surface with AsyncIO semantics.
Expand Down
18 changes: 9 additions & 9 deletions packages/google-api-core/noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

# Helpful notes for local usage:
# unset PYENV_VERSION
# pyenv local 3.14.1 3.13.10 3.12.11 3.11.4 3.10.12 3.9.17
# pyenv local 3.14.1 3.13.10 3.12.11 3.11.4 3.10.12
# PIP_INDEX_URL=https://pypi.org/simple nox

from __future__ import absolute_import
Expand All @@ -34,8 +34,8 @@
# Black and flake8 clash on the syntax for ignoring flake8's F401 in this file.
BLACK_EXCLUDES = ["--exclude", "^/google/api_core/operations_v1/__init__.py"]

ALL_PYTHON = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
SUPPORTED_PYTHON_VERSIONS = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
ALL_PYTHON = ["3.10", "3.11", "3.12", "3.13", "3.14"]
SUPPORTED_PYTHON_VERSIONS = ["3.10", "3.11", "3.12", "3.13", "3.14"]

DEFAULT_PYTHON_VERSION = "3.14"
CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute()
Expand Down Expand Up @@ -303,7 +303,7 @@ def default(
(
True,
False,
["3.9", "3.10", "3.11"],
["3.10", "3.11"],
4,
), # Run proto4 tests with grpcio/grpcio-gcp installed
],
Expand All @@ -325,13 +325,13 @@ def unit(
session.log(f"Skipping session for Python {session.python}")
session.skip()

# TODO: consider converting the following into a `match` statement once
# we drop Python 3.9 support.
if legacy_proto:
if legacy_proto == 4:
match legacy_proto:
case 4:
# Pin protobuf to a 4.x version to ensure coverage for the legacy code path.
session.install("protobuf>=4.25.8,<5.0.0")
else:
case None | False:
pass
case _:
assert False, f"Unknown legacy_proto: {legacy_proto}"
Comment thread
chalmerlowe marked this conversation as resolved.

default(
Expand Down
11 changes: 5 additions & 6 deletions packages/google-api-core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ build-backend = "setuptools.build_meta"
name = "google-api-core"
authors = [{ name = "Google LLC", email = "googleapis-packages@google.com" }]
license = { text = "Apache 2.0" }
requires-python = ">=3.9"
requires-python = ">=3.10"
readme = "README.rst"
description = "Google API client core library"
classifiers = [
Expand All @@ -34,7 +34,6 @@ classifiers = [
"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",
Expand All @@ -48,8 +47,8 @@ dependencies = [
"protobuf >= 4.25.8, < 8.0.0",
"proto-plus >= 1.22.3, < 2.0.0",
"proto-plus >= 1.25.0, < 2.0.0; python_version >= '3.13'",
"google-auth >= 2.14.1, < 3.0.0",
"requests >= 2.20.0, < 3.0.0",
"google-auth >= 2.35.0, < 3.0.0",
"requests >= 2.33.0",
]
dynamic = ["version"]

Expand All @@ -59,9 +58,9 @@ Documentation = "https://googleapis.dev/python/google-api-core/latest/"
Repository = "https://github.com/googleapis/google-cloud-python"

[project.optional-dependencies]
async_rest = ["google-auth[aiohttp] >= 2.35.0, < 3.0.0"]
async_rest = ["google-auth[aiohttp] >= 2.35.0, < 3.0.0", "aiohttp >= 3.9.0"]
grpc = [
"grpcio >= 1.33.2, < 2.0.0",
"grpcio >= 1.80.0",
"grpcio >= 1.49.1, < 2.0.0; python_version >= '3.11'",
"grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'",
"grpcio-status >= 1.33.2, < 2.0.0",
Expand Down
14 changes: 14 additions & 0 deletions packages/google-api-core/testing/constraints-3.10.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This constraints file is used to check that lower bounds
# are correct in pyproject.toml
# List *all* library dependencies and extras in this file.
# Pin the version to the lower bound.
#
# e.g., if pyproject.toml has "foo >= 1.14.0, < 2.0.0dev",
# Then this file should have foo==1.14.0
googleapis-common-protos==1.63.2
protobuf==4.25.8
google-auth==2.35.0
requests==2.33.0
grpcio==1.80.0
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.

The constraints file should have the minimum support version of grpcio. 1.80.0 is the latest. PTAL . Applies to packages/google-api-core/testing/constraints-async-rest-3.10.txt also

https://pypi.org/project/grpcio/#history

grpcio-status==1.33.2
proto-plus==1.22.3
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
# Then this file should have foo==1.14.0
googleapis-common-protos==1.63.2
protobuf==4.25.8
google-auth==2.14.1
requests==2.20.0
grpcio==1.33.2
google-auth==2.35.0
requests==2.33.0
grpcio==1.80.0
grpcio-status==1.33.2
proto-plus==1.22.3
Comment thread
parthea marked this conversation as resolved.
# Note: We pin aiohttp to 3.9.0 to avoid deprecation warnings about '@coroutine'
# decorators which were deprecated since Python 3.8. When older versions of aiohttp
# load, because they used @coroutine deep inside their internals, the warnings
# bubble up and cause our nox session to fail.
aiohttp==3.9.0
20 changes: 0 additions & 20 deletions packages/google-api-core/testing/constraints-async-rest-3.9.txt

This file was deleted.

15 changes: 1 addition & 14 deletions packages/google-api-core/tests/asyncio/test_bidi_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import sys

import asyncio

from unittest import mock
Expand All @@ -33,15 +33,6 @@
from google.api_core import bidi_async
from google.api_core import exceptions

# TODO: remove this when droppping support for "Python 3.10" and below.
if sys.version_info < (3, 10): # type: ignore[operator]

def aiter(obj):
return obj.__aiter__()

async def anext(obj):
return await obj.__anext__()


@pytest.mark.asyncio
class Test_AsyncRequestQueueGenerator:
Expand Down Expand Up @@ -185,10 +176,6 @@ def test_done_callbacks(self):
callback.assert_called_once_with(mock.sentinel.future)

@pytest.mark.asyncio
@pytest.mark.skipif(
sys.version_info < (3, 8), # type: ignore[operator]
reason="Versions of Python below 3.8 don't provide support for assert_awaited_once",
)
async def test_metadata(self):
rpc, call = make_async_rpc()
bidi_rpc = bidi_async.AsyncBidiRpc(rpc, metadata=mock.sentinel.A)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def test_all_tracked_versions_and_date_scenarios(

def test_override_gapic_end_only():
"""Test behavior when only gapic_end is manually overridden."""
version_tuple = (3, 9)
version_tuple = (3, 10)
original_info = PYTHON_VERSION_INFO[version_tuple]
mock_py_version = VersionInfoMock(major=version_tuple[0], minor=version_tuple[1])

Expand Down Expand Up @@ -199,7 +199,7 @@ def test_override_gapic_end_only():

def test_override_gapic_deprecation_only():
"""Test behavior when only gapic_deprecation is manually overridden."""
version_tuple = (3, 9)
version_tuple = (3, 10)
original_info = PYTHON_VERSION_INFO[version_tuple]
mock_py_version = VersionInfoMock(major=version_tuple[0], minor=version_tuple[1])

Expand Down
Loading