Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cac8d87
feat: check Python and dependency versions in generated GAPICs
vchudnov-g Jul 30, 2025
20ea9b3
fix import
vchudnov-g Jul 30, 2025
e970c07
Update goldens
vchudnov-g Jul 31, 2025
9b2d7cd
allow Python, dependency versions checks without upgrading api_core
vchudnov-g Jul 31, 2025
e132cca
Update goldens; allow for not found dependencies
vchudnov-g Aug 1, 2025
34705c6
Update code & integration tests to work on all Python versions
vchudnov-g Aug 1, 2025
51a8b5e
fix import errors
vchudnov-g Sep 2, 2025
baefecb
Install `packaging` package
vchudnov-g Sep 3, 2025
ad55260
fix: skip type warnings for imports not present in older api_core
vchudnov-g Sep 3, 2025
ff52e49
fix: exclude the backwards-compatibility code fro mcoverage checks
vchudnov-g Sep 3, 2025
b29ad98
fix: fix coverage skip pragmas
vchudnov-g Sep 3, 2025
b90eb09
Use warnings module instead of logging module
vchudnov-g Sep 10, 2025
6ea418b
Test tweaks
vchudnov-g Sep 11, 2025
9c9db92
fix miports
vchudnov-g Sep 12, 2025
c72b272
WIP: expose the warnings in tests
vchudnov-g Sep 12, 2025
d1a4a3e
try setting future warning
vchudnov-g Sep 12, 2025
11a1f16
Update expected warnings....arbitrarily
vchudnov-g Sep 12, 2025
67e731d
Revert arbitrary change in test expectations: it caused more problems
vchudnov-g Sep 15, 2025
0bf1c15
fix: print the current package versions correctly
vchudnov-g Sep 15, 2025
8942f0c
fix: return package version as tuple of version tuple and string
vchudnov-g Sep 15, 2025
316ea57
fix: filter MTLS deprecation tests for "mtls"
vchudnov-g Sep 16, 2025
81a7379
fix tests
vchudnov-g Sep 16, 2025
08598d0
fix test: warning match string
vchudnov-g Sep 16, 2025
fbab30f
WIP trying to fix tests
vchudnov-g Sep 17, 2025
6e548f0
WIP: try to fix tests
vchudnov-g Sep 17, 2025
b1aea7a
fix tests
vchudnov-g Sep 17, 2025
de880d9
fix test_metadata_response_unary_async
vchudnov-g Sep 18, 2025
cb164fb
Recommend protobuf 6.x
vchudnov-g Oct 1, 2025
be23134
fix typo
vchudnov-g Oct 1, 2025
406f48d
chore: update goldens after rebase
vchudnov-g Oct 10, 2025
5f4bb03
feat: add try block around version check
vchudnov-g Oct 15, 2025
cd3fcae
Fix goldens
vchudnov-g Oct 21, 2025
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
86 changes: 86 additions & 0 deletions gapic/templates/%namespace/%name_%version/%sub/__init__.py.j2
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,92 @@ from {{package_path}} import gapic_version as package_version

__version__ = package_version.__version__


import google.api_core as api_core

if hasattr(api_core, "check_python_version") and hasattr(api_core, "check_dependency_versions"): # pragma: NO COVER
{# TODO(api_core): remove `type:ignore` below when minimum version of api_core makes the else clause unnecessary. #}
api_core.check_python_version("{{package_path}}") # type: ignore
api_core.check_dependency_versions("{{package_path}}") # type: ignore
else: # pragma: NO COVER
{# TODO(api_core): Remove this try-catch when we require api-core at a version that
supports the changes in https://github.com/googleapis/python-api-core/pull/832

In the meantime, please ensure the functionality here mirrors the
equivalent functionality in api_core, in those two functions above.
#}
# An older version of api_core is installed which does not define the
# functions above. We do equivalent checks manually.
try:
import warnings
import sys

_py_version_str = sys.version.split()[0]
_package_label = "{{package_path}}"
if sys.version_info < (3, 9):
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}.",
FutureWarning)

from packaging.version import parse as parse_version

if sys.version_info < (3, 8):
import pkg_resources

def _get_version(dependency_name):
try:
version_string = pkg_resources.get_distribution(dependency_name).version
return (parse_version(version_string), version_string)
except pkg_resources.DistributionNotFound:
return (None, "--")
else:
from importlib import metadata

def _get_version(dependency_name):
try:
version_string = metadata.version("requests")
parsed_version = parse_version(version_string)
return (parsed_version.release, version_string)
except metadata.PackageNotFoundError:
return (None, "--")

_dependency_package = "google.protobuf"
_next_supported_version = "4.25.8"
_next_supported_version_tuple = (4, 25, 8)
_recommendation = " (we recommend 6.x)"
(_version_used, _version_used_string) = _get_version(_dependency_package)
if _version_used and _version_used < _next_supported_version_tuple:
warnings.warn(f"Package {_package_label} depends on " +
f"{_dependency_package}, currently installed at version " +
f"{_version_used_string}. Future updates to " +
f"{_package_label} will require {_dependency_package} at " +
f"version {_next_supported_version} or higher{_recommendation}." +
" Please ensure " +
"that either (a) your Python environment doesn't pin the " +
f"version of {_dependency_package}, so that updates to " +
f"{_package_label} can require the higher version, or " +
"(b) you manually update your Python environment to use at " +
f"least version {_next_supported_version} of " +
f"{_dependency_package}.",
FutureWarning)
except Exception:
warnings.warn("Could not determine the version of Python " +
"currently being used. To continue receiving " +
"updates for {_package_label}, ensure you are " +
"using a supported version of Python; see " +
"https://devguide.python.org/versions/")

{# Import subpackages. -#}
{% for subpackage, _ in api.subpackages|dictsort %}
from . import {{ subpackage }}
Expand Down
1 change: 1 addition & 0 deletions gapic/templates/setup.py.j2
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependencies = [
"google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0",
"grpcio >= 1.33.2, < 2.0.0",
"grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'",
"packaging", # TODO: Remove once we require versions of api core that include this
"proto-plus >= 1.22.3, <2.0.0",
"proto-plus >= 1.25.0, <2.0.0; python_version >= '3.13'",
{# Explicitly exclude protobuf versions mentioned in https://cloud.google.com/support/bulletins#GCP-2022-019 #}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,7 @@ def test_{{ service.name|snake_case }}_grpc_asyncio_transport_channel():

# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
# removed from grpc/grpc_asyncio transport constructor.
@pytest.mark.filterwarnings("ignore::FutureWarning")
@pytest.mark.parametrize("transport_class", [transports.{{ service.grpc_transport_name }}, transports.{{ service.grpc_asyncio_transport_name }}])
def test_{{ service.name|snake_case }}_transport_channel_mtls_with_client_cert_source(
transport_class
Expand Down
8 changes: 8 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ def unit(session):
"pyfakefs",
"grpcio-status",
"proto-plus",
"setuptools", # TODO: Remove when not needed in __init__.py.j2
"packaging", # TODO: Remove when not needed in __init__.py.j2
)
session.install("-e", ".")
session.run(
Expand Down Expand Up @@ -482,6 +484,8 @@ def run_showcase_unit_tests(session, fail_under=100, rest_async_io_enabled=False
"pytest-xdist",
"asyncmock; python_version < '3.8'",
"pytest-asyncio",
"setuptools", # TODO: Remove when not needed in __init__.py.j2
"packaging", # TODO: Remove when not needed in __init__.py.j2
)
# Run the tests.
# NOTE: async rest is not supported against the minimum supported version of google-api-core.
Expand Down Expand Up @@ -596,6 +600,8 @@ def showcase_mypy(
"types-protobuf",
"types-requests",
"types-dataclasses",
"setuptools", # TODO: Remove when not needed in __init__.py.j2
"packaging", # TODO: Remove when not needed in __init__.py.j2
)

with showcase_library(session, templates=templates, other_opts=other_opts) as lib:
Expand Down Expand Up @@ -726,6 +732,8 @@ def mypy(session):
"types-PyYAML",
"types-dataclasses",
"click==8.1.3",
"setuptools", # TODO: Remove when not needed in __init__.py.j2
"packaging", # TODO: Remove when not needed in __init__.py.j2
)
session.install(".")
session.run("mypy", "-p", "gapic")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,85 @@
__version__ = package_version.__version__


import google.api_core as api_core

if hasattr(api_core, "check_python_version") and hasattr(api_core, "check_dependency_versions"): # pragma: NO COVER
api_core.check_python_version("google.cloud.asset_v1") # type: ignore
api_core.check_dependency_versions("google.cloud.asset_v1") # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to make sure I understand: we expect to see duplicate warnings for each GCP library and api_core, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not exactly duplicate; just very similar, one for each of our packages losing support for the Python version currently being used. Paraphrasing the exact wording, here's an example:

Warning: This `api_core` package is running on Python 3.7. There will be no more updates to `api_core` on Python 3.7 installations.

Warning: This `google.cloud.foo_v1` package is running on Python 3.7. There will be no more updates to `google.cloud.foo_v1` on Python 3.7 installations.

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if api_core.check_dependency_versions should take in a list of dependencies, and have the generator populate it? Or at least provide the option to override? It seems a little unexpected that all the dependencies are always hard-coded in the api_core layer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The intent was to have each package issue a warning saying it would soon stop getting updated if the specified dependencies were pinned to old versions. I guess when this PR started as a prototype PR, I was thinking that the dependencies in question were the same for both packages, so we would re-use the warnings.

But I think there's a lot of merit to what you're suggesting: each of our artifacts sets up the warning message for its direct, not transitive, dependencies.

Update: I updated googleapis/python-api-core#832 so we could pass the list of dependencies. If none are given, check_dependency_versions defaults to the dependencies in api_core. So we can start with this, and then change things so the dependencies are explicitly listed here, especially when we have GAPIC-specific dependencies.

Copy link
Contributor

Choose a reason for hiding this comment

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

Awesome, that seems like a great change!

else: # pragma: NO COVER
# An older version of api_core is installed which does not define the
# functions above. We do equivalent checks manually.
try:
import warnings
import sys

_py_version_str = sys.version.split()[0]
_package_label = "google.cloud.asset_v1"
if sys.version_info < (3, 9):
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}.",
FutureWarning)

from packaging.version import parse as parse_version

if sys.version_info < (3, 8):
import pkg_resources

def _get_version(dependency_name):
try:
version_string = pkg_resources.get_distribution(dependency_name).version
return (parse_version(version_string), version_string)
except pkg_resources.DistributionNotFound:
return (None, "--")
else:
from importlib import metadata

def _get_version(dependency_name):
try:
version_string = metadata.version("requests")
parsed_version = parse_version(version_string)
return (parsed_version.release, version_string)
except metadata.PackageNotFoundError:
return (None, "--")

_dependency_package = "google.protobuf"
_next_supported_version = "4.25.8"
_next_supported_version_tuple = (4, 25, 8)
_recommendation = " (we recommend 6.x)"
(_version_used, _version_used_string) = _get_version(_dependency_package)
if _version_used and _version_used < _next_supported_version_tuple:
warnings.warn(f"Package {_package_label} depends on " +
f"{_dependency_package}, currently installed at version " +
f"{_version_used_string}. Future updates to " +
f"{_package_label} will require {_dependency_package} at " +
f"version {_next_supported_version} or higher{_recommendation}." +
" Please ensure " +
"that either (a) your Python environment doesn't pin the " +
f"version of {_dependency_package}, so that updates to " +
f"{_package_label} can require the higher version, or " +
"(b) you manually update your Python environment to use at " +
f"least version {_next_supported_version} of " +
f"{_dependency_package}.",
FutureWarning)
except Exception:
warnings.warn("Could not determine the version of Python " +
"currently being used. To continue receiving " +
"updates for {_package_label}, ensure you are " +
"using a supported version of Python; see " +
"https://devguide.python.org/versions/")


from .services.asset_service import AssetServiceClient
from .services.asset_service import AssetServiceAsyncClient

Expand Down
1 change: 1 addition & 0 deletions tests/integration/goldens/asset/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0",
"grpcio >= 1.33.2, < 2.0.0",
"grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'",
"packaging", # TODO: Remove once we require versions of api core that include this
"proto-plus >= 1.22.3, <2.0.0",
"proto-plus >= 1.25.0, <2.0.0; python_version >= '3.13'",
"protobuf>=3.20.2,<7.0.0,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17712,6 +17712,7 @@ def test_asset_service_grpc_asyncio_transport_channel():

# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
# removed from grpc/grpc_asyncio transport constructor.
@pytest.mark.filterwarnings("ignore::FutureWarning")
@pytest.mark.parametrize("transport_class", [transports.AssetServiceGrpcTransport, transports.AssetServiceGrpcAsyncIOTransport])
def test_asset_service_transport_channel_mtls_with_client_cert_source(
transport_class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,85 @@
__version__ = package_version.__version__


import google.api_core as api_core

if hasattr(api_core, "check_python_version") and hasattr(api_core, "check_dependency_versions"): # pragma: NO COVER
api_core.check_python_version("google.iam.credentials_v1") # type: ignore
api_core.check_dependency_versions("google.iam.credentials_v1") # type: ignore
else: # pragma: NO COVER
# An older version of api_core is installed which does not define the
# functions above. We do equivalent checks manually.
try:
import warnings
import sys

_py_version_str = sys.version.split()[0]
_package_label = "google.iam.credentials_v1"
if sys.version_info < (3, 9):
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}.",
FutureWarning)

from packaging.version import parse as parse_version

if sys.version_info < (3, 8):
import pkg_resources

def _get_version(dependency_name):
try:
version_string = pkg_resources.get_distribution(dependency_name).version
return (parse_version(version_string), version_string)
except pkg_resources.DistributionNotFound:
return (None, "--")
else:
from importlib import metadata

def _get_version(dependency_name):
try:
version_string = metadata.version("requests")
parsed_version = parse_version(version_string)
return (parsed_version.release, version_string)
except metadata.PackageNotFoundError:
return (None, "--")

_dependency_package = "google.protobuf"
_next_supported_version = "4.25.8"
_next_supported_version_tuple = (4, 25, 8)
_recommendation = " (we recommend 6.x)"
(_version_used, _version_used_string) = _get_version(_dependency_package)
if _version_used and _version_used < _next_supported_version_tuple:
warnings.warn(f"Package {_package_label} depends on " +
f"{_dependency_package}, currently installed at version " +
f"{_version_used_string}. Future updates to " +
f"{_package_label} will require {_dependency_package} at " +
f"version {_next_supported_version} or higher{_recommendation}." +
" Please ensure " +
"that either (a) your Python environment doesn't pin the " +
f"version of {_dependency_package}, so that updates to " +
f"{_package_label} can require the higher version, or " +
"(b) you manually update your Python environment to use at " +
f"least version {_next_supported_version} of " +
f"{_dependency_package}.",
FutureWarning)
except Exception:
warnings.warn("Could not determine the version of Python " +
"currently being used. To continue receiving " +
"updates for {_package_label}, ensure you are " +
"using a supported version of Python; see " +
"https://devguide.python.org/versions/")


from .services.iam_credentials import IAMCredentialsClient
from .services.iam_credentials import IAMCredentialsAsyncClient

Expand Down
1 change: 1 addition & 0 deletions tests/integration/goldens/credentials/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0",
"grpcio >= 1.33.2, < 2.0.0",
"grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'",
"packaging", # TODO: Remove once we require versions of api core that include this
"proto-plus >= 1.22.3, <2.0.0",
"proto-plus >= 1.25.0, <2.0.0; python_version >= '3.13'",
"protobuf>=3.20.2,<7.0.0,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3972,6 +3972,7 @@ def test_iam_credentials_grpc_asyncio_transport_channel():

# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
# removed from grpc/grpc_asyncio transport constructor.
@pytest.mark.filterwarnings("ignore::FutureWarning")
@pytest.mark.parametrize("transport_class", [transports.IAMCredentialsGrpcTransport, transports.IAMCredentialsGrpcAsyncIOTransport])
def test_iam_credentials_transport_channel_mtls_with_client_cert_source(
transport_class
Expand Down
Loading
Loading