google-api-core 2.27.0__tar.gz → 2.28.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. {google_api_core-2.27.0/google_api_core.egg-info → google_api_core-2.28.0}/PKG-INFO +1 -1
  2. google_api_core-2.28.0/google/api_core/__init__.py +40 -0
  3. google_api_core-2.28.0/google/api_core/_python_package_support.py +209 -0
  4. google_api_core-2.28.0/google/api_core/_python_version_support.py +269 -0
  5. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/version.py +1 -1
  6. {google_api_core-2.27.0 → google_api_core-2.28.0/google_api_core.egg-info}/PKG-INFO +1 -1
  7. {google_api_core-2.27.0 → google_api_core-2.28.0}/google_api_core.egg-info/SOURCES.txt +4 -0
  8. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/gapic/test_method.py +3 -1
  9. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_bidi.py +6 -0
  10. google_api_core-2.28.0/tests/unit/test_python_package_support.py +147 -0
  11. google_api_core-2.28.0/tests/unit/test_python_version_support.py +253 -0
  12. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_timeout.py +10 -1
  13. google_api_core-2.27.0/google/api_core/__init__.py +0 -22
  14. {google_api_core-2.27.0 → google_api_core-2.28.0}/LICENSE +0 -0
  15. {google_api_core-2.27.0 → google_api_core-2.28.0}/MANIFEST.in +0 -0
  16. {google_api_core-2.27.0 → google_api_core-2.28.0}/README.rst +0 -0
  17. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/_rest_streaming_base.py +0 -0
  18. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/bidi.py +0 -0
  19. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/bidi_async.py +0 -0
  20. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/bidi_base.py +0 -0
  21. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/client_info.py +0 -0
  22. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/client_logging.py +0 -0
  23. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/client_options.py +0 -0
  24. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/datetime_helpers.py +0 -0
  25. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/exceptions.py +0 -0
  26. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/extended_operation.py +0 -0
  27. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/future/__init__.py +0 -0
  28. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/future/_helpers.py +0 -0
  29. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/future/async_future.py +0 -0
  30. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/future/base.py +0 -0
  31. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/future/polling.py +0 -0
  32. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/gapic_v1/__init__.py +0 -0
  33. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/gapic_v1/client_info.py +0 -0
  34. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/gapic_v1/config.py +0 -0
  35. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/gapic_v1/config_async.py +0 -0
  36. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/gapic_v1/method.py +0 -0
  37. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/gapic_v1/method_async.py +0 -0
  38. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/gapic_v1/routing_header.py +0 -0
  39. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/general_helpers.py +0 -0
  40. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/grpc_helpers.py +0 -0
  41. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/grpc_helpers_async.py +0 -0
  42. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/iam.py +0 -0
  43. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operation.py +0 -0
  44. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operation_async.py +0 -0
  45. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/__init__.py +0 -0
  46. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/abstract_operations_base_client.py +0 -0
  47. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/abstract_operations_client.py +0 -0
  48. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/operations_async_client.py +0 -0
  49. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/operations_client.py +0 -0
  50. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/operations_client_config.py +0 -0
  51. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/operations_rest_client_async.py +0 -0
  52. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/pagers.py +0 -0
  53. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/pagers_async.py +0 -0
  54. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/pagers_base.py +0 -0
  55. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/transports/__init__.py +0 -0
  56. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/transports/base.py +0 -0
  57. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/transports/rest.py +0 -0
  58. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/operations_v1/transports/rest_asyncio.py +0 -0
  59. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/page_iterator.py +0 -0
  60. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/page_iterator_async.py +0 -0
  61. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/path_template.py +0 -0
  62. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/protobuf_helpers.py +0 -0
  63. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/py.typed +0 -0
  64. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/rest_helpers.py +0 -0
  65. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/rest_streaming.py +0 -0
  66. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/rest_streaming_async.py +0 -0
  67. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/retry/__init__.py +0 -0
  68. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/retry/retry_base.py +0 -0
  69. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/retry/retry_streaming.py +0 -0
  70. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/retry/retry_streaming_async.py +0 -0
  71. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/retry/retry_unary.py +0 -0
  72. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/retry/retry_unary_async.py +0 -0
  73. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/retry_async.py +0 -0
  74. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/timeout.py +0 -0
  75. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/universe.py +0 -0
  76. {google_api_core-2.27.0 → google_api_core-2.28.0}/google/api_core/version_header.py +0 -0
  77. {google_api_core-2.27.0 → google_api_core-2.28.0}/google_api_core.egg-info/dependency_links.txt +0 -0
  78. {google_api_core-2.27.0 → google_api_core-2.28.0}/google_api_core.egg-info/requires.txt +0 -0
  79. {google_api_core-2.27.0 → google_api_core-2.28.0}/google_api_core.egg-info/top_level.txt +0 -0
  80. {google_api_core-2.27.0 → google_api_core-2.28.0}/pyproject.toml +0 -0
  81. {google_api_core-2.27.0 → google_api_core-2.28.0}/setup.cfg +0 -0
  82. {google_api_core-2.27.0 → google_api_core-2.28.0}/setup.py +0 -0
  83. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/__init__.py +0 -0
  84. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/__init__.py +0 -0
  85. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/future/__init__.py +0 -0
  86. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/future/test_async_future.py +0 -0
  87. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/gapic/test_config_async.py +0 -0
  88. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/gapic/test_method_async.py +0 -0
  89. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/operations_v1/__init__.py +0 -0
  90. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/operations_v1/test_operations_async_client.py +0 -0
  91. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/retry/__init__.py +0 -0
  92. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/retry/test_retry_streaming_async.py +0 -0
  93. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/retry/test_retry_unary_async.py +0 -0
  94. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/test_bidi_async.py +0 -0
  95. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/test_grpc_helpers_async.py +0 -0
  96. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/test_operation_async.py +0 -0
  97. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/test_page_iterator_async.py +0 -0
  98. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/asyncio/test_rest_streaming_async.py +0 -0
  99. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/helpers.py +0 -0
  100. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/__init__.py +0 -0
  101. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/future/__init__.py +0 -0
  102. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/future/test__helpers.py +0 -0
  103. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/future/test_polling.py +0 -0
  104. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/gapic/test_client_info.py +0 -0
  105. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/gapic/test_config.py +0 -0
  106. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/gapic/test_routing_header.py +0 -0
  107. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/operations_v1/__init__.py +0 -0
  108. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/operations_v1/test_operations_client.py +0 -0
  109. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/operations_v1/test_operations_rest_client.py +0 -0
  110. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/retry/__init__.py +0 -0
  111. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/retry/test_retry_base.py +0 -0
  112. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/retry/test_retry_imports.py +0 -0
  113. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/retry/test_retry_streaming.py +0 -0
  114. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/retry/test_retry_unary.py +0 -0
  115. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_client_info.py +0 -0
  116. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_client_logging.py +0 -0
  117. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_client_options.py +0 -0
  118. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_datetime_helpers.py +0 -0
  119. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_exceptions.py +0 -0
  120. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_extended_operation.py +0 -0
  121. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_grpc_helpers.py +0 -0
  122. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_iam.py +0 -0
  123. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_operation.py +0 -0
  124. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_packaging.py +0 -0
  125. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_page_iterator.py +0 -0
  126. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_path_template.py +0 -0
  127. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_protobuf_helpers.py +0 -0
  128. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_rest_helpers.py +0 -0
  129. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_rest_streaming.py +0 -0
  130. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_universe.py +0 -0
  131. {google_api_core-2.27.0 → google_api_core-2.28.0}/tests/unit/test_version_header.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-api-core
3
- Version: 2.27.0
3
+ Version: 2.28.0
4
4
  Summary: Google API client core library
5
5
  Author-email: Google LLC <googleapis-packages@google.com>
6
6
  License: Apache 2.0
@@ -0,0 +1,40 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Google API Core.
16
+
17
+ This package contains common code and utilities used by Google client libraries.
18
+ """
19
+
20
+ from google.api_core import _python_package_support
21
+ from google.api_core import _python_version_support
22
+ from google.api_core import version as api_core_version
23
+
24
+ __version__ = api_core_version.__version__
25
+
26
+ # NOTE: Until dependent artifacts require this version of
27
+ # google.api_core, the functionality below must be made available
28
+ # manually in those artifacts.
29
+
30
+ # expose dependency checks for external callers
31
+ check_python_version = _python_version_support.check_python_version
32
+ check_dependency_versions = _python_package_support.check_dependency_versions
33
+ warn_deprecation_for_versions_less_than = (
34
+ _python_package_support.warn_deprecation_for_versions_less_than
35
+ )
36
+ DependencyConstraint = _python_package_support.DependencyConstraint
37
+
38
+ # perform version checks against api_core, and emit warnings if needed
39
+ check_python_version(package="google.api_core")
40
+ check_dependency_versions("google.api_core")
@@ -0,0 +1,209 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Code to check versions of dependencies used by Google Cloud Client Libraries."""
16
+
17
+ import warnings
18
+ import sys
19
+ from typing import Optional
20
+
21
+ from collections import namedtuple
22
+
23
+ from ._python_version_support import (
24
+ _flatten_message,
25
+ _get_distribution_and_import_packages,
26
+ )
27
+
28
+ from packaging.version import parse as parse_version
29
+
30
+ # Here we list all the packages for which we want to issue warnings
31
+ # about deprecated and unsupported versions.
32
+ DependencyConstraint = namedtuple(
33
+ "DependencyConstraint",
34
+ ["package_name", "minimum_fully_supported_version", "recommended_version"],
35
+ )
36
+ _PACKAGE_DEPENDENCY_WARNINGS = [
37
+ DependencyConstraint(
38
+ "google.protobuf",
39
+ minimum_fully_supported_version="4.25.8",
40
+ recommended_version="6.x",
41
+ )
42
+ ]
43
+
44
+
45
+ DependencyVersion = namedtuple("DependencyVersion", ["version", "version_string"])
46
+ # Version string we provide in a DependencyVersion when we can't determine the version of a
47
+ # package.
48
+ UNKNOWN_VERSION_STRING = "--"
49
+
50
+
51
+ def get_dependency_version(
52
+ dependency_name: str,
53
+ ) -> DependencyVersion:
54
+ """Get the parsed version of an installed package dependency.
55
+
56
+ This function checks for an installed package and returns its version
57
+ as a `packaging.version.Version` object for safe comparison. It handles
58
+ both modern (Python 3.8+) and legacy (Python 3.7) environments.
59
+
60
+ Args:
61
+ dependency_name: The distribution name of the package (e.g., 'requests').
62
+
63
+ Returns:
64
+ A DependencyVersion namedtuple with `version` and
65
+ `version_string` attributes, or `DependencyVersion(None,
66
+ UNKNOWN_VERSION_STRING)` if the package is not found or
67
+ another error occurs during version discovery.
68
+
69
+ """
70
+ try:
71
+ if sys.version_info >= (3, 8):
72
+ from importlib import metadata
73
+
74
+ version_string = metadata.version(dependency_name)
75
+ return DependencyVersion(parse_version(version_string), version_string)
76
+
77
+ # TODO(https://github.com/googleapis/python-api-core/issues/835): Remove
78
+ # this code path once we drop support for Python 3.7
79
+ else: # pragma: NO COVER
80
+ # Use pkg_resources, which is part of setuptools.
81
+ import pkg_resources
82
+
83
+ version_string = pkg_resources.get_distribution(dependency_name).version
84
+ return DependencyVersion(parse_version(version_string), version_string)
85
+
86
+ except Exception:
87
+ return DependencyVersion(None, UNKNOWN_VERSION_STRING)
88
+
89
+
90
+ def warn_deprecation_for_versions_less_than(
91
+ consumer_import_package: str,
92
+ dependency_import_package: str,
93
+ minimum_fully_supported_version: str,
94
+ recommended_version: Optional[str] = None,
95
+ message_template: Optional[str] = None,
96
+ ):
97
+ """Issue any needed deprecation warnings for `dependency_import_package`.
98
+
99
+ If `dependency_import_package` is installed at a version less than
100
+ `minimum_fully_supported_version`, this issues a warning using either a
101
+ default `message_template` or one provided by the user. The
102
+ default `message_template` informs the user that they will not receive
103
+ future updates for `consumer_import_package` if
104
+ `dependency_import_package` is somehow pinned to a version lower
105
+ than `minimum_fully_supported_version`.
106
+
107
+ Args:
108
+ consumer_import_package: The import name of the package that
109
+ needs `dependency_import_package`.
110
+ dependency_import_package: The import name of the dependency to check.
111
+ minimum_fully_supported_version: The dependency_import_package version number
112
+ below which a deprecation warning will be logged.
113
+ recommended_version: If provided, the recommended next version, which
114
+ could be higher than `minimum_fully_supported_version`.
115
+ message_template: A custom default message template to replace
116
+ the default. This `message_template` is treated as an
117
+ f-string, where the following variables are defined:
118
+ `dependency_import_package`, `consumer_import_package` and
119
+ `dependency_distribution_package` and
120
+ `consumer_distribution_package` and `dependency_package`,
121
+ `consumer_package` , which contain the import packages, the
122
+ distribution packages, and pretty string with both the
123
+ distribution and import packages for the dependency and the
124
+ consumer, respectively; and `minimum_fully_supported_version`,
125
+ `version_used`, and `version_used_string`, which refer to supported
126
+ and currently-used versions of the dependency.
127
+
128
+ """
129
+ if (
130
+ not consumer_import_package
131
+ or not dependency_import_package
132
+ or not minimum_fully_supported_version
133
+ ): # pragma: NO COVER
134
+ return
135
+ dependency_version = get_dependency_version(dependency_import_package)
136
+ if not dependency_version.version:
137
+ return
138
+ if dependency_version.version < parse_version(minimum_fully_supported_version):
139
+ (
140
+ dependency_package,
141
+ dependency_distribution_package,
142
+ ) = _get_distribution_and_import_packages(dependency_import_package)
143
+ (
144
+ consumer_package,
145
+ consumer_distribution_package,
146
+ ) = _get_distribution_and_import_packages(consumer_import_package)
147
+
148
+ recommendation = (
149
+ " (we recommend {recommended_version})" if recommended_version else ""
150
+ )
151
+ message_template = message_template or _flatten_message(
152
+ """
153
+ DEPRECATION: Package {consumer_package} depends on
154
+ {dependency_package}, currently installed at version
155
+ {version_used_string}. Future updates to
156
+ {consumer_package} will require {dependency_package} at
157
+ version {minimum_fully_supported_version} or
158
+ higher{recommendation}. Please ensure that either (a) your
159
+ Python environment doesn't pin the version of
160
+ {dependency_package}, so that updates to
161
+ {consumer_package} can require the higher version, or (b)
162
+ you manually update your Python environment to use at
163
+ least version {minimum_fully_supported_version} of
164
+ {dependency_package}.
165
+ """
166
+ )
167
+ warnings.warn(
168
+ message_template.format(
169
+ consumer_import_package=consumer_import_package,
170
+ dependency_import_package=dependency_import_package,
171
+ consumer_distribution_package=consumer_distribution_package,
172
+ dependency_distribution_package=dependency_distribution_package,
173
+ dependency_package=dependency_package,
174
+ consumer_package=consumer_package,
175
+ minimum_fully_supported_version=minimum_fully_supported_version,
176
+ recommendation=recommendation,
177
+ version_used=dependency_version.version,
178
+ version_used_string=dependency_version.version_string,
179
+ ),
180
+ FutureWarning,
181
+ )
182
+
183
+
184
+ def check_dependency_versions(
185
+ consumer_import_package: str, *package_dependency_warnings: DependencyConstraint
186
+ ):
187
+ """Bundle checks for all package dependencies.
188
+
189
+ This function can be called by all consumers of google.api_core,
190
+ to emit needed deprecation warnings for any of their
191
+ dependencies. The dependencies to check can be passed as arguments, or if
192
+ none are provided, it will default to the list in
193
+ `_PACKAGE_DEPENDENCY_WARNINGS`.
194
+
195
+ Args:
196
+ consumer_import_package: The distribution name of the calling package, whose
197
+ dependencies we're checking.
198
+ *package_dependency_warnings: A variable number of DependencyConstraint
199
+ objects, each specifying a dependency to check.
200
+ """
201
+ if not package_dependency_warnings:
202
+ package_dependency_warnings = tuple(_PACKAGE_DEPENDENCY_WARNINGS)
203
+ for package_info in package_dependency_warnings:
204
+ warn_deprecation_for_versions_less_than(
205
+ consumer_import_package,
206
+ package_info.package_name,
207
+ package_info.minimum_fully_supported_version,
208
+ recommended_version=package_info.recommended_version,
209
+ )
@@ -0,0 +1,269 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Code to check Python versions supported by Google Cloud Client Libraries."""
16
+
17
+ import datetime
18
+ import enum
19
+ import warnings
20
+ import sys
21
+ import textwrap
22
+ from typing import Any, List, NamedTuple, Optional, Dict, Tuple
23
+
24
+
25
+ class PythonVersionStatus(enum.Enum):
26
+ """Support status of a Python version in this client library artifact release.
27
+
28
+ "Support", in this context, means that this release of a client library
29
+ artifact is configured to run on the currently configured version of
30
+ Python.
31
+ """
32
+
33
+ PYTHON_VERSION_STATUS_UNSPECIFIED = "PYTHON_VERSION_STATUS_UNSPECIFIED"
34
+
35
+ PYTHON_VERSION_SUPPORTED = "PYTHON_VERSION_SUPPORTED"
36
+ """This Python version is fully supported, so the artifact running on this
37
+ version will have all features and bug fixes."""
38
+
39
+ PYTHON_VERSION_DEPRECATED = "PYTHON_VERSION_DEPRECATED"
40
+ """This Python version is still supported, but support will end within a
41
+ year. At that time, there will be no more releases for this artifact
42
+ running under this Python version."""
43
+
44
+ PYTHON_VERSION_EOL = "PYTHON_VERSION_EOL"
45
+ """This Python version has reached its end of life in the Python community
46
+ (see https://devguide.python.org/versions/), and this artifact will cease
47
+ supporting this Python version within the next few releases."""
48
+
49
+ PYTHON_VERSION_UNSUPPORTED = "PYTHON_VERSION_UNSUPPORTED"
50
+ """This release of the client library artifact may not be the latest, since
51
+ current releases no longer support this Python version."""
52
+
53
+
54
+ class VersionInfo(NamedTuple):
55
+ """Hold release and support date information for a Python version."""
56
+
57
+ version: str
58
+ python_beta: Optional[datetime.date]
59
+ python_start: datetime.date
60
+ python_eol: datetime.date
61
+ gapic_start: Optional[datetime.date] = None # unused
62
+ gapic_deprecation: Optional[datetime.date] = None
63
+ gapic_end: Optional[datetime.date] = None
64
+ dep_unpatchable_cve: Optional[datetime.date] = None # unused
65
+
66
+
67
+ PYTHON_VERSIONS: List[VersionInfo] = [
68
+ # Refer to https://devguide.python.org/versions/ and the PEPs linked therefrom.
69
+ VersionInfo(
70
+ version="3.7",
71
+ python_beta=None,
72
+ python_start=datetime.date(2018, 6, 27),
73
+ python_eol=datetime.date(2023, 6, 27),
74
+ ),
75
+ VersionInfo(
76
+ version="3.8",
77
+ python_beta=None,
78
+ python_start=datetime.date(2019, 10, 14),
79
+ python_eol=datetime.date(2024, 10, 7),
80
+ ),
81
+ VersionInfo(
82
+ version="3.9",
83
+ python_beta=datetime.date(2020, 5, 18),
84
+ python_start=datetime.date(2020, 10, 5),
85
+ python_eol=datetime.date(2025, 10, 5),
86
+ gapic_end=datetime.date(2025, 10, 5) + datetime.timedelta(days=90),
87
+ ),
88
+ VersionInfo(
89
+ version="3.10",
90
+ python_beta=datetime.date(2021, 5, 3),
91
+ python_start=datetime.date(2021, 10, 4),
92
+ python_eol=datetime.date(2026, 10, 4), # TODO: specify day when announced
93
+ ),
94
+ VersionInfo(
95
+ version="3.11",
96
+ python_beta=datetime.date(2022, 5, 8),
97
+ python_start=datetime.date(2022, 10, 24),
98
+ python_eol=datetime.date(2027, 10, 24), # TODO: specify day when announced
99
+ ),
100
+ VersionInfo(
101
+ version="3.12",
102
+ python_beta=datetime.date(2023, 5, 22),
103
+ python_start=datetime.date(2023, 10, 2),
104
+ python_eol=datetime.date(2028, 10, 2), # TODO: specify day when announced
105
+ ),
106
+ VersionInfo(
107
+ version="3.13",
108
+ python_beta=datetime.date(2024, 5, 8),
109
+ python_start=datetime.date(2024, 10, 7),
110
+ python_eol=datetime.date(2029, 10, 7), # TODO: specify day when announced
111
+ ),
112
+ VersionInfo(
113
+ version="3.14",
114
+ python_beta=datetime.date(2025, 5, 7),
115
+ python_start=datetime.date(2025, 10, 7),
116
+ python_eol=datetime.date(2030, 10, 7), # TODO: specify day when announced
117
+ ),
118
+ ]
119
+
120
+ PYTHON_VERSION_INFO: Dict[Tuple[int, int], VersionInfo] = {}
121
+ for info in PYTHON_VERSIONS:
122
+ major, minor = map(int, info.version.split("."))
123
+ PYTHON_VERSION_INFO[(major, minor)] = info
124
+
125
+
126
+ LOWEST_TRACKED_VERSION = min(PYTHON_VERSION_INFO.keys())
127
+ _FAKE_PAST_DATE = datetime.date.min + datetime.timedelta(days=900)
128
+ _FAKE_PAST_VERSION = VersionInfo(
129
+ version="0.0",
130
+ python_beta=_FAKE_PAST_DATE,
131
+ python_start=_FAKE_PAST_DATE,
132
+ python_eol=_FAKE_PAST_DATE,
133
+ )
134
+ _FAKE_FUTURE_DATE = datetime.date.max - datetime.timedelta(days=900)
135
+ _FAKE_FUTURE_VERSION = VersionInfo(
136
+ version="999.0",
137
+ python_beta=_FAKE_FUTURE_DATE,
138
+ python_start=_FAKE_FUTURE_DATE,
139
+ python_eol=_FAKE_FUTURE_DATE,
140
+ )
141
+ DEPRECATION_WARNING_PERIOD = datetime.timedelta(days=365)
142
+ EOL_GRACE_PERIOD = datetime.timedelta(weeks=1)
143
+
144
+
145
+ def _flatten_message(text: str) -> str:
146
+ """Dedent a multi-line string and flatten it into a single line."""
147
+ return " ".join(textwrap.dedent(text).strip().split())
148
+
149
+
150
+ # TODO(https://github.com/googleapis/python-api-core/issues/835): Remove once we
151
+ # no longer support Python 3.7
152
+ if sys.version_info < (3, 8):
153
+
154
+ def _get_pypi_package_name(module_name): # pragma: NO COVER
155
+ """Determine the PyPI package name for a given module name."""
156
+ return None
157
+
158
+ else:
159
+ from importlib import metadata
160
+
161
+ def _get_pypi_package_name(module_name):
162
+ """Determine the PyPI package name for a given module name."""
163
+ try:
164
+ # Get the mapping of modules to distributions
165
+ module_to_distributions = metadata.packages_distributions()
166
+
167
+ # Check if the module is found in the mapping
168
+ if module_name in module_to_distributions: # pragma: NO COVER
169
+ # The value is a list of distribution names, take the first one
170
+ return module_to_distributions[module_name][0]
171
+ else:
172
+ return None # Module not found in the mapping
173
+ except Exception as e:
174
+ print(f"An error occurred: {e}")
175
+ return None
176
+
177
+
178
+ def _get_distribution_and_import_packages(import_package: str) -> Tuple[str, Any]:
179
+ """Return a pretty string with distribution & import package names."""
180
+ distribution_package = _get_pypi_package_name(import_package)
181
+ dependency_distribution_and_import_packages = (
182
+ f"package {distribution_package} ({import_package})"
183
+ if distribution_package
184
+ else import_package
185
+ )
186
+ return dependency_distribution_and_import_packages, distribution_package
187
+
188
+
189
+ def check_python_version(
190
+ package: str = "this package", today: Optional[datetime.date] = None
191
+ ) -> PythonVersionStatus:
192
+ """Check the running Python version and issue a support warning if needed.
193
+
194
+ Args:
195
+ today: The date to check against. Defaults to the current date.
196
+
197
+ Returns:
198
+ The support status of the current Python version.
199
+ """
200
+ today = today or datetime.date.today()
201
+ package_label, _ = _get_distribution_and_import_packages(package)
202
+
203
+ python_version = sys.version_info
204
+ version_tuple = (python_version.major, python_version.minor)
205
+ py_version_str = sys.version.split()[0]
206
+
207
+ version_info = PYTHON_VERSION_INFO.get(version_tuple)
208
+
209
+ if not version_info:
210
+ if version_tuple < LOWEST_TRACKED_VERSION:
211
+ version_info = _FAKE_PAST_VERSION
212
+ else:
213
+ version_info = _FAKE_FUTURE_VERSION
214
+
215
+ gapic_deprecation = version_info.gapic_deprecation or (
216
+ version_info.python_eol - DEPRECATION_WARNING_PERIOD
217
+ )
218
+ gapic_end = version_info.gapic_end or (version_info.python_eol + EOL_GRACE_PERIOD)
219
+
220
+ def min_python(date: datetime.date) -> str:
221
+ """Find the minimum supported Python version for a given date."""
222
+ for version, info in sorted(PYTHON_VERSION_INFO.items()):
223
+ if info.python_start <= date < info.python_eol:
224
+ return f"{version[0]}.{version[1]}"
225
+ return "at a currently supported version [https://devguide.python.org/versions]"
226
+
227
+ if gapic_end < today:
228
+ message = _flatten_message(
229
+ f"""
230
+ You are using a non-supported Python version ({py_version_str}).
231
+ Google will not post any further updates to {package_label}
232
+ supporting this Python version. Please upgrade to the latest Python
233
+ version, or at least Python {min_python(today)}, and then update
234
+ {package_label}.
235
+ """
236
+ )
237
+ warnings.warn(message, FutureWarning)
238
+ return PythonVersionStatus.PYTHON_VERSION_UNSUPPORTED
239
+
240
+ eol_date = version_info.python_eol + EOL_GRACE_PERIOD
241
+ if eol_date <= today <= gapic_end:
242
+ message = _flatten_message(
243
+ f"""
244
+ You are using a Python version ({py_version_str})
245
+ past its end of life. Google will update {package_label}
246
+ with critical bug fixes on a best-effort basis, but not
247
+ with any other fixes or features. Please upgrade
248
+ to the latest Python version, or at least Python
249
+ {min_python(today)}, and then update {package_label}.
250
+ """
251
+ )
252
+ warnings.warn(message, FutureWarning)
253
+ return PythonVersionStatus.PYTHON_VERSION_EOL
254
+
255
+ if gapic_deprecation <= today <= gapic_end:
256
+ message = _flatten_message(
257
+ f"""
258
+ You are using a Python version ({py_version_str}) which Google will
259
+ stop supporting in new releases of {package_label} once it reaches
260
+ its end of life ({version_info.python_eol}). Please upgrade to the
261
+ latest Python version, or at least Python
262
+ {min_python(version_info.python_eol)}, to continue receiving updates
263
+ for {package_label} past that date.
264
+ """
265
+ )
266
+ warnings.warn(message, FutureWarning)
267
+ return PythonVersionStatus.PYTHON_VERSION_DEPRECATED
268
+
269
+ return PythonVersionStatus.PYTHON_VERSION_SUPPORTED
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "2.27.0"
15
+ __version__ = "2.28.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-api-core
3
- Version: 2.27.0
3
+ Version: 2.28.0
4
4
  Summary: Google API client core library
5
5
  Author-email: Google LLC <googleapis-packages@google.com>
6
6
  License: Apache 2.0
@@ -5,6 +5,8 @@ pyproject.toml
5
5
  setup.cfg
6
6
  setup.py
7
7
  google/api_core/__init__.py
8
+ google/api_core/_python_package_support.py
9
+ google/api_core/_python_version_support.py
8
10
  google/api_core/_rest_streaming_base.py
9
11
  google/api_core/bidi.py
10
12
  google/api_core/bidi_async.py
@@ -103,6 +105,8 @@ tests/unit/test_packaging.py
103
105
  tests/unit/test_page_iterator.py
104
106
  tests/unit/test_path_template.py
105
107
  tests/unit/test_protobuf_helpers.py
108
+ tests/unit/test_python_package_support.py
109
+ tests/unit/test_python_version_support.py
106
110
  tests/unit/test_rest_helpers.py
107
111
  tests/unit/test_rest_streaming.py
108
112
  tests/unit/test_timeout.py
@@ -186,7 +186,9 @@ def test_wrap_method_with_overriding_retry_timeout_compression(unused_sleep):
186
186
  assert result == 42
187
187
  assert method.call_count == 2
188
188
  method.assert_called_with(
189
- timeout=22, compression=grpc.Compression.Deflate, metadata=mock.ANY
189
+ timeout=22,
190
+ compression=grpc.Compression.Deflate,
191
+ metadata=mock.ANY,
190
192
  )
191
193
 
192
194
 
@@ -828,7 +828,13 @@ class TestBackgroundConsumer(object):
828
828
  bidi_rpc._start_rpc.side_effect = expected_exception
829
829
 
830
830
  consumer = bidi.BackgroundConsumer(bidi_rpc, on_response=None)
831
+
831
832
  consumer.start()
833
+
834
+ # Wait for the consumer's thread to exit.
835
+ while consumer.is_active:
836
+ pass # pragma: NO COVER
837
+
832
838
  assert callback.call_args.args[0] == grpc.StatusCode.INVALID_ARGUMENT
833
839
 
834
840
  def test_consumer_expected_error(self, caplog):