Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/api_core/_python_package_support.py: 63%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

43 statements  

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 

17import warnings 

18from collections import namedtuple 

19from importlib import metadata 

20from typing import Optional, Tuple 

21 

22from ._python_version_support import ( 

23 _flatten_message, 

24 _get_distribution_and_import_packages, 

25) 

26 

27ParsedVersion = Tuple[int, ...] 

28 

29# Here we list all the packages for which we want to issue warnings 

30# about deprecated and unsupported versions. 

31DependencyConstraint = namedtuple( 

32 "DependencyConstraint", 

33 [ 

34 "package_name", 

35 "minimum_fully_supported_version", 

36 "recommended_version", 

37 "message_template", 

38 ], 

39 defaults=(None, None), 

40) 

41 

42PQC_GRPC_WARNING_TEMPLATE = ( 

43 "Package {consumer_package} depends on {dependency_package}, currently installed at version {version_used_string}. " 

44 "grpcio < 1.83.0 does not support Post-Quantum Cryptography (PQC). " 

45 "Support for non-PQC environments is deprecated. In October 2026, " 

46 "Google Cloud Python packages will raise their minimum requirements " 

47 "(including google-api-core, grpcio, and grpcio-status) to enforce grpcio >= 1.83.0. " 

48 "For more details on Google Cloud's post-quantum security migration, visit: " 

49 "https://cloud.google.com/security/resources/post-quantum-cryptography" 

50) 

51 

52_PACKAGE_DEPENDENCY_WARNINGS = [ 

53 DependencyConstraint( 

54 "google.protobuf", 

55 minimum_fully_supported_version="6.33.5", 

56 recommended_version="6.x", 

57 ), 

58 DependencyConstraint( 

59 "grpcio", 

60 minimum_fully_supported_version="1.83.0", 

61 recommended_version="1.83.x", 

62 message_template=PQC_GRPC_WARNING_TEMPLATE, 

63 ), 

64] 

65 

66 

67DependencyVersion = namedtuple("DependencyVersion", ["version", "version_string"]) 

68# Version string we provide in a DependencyVersion when we can't determine the version of a 

69# package. 

70UNKNOWN_VERSION_STRING = "--" 

71 

72 

73def parse_version_to_tuple(version_string: str) -> ParsedVersion: 

74 """Safely converts a semantic version string to a comparable tuple of integers. 

75 

76 Example: "6.33.5" -> (6, 33, 5) 

77 Ignores non-numeric parts and handles common version formats. 

78 

79 Args: 

80 version_string: Version string in the format "x.y.z" or "x.y.z<suffix>" 

81 

82 Returns: 

83 Tuple of integers for the parsed version string. 

84 """ 

85 parts = [] 

86 for part in version_string.split("."): 

87 try: 

88 parts.append(int(part)) 

89 except ValueError: 

90 # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here. 

91 # This is a simplification compared to 'packaging.parse_version', but sufficient 

92 # for comparing strictly numeric semantic versions. 

93 break 

94 return tuple(parts) 

95 

96 

97def get_dependency_version( 

98 dependency_name: str, 

99) -> DependencyVersion: 

100 """Get the parsed version of an installed package dependency. 

101 

102 This function checks for an installed package and returns its version 

103 as a comparable tuple of integers object for safe comparison. 

104 

105 Args: 

106 dependency_name: The distribution name of the package (e.g., 'requests'). 

107 

108 Returns: 

109 A DependencyVersion namedtuple with `version` (a tuple of integers) and 

110 `version_string` attributes, or `DependencyVersion(None, 

111 UNKNOWN_VERSION_STRING)` if the package is not found or 

112 another error occurs during version discovery. 

113 

114 """ 

115 try: 

116 version_string: str = metadata.version(dependency_name) 

117 parsed_version = parse_version_to_tuple(version_string) 

118 return DependencyVersion(parsed_version, version_string) 

119 except Exception: 

120 # Catch exceptions from metadata.version() (e.g., PackageNotFoundError) 

121 # or errors during parse_version_to_tuple 

122 return DependencyVersion(None, UNKNOWN_VERSION_STRING) 

123 

124 

125def warn_deprecation_for_versions_less_than( 

126 consumer_import_package: str, 

127 dependency_import_package: str, 

128 minimum_fully_supported_version: str, 

129 recommended_version: Optional[str] = None, 

130 message_template: Optional[str] = None, 

131): 

132 """Issue any needed deprecation warnings for `dependency_import_package`. 

133 

134 If `dependency_import_package` is installed at a version less than 

135 `minimum_fully_supported_version`, this issues a warning using either a 

136 default `message_template` or one provided by the user. The 

137 default `message_template` informs the user that they will not receive 

138 future updates for `consumer_import_package` if 

139 `dependency_import_package` is somehow pinned to a version lower 

140 than `minimum_fully_supported_version`. 

141 

142 Args: 

143 consumer_import_package: The import name of the package that 

144 needs `dependency_import_package`. 

145 dependency_import_package: The import name of the dependency to check. 

146 minimum_fully_supported_version: The dependency_import_package version number 

147 below which a deprecation warning will be logged. 

148 recommended_version: If provided, the recommended next version, which 

149 could be higher than `minimum_fully_supported_version`. 

150 message_template: A custom default message template to replace 

151 the default. This `message_template` is treated as an 

152 f-string, where the following variables are defined: 

153 `dependency_import_package`, `consumer_import_package` and 

154 `dependency_distribution_package` and 

155 `consumer_distribution_package` and `dependency_package`, 

156 `consumer_package` , which contain the import packages, the 

157 distribution packages, and pretty string with both the 

158 distribution and import packages for the dependency and the 

159 consumer, respectively; and `minimum_fully_supported_version`, 

160 `version_used`, and `version_used_string`, which refer to supported 

161 and currently-used versions of the dependency. 

162 

163 """ 

164 if ( 

165 not consumer_import_package 

166 or not dependency_import_package 

167 or not minimum_fully_supported_version 

168 ): # pragma: NO COVER 

169 return 

170 

171 dependency_version = get_dependency_version(dependency_import_package) 

172 if not dependency_version.version: 

173 return 

174 

175 if dependency_version.version < parse_version_to_tuple( 

176 minimum_fully_supported_version 

177 ): 

178 ( 

179 dependency_package, 

180 dependency_distribution_package, 

181 ) = _get_distribution_and_import_packages(dependency_import_package) 

182 ( 

183 consumer_package, 

184 consumer_distribution_package, 

185 ) = _get_distribution_and_import_packages(consumer_import_package) 

186 

187 recommendation = ( 

188 f" (we recommend {recommended_version})" if recommended_version else "" 

189 ) 

190 message_template = message_template or _flatten_message( 

191 """ 

192 DEPRECATION: Package {consumer_package} depends on 

193 {dependency_package}, currently installed at version 

194 {version_used_string}. Future updates to 

195 {consumer_package} will require {dependency_package} at 

196 version {minimum_fully_supported_version} or 

197 higher{recommendation}. Please ensure that either (a) your 

198 Python environment doesn't pin the version of 

199 {dependency_package}, so that updates to 

200 {consumer_package} can require the higher version, or (b) 

201 you manually update your Python environment to use at 

202 least version {minimum_fully_supported_version} of 

203 {dependency_package}. 

204 """ 

205 ) 

206 warnings.warn( 

207 message_template.format( 

208 consumer_import_package=consumer_import_package, 

209 dependency_import_package=dependency_import_package, 

210 consumer_distribution_package=consumer_distribution_package, 

211 dependency_distribution_package=dependency_distribution_package, 

212 dependency_package=dependency_package, 

213 consumer_package=consumer_package, 

214 minimum_fully_supported_version=minimum_fully_supported_version, 

215 recommendation=recommendation, 

216 version_used=dependency_version.version, 

217 version_used_string=dependency_version.version_string, 

218 ), 

219 FutureWarning, 

220 ) 

221 

222 

223def check_dependency_versions( 

224 consumer_import_package: str, *package_dependency_warnings: DependencyConstraint 

225): 

226 """Bundle checks for all package dependencies. 

227 

228 This function can be called by all consumers of google.api_core, 

229 to emit needed deprecation warnings for any of their 

230 dependencies. The dependencies to check can be passed as arguments, or if 

231 none are provided, it will default to the list in 

232 `_PACKAGE_DEPENDENCY_WARNINGS`. 

233 

234 Args: 

235 consumer_import_package: The distribution name of the calling package, whose 

236 dependencies we're checking. 

237 *package_dependency_warnings: A variable number of DependencyConstraint 

238 objects, each specifying a dependency to check. 

239 """ 

240 if not package_dependency_warnings: 

241 package_dependency_warnings = tuple(_PACKAGE_DEPENDENCY_WARNINGS) 

242 for package_info in package_dependency_warnings: 

243 warn_deprecation_for_versions_less_than( 

244 consumer_import_package, 

245 package_info.package_name, 

246 package_info.minimum_fully_supported_version, 

247 recommended_version=package_info.recommended_version, 

248 message_template=package_info.message_template, 

249 )