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

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

35 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 

18import sys 

19from typing import Optional 

20 

21from collections import namedtuple 

22 

23from ._python_version_support import ( 

24 _flatten_message, 

25 _get_distribution_and_import_packages, 

26) 

27 

28from 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. 

32DependencyConstraint = 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 

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

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

47# package. 

48UNKNOWN_VERSION_STRING = "--" 

49 

50 

51def 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 

90def 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 

184def 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 )