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

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

42 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 ["package_name", "minimum_fully_supported_version", "recommended_version"], 

34) 

35_PACKAGE_DEPENDENCY_WARNINGS = [ 

36 DependencyConstraint( 

37 "google.protobuf", 

38 minimum_fully_supported_version="4.25.8", 

39 recommended_version="6.x", 

40 ) 

41] 

42 

43 

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

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

46# package. 

47UNKNOWN_VERSION_STRING = "--" 

48 

49 

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

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

52 

53 Example: "4.25.8" -> (4, 25, 8) 

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

55 

56 Args: 

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

58 

59 Returns: 

60 Tuple of integers for the parsed version string. 

61 """ 

62 parts = [] 

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

64 try: 

65 parts.append(int(part)) 

66 except ValueError: 

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

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

69 # for comparing strictly numeric semantic versions. 

70 break 

71 return tuple(parts) 

72 

73 

74def get_dependency_version( 

75 dependency_name: str, 

76) -> DependencyVersion: 

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

78 

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

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

81 

82 Args: 

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

84 

85 Returns: 

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

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

88 UNKNOWN_VERSION_STRING)` if the package is not found or 

89 another error occurs during version discovery. 

90 

91 """ 

92 try: 

93 version_string: str = metadata.version(dependency_name) 

94 parsed_version = parse_version_to_tuple(version_string) 

95 return DependencyVersion(parsed_version, version_string) 

96 except Exception: 

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

98 # or errors during parse_version_to_tuple 

99 return DependencyVersion(None, UNKNOWN_VERSION_STRING) 

100 

101 

102def warn_deprecation_for_versions_less_than( 

103 consumer_import_package: str, 

104 dependency_import_package: str, 

105 minimum_fully_supported_version: str, 

106 recommended_version: Optional[str] = None, 

107 message_template: Optional[str] = None, 

108): 

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

110 

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

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

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

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

115 future updates for `consumer_import_package` if 

116 `dependency_import_package` is somehow pinned to a version lower 

117 than `minimum_fully_supported_version`. 

118 

119 Args: 

120 consumer_import_package: The import name of the package that 

121 needs `dependency_import_package`. 

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

123 minimum_fully_supported_version: The dependency_import_package version number 

124 below which a deprecation warning will be logged. 

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

126 could be higher than `minimum_fully_supported_version`. 

127 message_template: A custom default message template to replace 

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

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

130 `dependency_import_package`, `consumer_import_package` and 

131 `dependency_distribution_package` and 

132 `consumer_distribution_package` and `dependency_package`, 

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

134 distribution packages, and pretty string with both the 

135 distribution and import packages for the dependency and the 

136 consumer, respectively; and `minimum_fully_supported_version`, 

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

138 and currently-used versions of the dependency. 

139 

140 """ 

141 if ( 

142 not consumer_import_package 

143 or not dependency_import_package 

144 or not minimum_fully_supported_version 

145 ): # pragma: NO COVER 

146 return 

147 

148 dependency_version = get_dependency_version(dependency_import_package) 

149 if not dependency_version.version: 

150 return 

151 

152 if dependency_version.version < parse_version_to_tuple( 

153 minimum_fully_supported_version 

154 ): 

155 ( 

156 dependency_package, 

157 dependency_distribution_package, 

158 ) = _get_distribution_and_import_packages(dependency_import_package) 

159 ( 

160 consumer_package, 

161 consumer_distribution_package, 

162 ) = _get_distribution_and_import_packages(consumer_import_package) 

163 

164 recommendation = ( 

165 " (we recommend {recommended_version})" if recommended_version else "" 

166 ) 

167 message_template = message_template or _flatten_message( 

168 """ 

169 DEPRECATION: Package {consumer_package} depends on 

170 {dependency_package}, currently installed at version 

171 {version_used_string}. Future updates to 

172 {consumer_package} will require {dependency_package} at 

173 version {minimum_fully_supported_version} or 

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

175 Python environment doesn't pin the version of 

176 {dependency_package}, so that updates to 

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

178 you manually update your Python environment to use at 

179 least version {minimum_fully_supported_version} of 

180 {dependency_package}. 

181 """ 

182 ) 

183 warnings.warn( 

184 message_template.format( 

185 consumer_import_package=consumer_import_package, 

186 dependency_import_package=dependency_import_package, 

187 consumer_distribution_package=consumer_distribution_package, 

188 dependency_distribution_package=dependency_distribution_package, 

189 dependency_package=dependency_package, 

190 consumer_package=consumer_package, 

191 minimum_fully_supported_version=minimum_fully_supported_version, 

192 recommendation=recommendation, 

193 version_used=dependency_version.version, 

194 version_used_string=dependency_version.version_string, 

195 ), 

196 FutureWarning, 

197 ) 

198 

199 

200def check_dependency_versions( 

201 consumer_import_package: str, *package_dependency_warnings: DependencyConstraint 

202): 

203 """Bundle checks for all package dependencies. 

204 

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

206 to emit needed deprecation warnings for any of their 

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

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

209 `_PACKAGE_DEPENDENCY_WARNINGS`. 

210 

211 Args: 

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

213 dependencies we're checking. 

214 *package_dependency_warnings: A variable number of DependencyConstraint 

215 objects, each specifying a dependency to check. 

216 """ 

217 if not package_dependency_warnings: 

218 package_dependency_warnings = tuple(_PACKAGE_DEPENDENCY_WARNINGS) 

219 for package_info in package_dependency_warnings: 

220 warn_deprecation_for_versions_less_than( 

221 consumer_import_package, 

222 package_info.package_name, 

223 package_info.minimum_fully_supported_version, 

224 recommended_version=package_info.recommended_version, 

225 )