google-api-core 2.28.0__tar.gz → 2.29.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.28.0/google_api_core.egg-info → google_api_core-2.29.0}/PKG-INFO +2 -1
  2. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/__init__.py +1 -0
  3. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/_python_package_support.py +45 -20
  4. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/_python_version_support.py +17 -8
  5. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/bidi.py +3 -3
  6. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/bidi_async.py +3 -3
  7. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/grpc_helpers_async.py +1 -1
  8. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operation.py +1 -1
  9. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/abstract_operations_base_client.py +14 -8
  10. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/pagers.py +1 -1
  11. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/pagers_async.py +1 -1
  12. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/pagers_base.py +1 -1
  13. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/transports/base.py +7 -4
  14. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/version.py +1 -1
  15. {google_api_core-2.28.0 → google_api_core-2.29.0/google_api_core.egg-info}/PKG-INFO +2 -1
  16. {google_api_core-2.28.0 → google_api_core-2.29.0}/google_api_core.egg-info/requires.txt +3 -0
  17. {google_api_core-2.28.0 → google_api_core-2.29.0}/pyproject.toml +6 -1
  18. google_api_core-2.29.0/setup.cfg +4 -0
  19. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/gapic/test_method_async.py +1 -1
  20. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/test_bidi_async.py +15 -0
  21. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/test_grpc_helpers_async.py +19 -14
  22. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/test_rest_streaming_async.py +0 -2
  23. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/helpers.py +11 -0
  24. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/gapic/test_method.py +21 -0
  25. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/gapic/test_routing_header.py +2 -2
  26. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/operations_v1/test_operations_rest_client.py +52 -20
  27. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_bidi.py +10 -1
  28. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_client_logging.py +0 -1
  29. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_client_options.py +19 -19
  30. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_grpc_helpers.py +17 -12
  31. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_page_iterator.py +0 -1
  32. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_python_package_support.py +28 -35
  33. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_python_version_support.py +12 -8
  34. google_api_core-2.28.0/setup.cfg +0 -13
  35. {google_api_core-2.28.0 → google_api_core-2.29.0}/LICENSE +0 -0
  36. {google_api_core-2.28.0 → google_api_core-2.29.0}/MANIFEST.in +0 -0
  37. {google_api_core-2.28.0 → google_api_core-2.29.0}/README.rst +0 -0
  38. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/_rest_streaming_base.py +0 -0
  39. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/bidi_base.py +0 -0
  40. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/client_info.py +0 -0
  41. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/client_logging.py +0 -0
  42. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/client_options.py +0 -0
  43. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/datetime_helpers.py +0 -0
  44. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/exceptions.py +0 -0
  45. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/extended_operation.py +0 -0
  46. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/future/__init__.py +0 -0
  47. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/future/_helpers.py +0 -0
  48. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/future/async_future.py +0 -0
  49. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/future/base.py +0 -0
  50. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/future/polling.py +0 -0
  51. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/gapic_v1/__init__.py +0 -0
  52. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/gapic_v1/client_info.py +0 -0
  53. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/gapic_v1/config.py +0 -0
  54. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/gapic_v1/config_async.py +0 -0
  55. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/gapic_v1/method.py +0 -0
  56. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/gapic_v1/method_async.py +0 -0
  57. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/gapic_v1/routing_header.py +0 -0
  58. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/general_helpers.py +0 -0
  59. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/grpc_helpers.py +0 -0
  60. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/iam.py +0 -0
  61. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operation_async.py +0 -0
  62. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/__init__.py +0 -0
  63. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/abstract_operations_client.py +0 -0
  64. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/operations_async_client.py +0 -0
  65. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/operations_client.py +0 -0
  66. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/operations_client_config.py +0 -0
  67. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/operations_rest_client_async.py +0 -0
  68. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/transports/__init__.py +0 -0
  69. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/transports/rest.py +0 -0
  70. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/operations_v1/transports/rest_asyncio.py +0 -0
  71. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/page_iterator.py +0 -0
  72. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/page_iterator_async.py +0 -0
  73. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/path_template.py +0 -0
  74. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/protobuf_helpers.py +0 -0
  75. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/py.typed +0 -0
  76. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/rest_helpers.py +0 -0
  77. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/rest_streaming.py +0 -0
  78. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/rest_streaming_async.py +0 -0
  79. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/retry/__init__.py +0 -0
  80. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/retry/retry_base.py +0 -0
  81. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/retry/retry_streaming.py +0 -0
  82. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/retry/retry_streaming_async.py +0 -0
  83. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/retry/retry_unary.py +0 -0
  84. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/retry/retry_unary_async.py +0 -0
  85. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/retry_async.py +0 -0
  86. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/timeout.py +0 -0
  87. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/universe.py +0 -0
  88. {google_api_core-2.28.0 → google_api_core-2.29.0}/google/api_core/version_header.py +0 -0
  89. {google_api_core-2.28.0 → google_api_core-2.29.0}/google_api_core.egg-info/SOURCES.txt +0 -0
  90. {google_api_core-2.28.0 → google_api_core-2.29.0}/google_api_core.egg-info/dependency_links.txt +0 -0
  91. {google_api_core-2.28.0 → google_api_core-2.29.0}/google_api_core.egg-info/top_level.txt +0 -0
  92. {google_api_core-2.28.0 → google_api_core-2.29.0}/setup.py +0 -0
  93. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/__init__.py +0 -0
  94. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/__init__.py +0 -0
  95. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/future/__init__.py +0 -0
  96. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/future/test_async_future.py +0 -0
  97. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/gapic/test_config_async.py +0 -0
  98. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/operations_v1/__init__.py +0 -0
  99. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/operations_v1/test_operations_async_client.py +0 -0
  100. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/retry/__init__.py +0 -0
  101. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/retry/test_retry_streaming_async.py +0 -0
  102. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/retry/test_retry_unary_async.py +0 -0
  103. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/test_operation_async.py +0 -0
  104. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/asyncio/test_page_iterator_async.py +0 -0
  105. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/__init__.py +0 -0
  106. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/future/__init__.py +0 -0
  107. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/future/test__helpers.py +0 -0
  108. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/future/test_polling.py +0 -0
  109. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/gapic/test_client_info.py +0 -0
  110. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/gapic/test_config.py +0 -0
  111. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/operations_v1/__init__.py +0 -0
  112. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/operations_v1/test_operations_client.py +0 -0
  113. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/retry/__init__.py +0 -0
  114. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/retry/test_retry_base.py +0 -0
  115. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/retry/test_retry_imports.py +0 -0
  116. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/retry/test_retry_streaming.py +0 -0
  117. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/retry/test_retry_unary.py +0 -0
  118. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_client_info.py +0 -0
  119. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_datetime_helpers.py +0 -0
  120. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_exceptions.py +0 -0
  121. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_extended_operation.py +0 -0
  122. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_iam.py +0 -0
  123. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_operation.py +0 -0
  124. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_packaging.py +0 -0
  125. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_path_template.py +0 -0
  126. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_protobuf_helpers.py +0 -0
  127. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_rest_helpers.py +0 -0
  128. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_rest_streaming.py +0 -0
  129. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_timeout.py +0 -0
  130. {google_api_core-2.28.0 → google_api_core-2.29.0}/tests/unit/test_universe.py +0 -0
  131. {google_api_core-2.28.0 → google_api_core-2.29.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.28.0
3
+ Version: 2.29.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
@@ -30,6 +30,7 @@ Requires-Dist: proto-plus<2.0.0,>=1.22.3
30
30
  Requires-Dist: proto-plus<2.0.0,>=1.25.0; python_version >= "3.13"
31
31
  Requires-Dist: google-auth<3.0.0,>=2.14.1
32
32
  Requires-Dist: requests<3.0.0,>=2.18.0
33
+ Requires-Dist: importlib_metadata>=1.4; python_version < "3.8"
33
34
  Provides-Extra: async-rest
34
35
  Requires-Dist: google-auth[aiohttp]<3.0.0,>=2.35.0; extra == "async-rest"
35
36
  Provides-Extra: grpc
@@ -30,6 +30,7 @@ __version__ = api_core_version.__version__
30
30
  # expose dependency checks for external callers
31
31
  check_python_version = _python_version_support.check_python_version
32
32
  check_dependency_versions = _python_package_support.check_dependency_versions
33
+ parse_version_to_tuple = _python_package_support.parse_version_to_tuple
33
34
  warn_deprecation_for_versions_less_than = (
34
35
  _python_package_support.warn_deprecation_for_versions_less_than
35
36
  )
@@ -16,7 +16,7 @@
16
16
 
17
17
  import warnings
18
18
  import sys
19
- from typing import Optional
19
+ from typing import Optional, Tuple
20
20
 
21
21
  from collections import namedtuple
22
22
 
@@ -25,7 +25,14 @@ from ._python_version_support import (
25
25
  _get_distribution_and_import_packages,
26
26
  )
27
27
 
28
- from packaging.version import parse as parse_version
28
+ if sys.version_info >= (3, 8):
29
+ from importlib import metadata
30
+ else:
31
+ # TODO(https://github.com/googleapis/python-api-core/issues/835): Remove
32
+ # this code path once we drop support for Python 3.7
33
+ import importlib_metadata as metadata
34
+
35
+ ParsedVersion = Tuple[int, ...]
29
36
 
30
37
  # Here we list all the packages for which we want to issue warnings
31
38
  # about deprecated and unsupported versions.
@@ -48,42 +55,56 @@ DependencyVersion = namedtuple("DependencyVersion", ["version", "version_string"
48
55
  UNKNOWN_VERSION_STRING = "--"
49
56
 
50
57
 
58
+ def parse_version_to_tuple(version_string: str) -> ParsedVersion:
59
+ """Safely converts a semantic version string to a comparable tuple of integers.
60
+
61
+ Example: "4.25.8" -> (4, 25, 8)
62
+ Ignores non-numeric parts and handles common version formats.
63
+
64
+ Args:
65
+ version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
66
+
67
+ Returns:
68
+ Tuple of integers for the parsed version string.
69
+ """
70
+ parts = []
71
+ for part in version_string.split("."):
72
+ try:
73
+ parts.append(int(part))
74
+ except ValueError:
75
+ # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
76
+ # This is a simplification compared to 'packaging.parse_version', but sufficient
77
+ # for comparing strictly numeric semantic versions.
78
+ break
79
+ return tuple(parts)
80
+
81
+
51
82
  def get_dependency_version(
52
83
  dependency_name: str,
53
84
  ) -> DependencyVersion:
54
85
  """Get the parsed version of an installed package dependency.
55
86
 
56
87
  This function checks for an installed package and returns its version
57
- as a `packaging.version.Version` object for safe comparison. It handles
88
+ as a comparable tuple of integers object for safe comparison. It handles
58
89
  both modern (Python 3.8+) and legacy (Python 3.7) environments.
59
90
 
60
91
  Args:
61
92
  dependency_name: The distribution name of the package (e.g., 'requests').
62
93
 
63
94
  Returns:
64
- A DependencyVersion namedtuple with `version` and
95
+ A DependencyVersion namedtuple with `version` (a tuple of integers) and
65
96
  `version_string` attributes, or `DependencyVersion(None,
66
97
  UNKNOWN_VERSION_STRING)` if the package is not found or
67
98
  another error occurs during version discovery.
68
99
 
69
100
  """
70
101
  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
-
102
+ version_string: str = metadata.version(dependency_name)
103
+ parsed_version = parse_version_to_tuple(version_string)
104
+ return DependencyVersion(parsed_version, version_string)
86
105
  except Exception:
106
+ # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
107
+ # or errors during parse_version_to_tuple
87
108
  return DependencyVersion(None, UNKNOWN_VERSION_STRING)
88
109
 
89
110
 
@@ -132,10 +153,14 @@ def warn_deprecation_for_versions_less_than(
132
153
  or not minimum_fully_supported_version
133
154
  ): # pragma: NO COVER
134
155
  return
156
+
135
157
  dependency_version = get_dependency_version(dependency_import_package)
136
158
  if not dependency_version.version:
137
159
  return
138
- if dependency_version.version < parse_version(minimum_fully_supported_version):
160
+
161
+ if dependency_version.version < parse_version_to_tuple(
162
+ minimum_fully_supported_version
163
+ ):
139
164
  (
140
165
  dependency_package,
141
166
  dependency_distribution_package,
@@ -16,12 +16,16 @@
16
16
 
17
17
  import datetime
18
18
  import enum
19
+ import logging
19
20
  import warnings
20
21
  import sys
21
22
  import textwrap
22
23
  from typing import Any, List, NamedTuple, Optional, Dict, Tuple
23
24
 
24
25
 
26
+ _LOGGER = logging.getLogger(__name__)
27
+
28
+
25
29
  class PythonVersionStatus(enum.Enum):
26
30
  """Support status of a Python version in this client library artifact release.
27
31
 
@@ -147,9 +151,11 @@ def _flatten_message(text: str) -> str:
147
151
  return " ".join(textwrap.dedent(text).strip().split())
148
152
 
149
153
 
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):
154
+ # TODO(https://github.com/googleapis/python-api-core/issues/835):
155
+ # Remove once we no longer support Python 3.9.
156
+ # `importlib.metadata.packages_distributions()` is only supported in Python 3.10 and newer
157
+ # https://docs.python.org/3/library/importlib.metadata.html#importlib.metadata.packages_distributions
158
+ if sys.version_info < (3, 10):
153
159
 
154
160
  def _get_pypi_package_name(module_name): # pragma: NO COVER
155
161
  """Determine the PyPI package name for a given module name."""
@@ -168,11 +174,14 @@ else:
168
174
  if module_name in module_to_distributions: # pragma: NO COVER
169
175
  # The value is a list of distribution names, take the first one
170
176
  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
177
+ except Exception as e: # pragma: NO COVER
178
+ _LOGGER.info(
179
+ "An error occurred while determining PyPI package name for %s: %s",
180
+ module_name,
181
+ e,
182
+ )
183
+
184
+ return None
176
185
 
177
186
 
178
187
  def _get_distribution_and_import_packages(import_package: str) -> Tuple[str, Any]:
@@ -281,11 +281,11 @@ class BidiRpc(BidiRpcBase):
281
281
 
282
282
  def close(self):
283
283
  """Closes the stream."""
284
- if self.call is None:
285
- return
284
+ if self.call is not None:
285
+ self.call.cancel()
286
286
 
287
+ # Put None in request queue to signal termination.
287
288
  self._request_queue.put(None)
288
- self.call.cancel()
289
289
  self._request_generator = None
290
290
  self._initial_request = None
291
291
  self._callbacks = []
@@ -197,11 +197,11 @@ class AsyncBidiRpc(BidiRpcBase):
197
197
 
198
198
  async def close(self) -> None:
199
199
  """Closes the stream."""
200
- if self.call is None:
201
- return
200
+ if self.call is not None:
201
+ self.call.cancel()
202
202
 
203
+ # Put None in request queue to signal termination.
203
204
  await self._request_queue.put(None)
204
- self.call.cancel()
205
205
  self._request_generator = None
206
206
  self._initial_request = None
207
207
  self._callbacks = []
@@ -220,7 +220,7 @@ def create_channel(
220
220
  default_host=None,
221
221
  compression=None,
222
222
  attempt_direct_path: Optional[bool] = False,
223
- **kwargs
223
+ **kwargs,
224
224
  ):
225
225
  """Create an AsyncIO secure channel with credentials.
226
226
 
@@ -78,7 +78,7 @@ class Operation(polling.PollingFuture):
78
78
  result_type,
79
79
  metadata_type=None,
80
80
  polling=polling.DEFAULT_POLLING,
81
- **kwargs
81
+ **kwargs,
82
82
  ):
83
83
  super(Operation, self).__init__(polling=polling, **kwargs)
84
84
  self._operation = operation
@@ -300,16 +300,22 @@ class AbstractOperationsBaseClient(metaclass=AbstractOperationsBaseClientMeta):
300
300
  client_options = client_options_lib.ClientOptions()
301
301
 
302
302
  # Create SSL credentials for mutual TLS if needed.
303
- use_client_cert = os.getenv(
304
- "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
305
- ).lower()
306
- if use_client_cert not in ("true", "false"):
307
- raise ValueError(
308
- "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
309
- )
303
+ if hasattr(mtls, "should_use_client_cert"):
304
+ use_client_cert = mtls.should_use_client_cert()
305
+ else:
306
+ # if unsupported, fallback to reading from env var
307
+ use_client_cert_str = os.getenv(
308
+ "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
309
+ ).lower()
310
+ if use_client_cert_str not in ("true", "false"):
311
+ raise ValueError(
312
+ "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
313
+ " either `true` or `false`"
314
+ )
315
+ use_client_cert = use_client_cert_str == "true"
310
316
  client_cert_source_func = None
311
317
  is_mtls = False
312
- if use_client_cert == "true":
318
+ if use_client_cert:
313
319
  if client_options.client_cert_source:
314
320
  is_mtls = True
315
321
  client_cert_source_func = client_options.client_cert_source
@@ -48,7 +48,7 @@ class ListOperationsPager(ListOperationsPagerBase):
48
48
  request: operations_pb2.ListOperationsRequest,
49
49
  response: operations_pb2.ListOperationsResponse,
50
50
  *,
51
- metadata: Sequence[Tuple[str, str]] = ()
51
+ metadata: Sequence[Tuple[str, str]] = (),
52
52
  ):
53
53
  super().__init__(
54
54
  method=method, request=request, response=response, metadata=metadata
@@ -48,7 +48,7 @@ class ListOperationsAsyncPager(ListOperationsPagerBase):
48
48
  request: operations_pb2.ListOperationsRequest,
49
49
  response: operations_pb2.ListOperationsResponse,
50
50
  *,
51
- metadata: Sequence[Tuple[str, str]] = ()
51
+ metadata: Sequence[Tuple[str, str]] = (),
52
52
  ):
53
53
  super().__init__(
54
54
  method=method, request=request, response=response, metadata=metadata
@@ -47,7 +47,7 @@ class ListOperationsPagerBase:
47
47
  request: operations_pb2.ListOperationsRequest,
48
48
  response: operations_pb2.ListOperationsResponse,
49
49
  *,
50
- metadata: Sequence[Tuple[str, str]] = ()
50
+ metadata: Sequence[Tuple[str, str]] = (),
51
51
  ):
52
52
  """Instantiate the pager.
53
53
 
@@ -119,8 +119,6 @@ class OperationsTransport(abc.ABC):
119
119
  host += ":443" # pragma: NO COVER
120
120
  self._host = host
121
121
 
122
- scopes_kwargs = {"scopes": scopes, "default_scopes": self.AUTH_SCOPES}
123
-
124
122
  # Save the scopes.
125
123
  self._scopes = scopes
126
124
 
@@ -133,12 +131,17 @@ class OperationsTransport(abc.ABC):
133
131
 
134
132
  if credentials_file is not None:
135
133
  credentials, _ = google.auth.load_credentials_from_file(
136
- credentials_file, **scopes_kwargs, quota_project_id=quota_project_id
134
+ credentials_file,
135
+ scopes=scopes,
136
+ quota_project_id=quota_project_id,
137
+ default_scopes=self.AUTH_SCOPES,
137
138
  )
138
139
 
139
140
  elif credentials is None:
140
141
  credentials, _ = google.auth.default(
141
- **scopes_kwargs, quota_project_id=quota_project_id
142
+ scopes=scopes,
143
+ quota_project_id=quota_project_id,
144
+ default_scopes=self.AUTH_SCOPES,
142
145
  )
143
146
 
144
147
  # If the credentials are service account credentials, then always try to use self signed JWT.
@@ -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.28.0"
15
+ __version__ = "2.29.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-api-core
3
- Version: 2.28.0
3
+ Version: 2.29.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
@@ -30,6 +30,7 @@ Requires-Dist: proto-plus<2.0.0,>=1.22.3
30
30
  Requires-Dist: proto-plus<2.0.0,>=1.25.0; python_version >= "3.13"
31
31
  Requires-Dist: google-auth<3.0.0,>=2.14.1
32
32
  Requires-Dist: requests<3.0.0,>=2.18.0
33
+ Requires-Dist: importlib_metadata>=1.4; python_version < "3.8"
33
34
  Provides-Extra: async-rest
34
35
  Requires-Dist: google-auth[aiohttp]<3.0.0,>=2.35.0; extra == "async-rest"
35
36
  Provides-Extra: grpc
@@ -4,6 +4,9 @@ proto-plus<2.0.0,>=1.22.3
4
4
  google-auth<3.0.0,>=2.14.1
5
5
  requests<3.0.0,>=2.18.0
6
6
 
7
+ [:python_version < "3.8"]
8
+ importlib_metadata>=1.4
9
+
7
10
  [:python_version >= "3.13"]
8
11
  proto-plus<2.0.0,>=1.25.0
9
12
 
@@ -51,6 +51,9 @@ dependencies = [
51
51
  "proto-plus >= 1.25.0, < 2.0.0; python_version >= '3.13'",
52
52
  "google-auth >= 2.14.1, < 3.0.0",
53
53
  "requests >= 2.18.0, < 3.0.0",
54
+ # TODO(https://github.com/googleapis/python-api-core/issues/835): Remove
55
+ # `importlib_metadata` once we drop support for Python 3.7
56
+ "importlib_metadata>=1.4; python_version<'3.8'",
54
57
  ]
55
58
  dynamic = ["version"]
56
59
 
@@ -80,7 +83,7 @@ version = { attr = "google.api_core.version.__version__" }
80
83
  include = ["google*"]
81
84
 
82
85
  [tool.mypy]
83
- python_version = "3.7"
86
+ python_version = "3.14"
84
87
  namespace_packages = true
85
88
  ignore_missing_imports = true
86
89
 
@@ -88,6 +91,8 @@ ignore_missing_imports = true
88
91
  filterwarnings = [
89
92
  # treat all warnings as errors
90
93
  "error",
94
+ # Prevent Python version warnings from interfering with tests
95
+ "ignore:.* Python version .*:FutureWarning",
91
96
  # Remove once https://github.com/pytest-dev/pytest-cov/issues/621 is fixed
92
97
  "ignore:.*The --rsyncdir command line argument and rsyncdirs config variable are deprecated:DeprecationWarning",
93
98
  # Remove once https://github.com/protocolbuffers/protobuf/issues/12186 is fixed
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -260,7 +260,7 @@ async def test_wrap_method_with_overriding_timeout_as_a_number():
260
260
  actual_timeout = method.call_args[1]["timeout"]
261
261
  metadata = method.call_args[1]["metadata"]
262
262
  assert metadata == mock.ANY
263
- assert actual_timeout == pytest.approx(22, abs=0.01)
263
+ assert actual_timeout == pytest.approx(22, abs=0.05)
264
264
 
265
265
 
266
266
  @pytest.mark.asyncio
@@ -255,6 +255,21 @@ class TestAsyncBidiRpc:
255
255
  assert bidi_rpc._initial_request is None
256
256
  assert not bidi_rpc._callbacks
257
257
 
258
+ @pytest.mark.asyncio
259
+ async def test_close_with_no_rpc(self):
260
+ bidi_rpc = bidi_async.AsyncBidiRpc(None)
261
+
262
+ await bidi_rpc.close()
263
+
264
+ assert bidi_rpc.call is None
265
+ assert bidi_rpc.is_active is False
266
+ # ensure the request queue was signaled to stop.
267
+ assert bidi_rpc.pending_requests == 1
268
+ assert await bidi_rpc._request_queue.get() is None
269
+ # ensure request and callbacks are cleaned up
270
+ assert bidi_rpc._initial_request is None
271
+ assert not bidi_rpc._callbacks
272
+
258
273
  @pytest.mark.asyncio
259
274
  async def test_close_no_rpc(self):
260
275
  bidi_rpc = bidi_async.AsyncBidiRpc(None)
@@ -17,6 +17,7 @@ try:
17
17
  from unittest.mock import AsyncMock # pragma: NO COVER # noqa: F401
18
18
  except ImportError: # pragma: NO COVER
19
19
  import mock # type: ignore
20
+ from ..helpers import warn_deprecated_credentials_file
20
21
  import pytest # noqa: I202
21
22
 
22
23
  try:
@@ -522,11 +523,12 @@ def test_create_channel_explicit_with_duplicate_credentials():
522
523
  target = "example:443"
523
524
 
524
525
  with pytest.raises(exceptions.DuplicateCredentialArgs) as excinfo:
525
- grpc_helpers_async.create_channel(
526
- target,
527
- credentials_file="credentials.json",
528
- credentials=mock.sentinel.credentials,
529
- )
526
+ with warn_deprecated_credentials_file():
527
+ grpc_helpers_async.create_channel(
528
+ target,
529
+ credentials_file="credentials.json",
530
+ credentials=mock.sentinel.credentials,
531
+ )
530
532
 
531
533
  assert "mutually exclusive" in str(excinfo.value)
532
534
 
@@ -641,9 +643,10 @@ def test_create_channel_with_credentials_file(
641
643
  credentials_file = "/path/to/credentials/file.json"
642
644
  composite_creds = composite_creds_call.return_value
643
645
 
644
- channel = grpc_helpers_async.create_channel(
645
- target, credentials_file=credentials_file
646
- )
646
+ with warn_deprecated_credentials_file():
647
+ channel = grpc_helpers_async.create_channel(
648
+ target, credentials_file=credentials_file
649
+ )
647
650
 
648
651
  google.auth.load_credentials_from_file.assert_called_once_with(
649
652
  credentials_file, scopes=None, default_scopes=None
@@ -670,9 +673,10 @@ def test_create_channel_with_credentials_file_and_scopes(
670
673
  credentials_file = "/path/to/credentials/file.json"
671
674
  composite_creds = composite_creds_call.return_value
672
675
 
673
- channel = grpc_helpers_async.create_channel(
674
- target, credentials_file=credentials_file, scopes=scopes
675
- )
676
+ with warn_deprecated_credentials_file():
677
+ channel = grpc_helpers_async.create_channel(
678
+ target, credentials_file=credentials_file, scopes=scopes
679
+ )
676
680
 
677
681
  google.auth.load_credentials_from_file.assert_called_once_with(
678
682
  credentials_file, scopes=scopes, default_scopes=None
@@ -699,9 +703,10 @@ def test_create_channel_with_credentials_file_and_default_scopes(
699
703
  credentials_file = "/path/to/credentials/file.json"
700
704
  composite_creds = composite_creds_call.return_value
701
705
 
702
- channel = grpc_helpers_async.create_channel(
703
- target, credentials_file=credentials_file, default_scopes=default_scopes
704
- )
706
+ with warn_deprecated_credentials_file():
707
+ channel = grpc_helpers_async.create_channel(
708
+ target, credentials_file=credentials_file, default_scopes=default_scopes
709
+ )
705
710
 
706
711
  google.auth.load_credentials_from_file.assert_called_once_with(
707
712
  credentials_file, scopes=None, default_scopes=default_scopes
@@ -292,7 +292,6 @@ async def test_next_escaped_characters_in_string(
292
292
  @pytest.mark.asyncio
293
293
  @pytest.mark.parametrize("response_type", [EchoResponse, httpbody_pb2.HttpBody])
294
294
  async def test_next_not_array(response_type):
295
-
296
295
  data = '{"hello": 0}'
297
296
  with mock.patch.object(
298
297
  ResponseMock, "content", return_value=mock_async_gen(data)
@@ -352,7 +351,6 @@ async def test_check_buffer(response_type, return_value):
352
351
  @pytest.mark.asyncio
353
352
  @pytest.mark.parametrize("response_type", [EchoResponse, httpbody_pb2.HttpBody])
354
353
  async def test_next_html(response_type):
355
-
356
354
  data = "<!DOCTYPE html><html></html>"
357
355
  with mock.patch.object(
358
356
  ResponseMock, "content", return_value=mock_async_gen(data)
@@ -14,7 +14,9 @@
14
14
 
15
15
  """Helpers for tests"""
16
16
 
17
+ import functools
17
18
  import logging
19
+ import pytest # noqa: I202
18
20
  from typing import List
19
21
 
20
22
  import proto
@@ -69,3 +71,12 @@ def parse_responses(response_message_cls, all_responses: List[proto.Message]) ->
69
71
  logging.info(f"Sending JSON stream: {json_responses}")
70
72
  ret_val = "[{}]".format(",".join(json_responses))
71
73
  return bytes(ret_val, "utf-8")
74
+
75
+
76
+ warn_deprecated_credentials_file = functools.partial(
77
+ # This is used to test that the auth credentials file deprecation
78
+ # warning is emitted as expected.
79
+ pytest.warns,
80
+ DeprecationWarning,
81
+ match="argument is deprecated because of a potential security risk",
82
+ )
@@ -192,6 +192,7 @@ def test_wrap_method_with_overriding_retry_timeout_compression(unused_sleep):
192
192
  )
193
193
 
194
194
 
195
+ @pytest.mark.skip(reason="Known flaky due to floating point comparison. #866")
195
196
  def test_wrap_method_with_overriding_timeout_as_a_number():
196
197
  method = mock.Mock(spec=["__call__"], return_value=42)
197
198
  default_retry = retry.Retry()
@@ -200,6 +201,8 @@ def test_wrap_method_with_overriding_timeout_as_a_number():
200
201
  method, default_retry, default_timeout
201
202
  )
202
203
 
204
+ # Using "result = wrapped_method(timeout=22)" fails since wrapped_method
205
+ # does floating point calculations that results in 21.987.. instead of 22
203
206
  result = wrapped_method(timeout=22)
204
207
 
205
208
  assert result == 42
@@ -210,6 +213,24 @@ def test_wrap_method_with_overriding_timeout_as_a_number():
210
213
  assert actual_timeout == pytest.approx(22, abs=0.01)
211
214
 
212
215
 
216
+ def test_wrap_method_with_overriding_constant_timeout():
217
+ method = mock.Mock(spec=["__call__"], return_value=42)
218
+ default_retry = retry.Retry()
219
+ default_timeout = timeout.ConstantTimeout(60)
220
+ wrapped_method = google.api_core.gapic_v1.method.wrap_method(
221
+ method, default_retry, default_timeout
222
+ )
223
+
224
+ result = wrapped_method(timeout=timeout.ConstantTimeout(22))
225
+
226
+ assert result == 42
227
+
228
+ actual_timeout = method.call_args[1]["timeout"]
229
+ metadata = method.call_args[1]["metadata"]
230
+ assert metadata == mock.ANY
231
+ assert actual_timeout == 22
232
+
233
+
213
234
  def test_wrap_method_with_call():
214
235
  method = mock.Mock()
215
236
  mock_call = mock.Mock()
@@ -90,8 +90,8 @@ def test__urlencode_param(key, value, expected):
90
90
  def test__urlencode_param_caching_performance():
91
91
  import time
92
92
 
93
- key = "key" * 100
94
- value = "value" * 100
93
+ key = "key" * 10000
94
+ value = "value" * 10000
95
95
  # time with empty cache
96
96
  start_time = time.perf_counter()
97
97
  routing_header._urlencode_param(key, value)