Skip to content

Commit a9484c6

Browse files
authored
feat: check Python and dependency versions in generated GAPICs (#2419)
1 parent 7bba0bd commit a9484c6

File tree

30 files changed

+668
-0
lines changed

30 files changed

+668
-0
lines changed

gapic/templates/%namespace/%name_%version/%sub/__init__.py.j2

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,92 @@ from {{package_path}} import gapic_version as package_version
77

88
__version__ = package_version.__version__
99

10+
11+
import google.api_core as api_core
12+
13+
if hasattr(api_core, "check_python_version") and hasattr(api_core, "check_dependency_versions"): # pragma: NO COVER
14+
{# TODO(api_core): remove `type:ignore` below when minimum version of api_core makes the else clause unnecessary. #}
15+
api_core.check_python_version("{{package_path}}") # type: ignore
16+
api_core.check_dependency_versions("{{package_path}}") # type: ignore
17+
else: # pragma: NO COVER
18+
{# TODO(api_core): Remove this try-catch when we require api-core at a version that
19+
supports the changes in https://github.com/googleapis/python-api-core/pull/832
20+
21+
In the meantime, please ensure the functionality here mirrors the
22+
equivalent functionality in api_core, in those two functions above.
23+
#}
24+
# An older version of api_core is installed which does not define the
25+
# functions above. We do equivalent checks manually.
26+
try:
27+
import warnings
28+
import sys
29+
30+
_py_version_str = sys.version.split()[0]
31+
_package_label = "{{package_path}}"
32+
if sys.version_info < (3, 9):
33+
warnings.warn("You are using a non-supported Python version " +
34+
f"({_py_version_str}). Google will not post any further " +
35+
f"updates to {_package_label} supporting this Python version. " +
36+
"Please upgrade to the latest Python version, or at " +
37+
f"least to Python 3.9, and then update {_package_label}.",
38+
FutureWarning)
39+
if sys.version_info[:2] == (3, 9):
40+
warnings.warn(f"You are using a Python version ({_py_version_str}) " +
41+
f"which Google will stop supporting in {_package_label} in " +
42+
"January 2026. Please " +
43+
"upgrade to the latest Python version, or at " +
44+
"least to Python 3.10, before then, and " +
45+
f"then update {_package_label}.",
46+
FutureWarning)
47+
48+
from packaging.version import parse as parse_version
49+
50+
if sys.version_info < (3, 8):
51+
import pkg_resources
52+
53+
def _get_version(dependency_name):
54+
try:
55+
version_string = pkg_resources.get_distribution(dependency_name).version
56+
return (parse_version(version_string), version_string)
57+
except pkg_resources.DistributionNotFound:
58+
return (None, "--")
59+
else:
60+
from importlib import metadata
61+
62+
def _get_version(dependency_name):
63+
try:
64+
version_string = metadata.version("requests")
65+
parsed_version = parse_version(version_string)
66+
return (parsed_version.release, version_string)
67+
except metadata.PackageNotFoundError:
68+
return (None, "--")
69+
70+
_dependency_package = "google.protobuf"
71+
_next_supported_version = "4.25.8"
72+
_next_supported_version_tuple = (4, 25, 8)
73+
_recommendation = " (we recommend 6.x)"
74+
(_version_used, _version_used_string) = _get_version(_dependency_package)
75+
if _version_used and _version_used < _next_supported_version_tuple:
76+
warnings.warn(f"Package {_package_label} depends on " +
77+
f"{_dependency_package}, currently installed at version " +
78+
f"{_version_used_string}. Future updates to " +
79+
f"{_package_label} will require {_dependency_package} at " +
80+
f"version {_next_supported_version} or higher{_recommendation}." +
81+
" Please ensure " +
82+
"that either (a) your Python environment doesn't pin the " +
83+
f"version of {_dependency_package}, so that updates to " +
84+
f"{_package_label} can require the higher version, or " +
85+
"(b) you manually update your Python environment to use at " +
86+
f"least version {_next_supported_version} of " +
87+
f"{_dependency_package}.",
88+
FutureWarning)
89+
except Exception:
90+
warnings.warn("Could not determine the version of Python " +
91+
"currently being used. To continue receiving " +
92+
"updates for {_package_label}, ensure you are " +
93+
"using a supported version of Python; see " +
94+
"https://devguide.python.org/versions/")
95+
1096
{# Import subpackages. -#}
1197
{% for subpackage, _ in api.subpackages|dictsort %}
1298
from . import {{ subpackage }}

gapic/templates/setup.py.j2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dependencies = [
3939
"google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0",
4040
"grpcio >= 1.33.2, < 2.0.0",
4141
"grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'",
42+
"packaging", # TODO: Remove once we require versions of api core that include this
4243
"proto-plus >= 1.22.3, <2.0.0",
4344
"proto-plus >= 1.25.0, <2.0.0; python_version >= '3.13'",
4445
{# Explicitly exclude protobuf versions mentioned in https://cloud.google.com/support/bulletins#GCP-2022-019 #}

gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,6 +1472,7 @@ def test_{{ service.name|snake_case }}_grpc_asyncio_transport_channel():
14721472

14731473
# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
14741474
# removed from grpc/grpc_asyncio transport constructor.
1475+
@pytest.mark.filterwarnings("ignore::FutureWarning")
14751476
@pytest.mark.parametrize("transport_class", [transports.{{ service.grpc_transport_name }}, transports.{{ service.grpc_asyncio_transport_name }}])
14761477
def test_{{ service.name|snake_case }}_transport_channel_mtls_with_client_cert_source(
14771478
transport_class

noxfile.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ def unit(session):
6161
"pyfakefs",
6262
"grpcio-status",
6363
"proto-plus",
64+
"setuptools", # TODO: Remove when not needed in __init__.py.j2
65+
"packaging", # TODO: Remove when not needed in __init__.py.j2
6466
)
6567
session.install("-e", ".")
6668
session.run(
@@ -482,6 +484,8 @@ def run_showcase_unit_tests(session, fail_under=100, rest_async_io_enabled=False
482484
"pytest-xdist",
483485
"asyncmock; python_version < '3.8'",
484486
"pytest-asyncio",
487+
"setuptools", # TODO: Remove when not needed in __init__.py.j2
488+
"packaging", # TODO: Remove when not needed in __init__.py.j2
485489
)
486490
# Run the tests.
487491
# NOTE: async rest is not supported against the minimum supported version of google-api-core.
@@ -596,6 +600,8 @@ def showcase_mypy(
596600
"types-protobuf",
597601
"types-requests",
598602
"types-dataclasses",
603+
"setuptools", # TODO: Remove when not needed in __init__.py.j2
604+
"packaging", # TODO: Remove when not needed in __init__.py.j2
599605
)
600606

601607
with showcase_library(session, templates=templates, other_opts=other_opts) as lib:
@@ -726,6 +732,8 @@ def mypy(session):
726732
"types-PyYAML",
727733
"types-dataclasses",
728734
"click==8.1.3",
735+
"setuptools", # TODO: Remove when not needed in __init__.py.j2
736+
"packaging", # TODO: Remove when not needed in __init__.py.j2
729737
)
730738
session.install(".")
731739
session.run("mypy", "-p", "gapic")

tests/integration/goldens/asset/google/cloud/asset_v1/__init__.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,85 @@
1818
__version__ = package_version.__version__
1919

2020

21+
import google.api_core as api_core
22+
23+
if hasattr(api_core, "check_python_version") and hasattr(api_core, "check_dependency_versions"): # pragma: NO COVER
24+
api_core.check_python_version("google.cloud.asset_v1") # type: ignore
25+
api_core.check_dependency_versions("google.cloud.asset_v1") # type: ignore
26+
else: # pragma: NO COVER
27+
# An older version of api_core is installed which does not define the
28+
# functions above. We do equivalent checks manually.
29+
try:
30+
import warnings
31+
import sys
32+
33+
_py_version_str = sys.version.split()[0]
34+
_package_label = "google.cloud.asset_v1"
35+
if sys.version_info < (3, 9):
36+
warnings.warn("You are using a non-supported Python version " +
37+
f"({_py_version_str}). Google will not post any further " +
38+
f"updates to {_package_label} supporting this Python version. " +
39+
"Please upgrade to the latest Python version, or at " +
40+
f"least to Python 3.9, and then update {_package_label}.",
41+
FutureWarning)
42+
if sys.version_info[:2] == (3, 9):
43+
warnings.warn(f"You are using a Python version ({_py_version_str}) " +
44+
f"which Google will stop supporting in {_package_label} in " +
45+
"January 2026. Please " +
46+
"upgrade to the latest Python version, or at " +
47+
"least to Python 3.10, before then, and " +
48+
f"then update {_package_label}.",
49+
FutureWarning)
50+
51+
from packaging.version import parse as parse_version
52+
53+
if sys.version_info < (3, 8):
54+
import pkg_resources
55+
56+
def _get_version(dependency_name):
57+
try:
58+
version_string = pkg_resources.get_distribution(dependency_name).version
59+
return (parse_version(version_string), version_string)
60+
except pkg_resources.DistributionNotFound:
61+
return (None, "--")
62+
else:
63+
from importlib import metadata
64+
65+
def _get_version(dependency_name):
66+
try:
67+
version_string = metadata.version("requests")
68+
parsed_version = parse_version(version_string)
69+
return (parsed_version.release, version_string)
70+
except metadata.PackageNotFoundError:
71+
return (None, "--")
72+
73+
_dependency_package = "google.protobuf"
74+
_next_supported_version = "4.25.8"
75+
_next_supported_version_tuple = (4, 25, 8)
76+
_recommendation = " (we recommend 6.x)"
77+
(_version_used, _version_used_string) = _get_version(_dependency_package)
78+
if _version_used and _version_used < _next_supported_version_tuple:
79+
warnings.warn(f"Package {_package_label} depends on " +
80+
f"{_dependency_package}, currently installed at version " +
81+
f"{_version_used_string}. Future updates to " +
82+
f"{_package_label} will require {_dependency_package} at " +
83+
f"version {_next_supported_version} or higher{_recommendation}." +
84+
" Please ensure " +
85+
"that either (a) your Python environment doesn't pin the " +
86+
f"version of {_dependency_package}, so that updates to " +
87+
f"{_package_label} can require the higher version, or " +
88+
"(b) you manually update your Python environment to use at " +
89+
f"least version {_next_supported_version} of " +
90+
f"{_dependency_package}.",
91+
FutureWarning)
92+
except Exception:
93+
warnings.warn("Could not determine the version of Python " +
94+
"currently being used. To continue receiving " +
95+
"updates for {_package_label}, ensure you are " +
96+
"using a supported version of Python; see " +
97+
"https://devguide.python.org/versions/")
98+
99+
21100
from .services.asset_service import AssetServiceClient
22101
from .services.asset_service import AssetServiceAsyncClient
23102

tests/integration/goldens/asset/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0",
4646
"grpcio >= 1.33.2, < 2.0.0",
4747
"grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'",
48+
"packaging", # TODO: Remove once we require versions of api core that include this
4849
"proto-plus >= 1.22.3, <2.0.0",
4950
"proto-plus >= 1.25.0, <2.0.0; python_version >= '3.13'",
5051
"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",

tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17712,6 +17712,7 @@ def test_asset_service_grpc_asyncio_transport_channel():
1771217712

1771317713
# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
1771417714
# removed from grpc/grpc_asyncio transport constructor.
17715+
@pytest.mark.filterwarnings("ignore::FutureWarning")
1771517716
@pytest.mark.parametrize("transport_class", [transports.AssetServiceGrpcTransport, transports.AssetServiceGrpcAsyncIOTransport])
1771617717
def test_asset_service_transport_channel_mtls_with_client_cert_source(
1771717718
transport_class

tests/integration/goldens/credentials/google/iam/credentials_v1/__init__.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,85 @@
1818
__version__ = package_version.__version__
1919

2020

21+
import google.api_core as api_core
22+
23+
if hasattr(api_core, "check_python_version") and hasattr(api_core, "check_dependency_versions"): # pragma: NO COVER
24+
api_core.check_python_version("google.iam.credentials_v1") # type: ignore
25+
api_core.check_dependency_versions("google.iam.credentials_v1") # type: ignore
26+
else: # pragma: NO COVER
27+
# An older version of api_core is installed which does not define the
28+
# functions above. We do equivalent checks manually.
29+
try:
30+
import warnings
31+
import sys
32+
33+
_py_version_str = sys.version.split()[0]
34+
_package_label = "google.iam.credentials_v1"
35+
if sys.version_info < (3, 9):
36+
warnings.warn("You are using a non-supported Python version " +
37+
f"({_py_version_str}). Google will not post any further " +
38+
f"updates to {_package_label} supporting this Python version. " +
39+
"Please upgrade to the latest Python version, or at " +
40+
f"least to Python 3.9, and then update {_package_label}.",
41+
FutureWarning)
42+
if sys.version_info[:2] == (3, 9):
43+
warnings.warn(f"You are using a Python version ({_py_version_str}) " +
44+
f"which Google will stop supporting in {_package_label} in " +
45+
"January 2026. Please " +
46+
"upgrade to the latest Python version, or at " +
47+
"least to Python 3.10, before then, and " +
48+
f"then update {_package_label}.",
49+
FutureWarning)
50+
51+
from packaging.version import parse as parse_version
52+
53+
if sys.version_info < (3, 8):
54+
import pkg_resources
55+
56+
def _get_version(dependency_name):
57+
try:
58+
version_string = pkg_resources.get_distribution(dependency_name).version
59+
return (parse_version(version_string), version_string)
60+
except pkg_resources.DistributionNotFound:
61+
return (None, "--")
62+
else:
63+
from importlib import metadata
64+
65+
def _get_version(dependency_name):
66+
try:
67+
version_string = metadata.version("requests")
68+
parsed_version = parse_version(version_string)
69+
return (parsed_version.release, version_string)
70+
except metadata.PackageNotFoundError:
71+
return (None, "--")
72+
73+
_dependency_package = "google.protobuf"
74+
_next_supported_version = "4.25.8"
75+
_next_supported_version_tuple = (4, 25, 8)
76+
_recommendation = " (we recommend 6.x)"
77+
(_version_used, _version_used_string) = _get_version(_dependency_package)
78+
if _version_used and _version_used < _next_supported_version_tuple:
79+
warnings.warn(f"Package {_package_label} depends on " +
80+
f"{_dependency_package}, currently installed at version " +
81+
f"{_version_used_string}. Future updates to " +
82+
f"{_package_label} will require {_dependency_package} at " +
83+
f"version {_next_supported_version} or higher{_recommendation}." +
84+
" Please ensure " +
85+
"that either (a) your Python environment doesn't pin the " +
86+
f"version of {_dependency_package}, so that updates to " +
87+
f"{_package_label} can require the higher version, or " +
88+
"(b) you manually update your Python environment to use at " +
89+
f"least version {_next_supported_version} of " +
90+
f"{_dependency_package}.",
91+
FutureWarning)
92+
except Exception:
93+
warnings.warn("Could not determine the version of Python " +
94+
"currently being used. To continue receiving " +
95+
"updates for {_package_label}, ensure you are " +
96+
"using a supported version of Python; see " +
97+
"https://devguide.python.org/versions/")
98+
99+
21100
from .services.iam_credentials import IAMCredentialsClient
22101
from .services.iam_credentials import IAMCredentialsAsyncClient
23102

tests/integration/goldens/credentials/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0",
4646
"grpcio >= 1.33.2, < 2.0.0",
4747
"grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'",
48+
"packaging", # TODO: Remove once we require versions of api core that include this
4849
"proto-plus >= 1.22.3, <2.0.0",
4950
"proto-plus >= 1.25.0, <2.0.0; python_version >= '3.13'",
5051
"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",

tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3972,6 +3972,7 @@ def test_iam_credentials_grpc_asyncio_transport_channel():
39723972

39733973
# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
39743974
# removed from grpc/grpc_asyncio transport constructor.
3975+
@pytest.mark.filterwarnings("ignore::FutureWarning")
39753976
@pytest.mark.parametrize("transport_class", [transports.IAMCredentialsGrpcTransport, transports.IAMCredentialsGrpcAsyncIOTransport])
39763977
def test_iam_credentials_transport_channel_mtls_with_client_cert_source(
39773978
transport_class

0 commit comments

Comments
 (0)