|
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 | 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 | 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, |
|
0 commit comments