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 typing import Optional, Tuple 

19 

20from collections import namedtuple 

21 

22from ._python_version_support import ( 

23 _flatten_message, 

24 _get_distribution_and_import_packages, 

25) 

26 

27from importlib import metadata 

28 

29ParsedVersion = Tuple[int, ...] 

30 

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

32# about deprecated and unsupported versions. 

33DependencyConstraint = namedtuple( 

34 "DependencyConstraint", 

35 ["package_name", "minimum_fully_supported_version", "recommended_version"], 

36) 

37_PACKAGE_DEPENDENCY_WARNINGS = [ 

38 DependencyConstraint( 

39 "google.protobuf", 

40 minimum_fully_supported_version="4.25.8", 

41 recommended_version="6.x", 

42 ) 

43] 

44 

45 

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

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

48# package. 

49UNKNOWN_VERSION_STRING = "--" 

50 

51 

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

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

54 

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

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

57 

58 Args: 

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

60 

61 Returns: 

62 Tuple of integers for the parsed version string. 

63 """ 

64 parts = [] 

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

66 try: 

67 parts.append(int(part)) 

68 except ValueError: 

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

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

71 # for comparing strictly numeric semantic versions. 

72 break 

73 return tuple(parts) 

74 

75 

76def get_dependency_version( 

77 dependency_name: str, 

78) -> DependencyVersion: 

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

80 

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

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

83 

84 Args: 

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

86 

87 Returns: 

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

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

90 UNKNOWN_VERSION_STRING)` if the package is not found or 

91 another error occurs during version discovery. 

92 

93 """ 

94 try: 

95 version_string: str = metadata.version(dependency_name) 

96 parsed_version = parse_version_to_tuple(version_string) 

97 return DependencyVersion(parsed_version, version_string) 

98 except Exception: 

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

100 # or errors during parse_version_to_tuple 

101 return DependencyVersion(None, UNKNOWN_VERSION_STRING) 

102 

103 

104def warn_deprecation_for_versions_less_than( 

105 consumer_import_package: str, 

106 dependency_import_package: str, 

107 minimum_fully_supported_version: str, 

108 recommended_version: Optional[str] = None, 

109 message_template: Optional[str] = None, 

110): 

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

112 

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

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

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

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

117 future updates for `consumer_import_package` if 

118 `dependency_import_package` is somehow pinned to a version lower 

119 than `minimum_fully_supported_version`. 

120 

121 Args: 

122 consumer_import_package: The import name of the package that 

123 needs `dependency_import_package`. 

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

125 minimum_fully_supported_version: The dependency_import_package version number 

126 below which a deprecation warning will be logged. 

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

128 could be higher than `minimum_fully_supported_version`. 

129 message_template: A custom default message template to replace 

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

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

132 `dependency_import_package`, `consumer_import_package` and 

133 `dependency_distribution_package` and 

134 `consumer_distribution_package` and `dependency_package`, 

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

136 distribution packages, and pretty string with both the 

137 distribution and import packages for the dependency and the 

138 consumer, respectively; and `minimum_fully_supported_version`, 

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

140 and currently-used versions of the dependency. 

141 

142 """ 

143 if ( 

144 not consumer_import_package 

145 or not dependency_import_package 

146 or not minimum_fully_supported_version 

147 ): # pragma: NO COVER 

148 return 

149 

150 dependency_version = get_dependency_version(dependency_import_package) 

151 if not dependency_version.version: 

152 return 

153 

154 if dependency_version.version < parse_version_to_tuple( 

155 minimum_fully_supported_version 

156 ): 

157 ( 

158 dependency_package, 

159 dependency_distribution_package, 

160 ) = _get_distribution_and_import_packages(dependency_import_package) 

161 ( 

162 consumer_package, 

163 consumer_distribution_package, 

164 ) = _get_distribution_and_import_packages(consumer_import_package) 

165 

166 recommendation = ( 

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

168 ) 

169 message_template = message_template or _flatten_message( 

170 """ 

171 DEPRECATION: Package {consumer_package} depends on 

172 {dependency_package}, currently installed at version 

173 {version_used_string}. Future updates to 

174 {consumer_package} will require {dependency_package} at 

175 version {minimum_fully_supported_version} or 

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

177 Python environment doesn't pin the version of 

178 {dependency_package}, so that updates to 

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

180 you manually update your Python environment to use at 

181 least version {minimum_fully_supported_version} of 

182 {dependency_package}. 

183 """ 

184 ) 

185 warnings.warn( 

186 message_template.format( 

187 consumer_import_package=consumer_import_package, 

188 dependency_import_package=dependency_import_package, 

189 consumer_distribution_package=consumer_distribution_package, 

190 dependency_distribution_package=dependency_distribution_package, 

191 dependency_package=dependency_package, 

192 consumer_package=consumer_package, 

193 minimum_fully_supported_version=minimum_fully_supported_version, 

194 recommendation=recommendation, 

195 version_used=dependency_version.version, 

196 version_used_string=dependency_version.version_string, 

197 ), 

198 FutureWarning, 

199 ) 

200 

201 

202def check_dependency_versions( 

203 consumer_import_package: str, *package_dependency_warnings: DependencyConstraint 

204): 

205 """Bundle checks for all package dependencies. 

206 

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

208 to emit needed deprecation warnings for any of their 

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

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

211 `_PACKAGE_DEPENDENCY_WARNINGS`. 

212 

213 Args: 

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

215 dependencies we're checking. 

216 *package_dependency_warnings: A variable number of DependencyConstraint 

217 objects, each specifying a dependency to check. 

218 """ 

219 if not package_dependency_warnings: 

220 package_dependency_warnings = tuple(_PACKAGE_DEPENDENCY_WARNINGS) 

221 for package_info in package_dependency_warnings: 

222 warn_deprecation_for_versions_less_than( 

223 consumer_import_package, 

224 package_info.package_name, 

225 package_info.minimum_fully_supported_version, 

226 recommended_version=package_info.recommended_version, 

227 )