Skip to content
Navigation Menu
{{ message }}
forked from googleapis/python-api-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_python_version_support.py
More file actions
269 lines (228 loc) · 10.1 KB
/
Copy path_python_version_support.py
File metadata and controls
269 lines (228 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Code to check Python versions supported by Google Cloud Client Libraries."""
import datetime
import enum
import warnings
import sys
import textwrap
from typing import Any, List, NamedTuple, Optional, Dict, Tuple
class PythonVersionStatus(enum.Enum):
"""Support status of a Python version in this client library artifact release.
"Support", in this context, means that this release of a client library
artifact is configured to run on the currently configured version of
Python.
"""
PYTHON_VERSION_STATUS_UNSPECIFIED = "PYTHON_VERSION_STATUS_UNSPECIFIED"
PYTHON_VERSION_SUPPORTED = "PYTHON_VERSION_SUPPORTED"
"""This Python version is fully supported, so the artifact running on this
version will have all features and bug fixes."""
PYTHON_VERSION_DEPRECATED = "PYTHON_VERSION_DEPRECATED"
"""This Python version is still supported, but support will end within a
year. At that time, there will be no more releases for this artifact
running under this Python version."""
PYTHON_VERSION_EOL = "PYTHON_VERSION_EOL"
"""This Python version has reached its end of life in the Python community
(see https://devguide.python.org/versions/), and this artifact will cease
supporting this Python version within the next few releases."""
PYTHON_VERSION_UNSUPPORTED = "PYTHON_VERSION_UNSUPPORTED"
"""This release of the client library artifact may not be the latest, since
current releases no longer support this Python version."""
class VersionInfo(NamedTuple):
"""Hold release and support date information for a Python version."""
version: str
python_beta: Optional[datetime.date]
python_start: datetime.date
python_eol: datetime.date
gapic_start: Optional[datetime.date] = None # unused
gapic_deprecation: Optional[datetime.date] = None
gapic_end: Optional[datetime.date] = None
dep_unpatchable_cve: Optional[datetime.date] = None # unused
PYTHON_VERSIONS: List[VersionInfo] = [
# Refer to https://devguide.python.org/versions/ and the PEPs linked therefrom.
VersionInfo(
version="3.7",
python_beta=None,
python_start=datetime.date(2018, 6, 27),
python_eol=datetime.date(2023, 6, 27),
),
VersionInfo(
version="3.8",
python_beta=None,
python_start=datetime.date(2019, 10, 14),
python_eol=datetime.date(2024, 10, 7),
),
VersionInfo(
version="3.9",
python_beta=datetime.date(2020, 5, 18),
python_start=datetime.date(2020, 10, 5),
python_eol=datetime.date(2025, 10, 5),
gapic_end=datetime.date(2025, 10, 5) + datetime.timedelta(days=90),
),
VersionInfo(
version="3.10",
python_beta=datetime.date(2021, 5, 3),
python_start=datetime.date(2021, 10, 4),
python_eol=datetime.date(2026, 10, 4), # TODO: specify day when announced
),
VersionInfo(
version="3.11",
python_beta=datetime.date(2022, 5, 8),
python_start=datetime.date(2022, 10, 24),
python_eol=datetime.date(2027, 10, 24), # TODO: specify day when announced
),
VersionInfo(
version="3.12",
python_beta=datetime.date(2023, 5, 22),
python_start=datetime.date(2023, 10, 2),
python_eol=datetime.date(2028, 10, 2), # TODO: specify day when announced
),
VersionInfo(
version="3.13",
python_beta=datetime.date(2024, 5, 8),
python_start=datetime.date(2024, 10, 7),
python_eol=datetime.date(2029, 10, 7), # TODO: specify day when announced
),
VersionInfo(
version="3.14",
python_beta=datetime.date(2025, 5, 7),
python_start=datetime.date(2025, 10, 7),
python_eol=datetime.date(2030, 10, 7), # TODO: specify day when announced
),
]
PYTHON_VERSION_INFO: Dict[Tuple[int, int], VersionInfo] = {}
for info in PYTHON_VERSIONS:
major, minor = map(int, info.version.split("."))
PYTHON_VERSION_INFO[(major, minor)] = info
LOWEST_TRACKED_VERSION = min(PYTHON_VERSION_INFO.keys())
_FAKE_PAST_DATE = datetime.date.min + datetime.timedelta(days=900)
_FAKE_PAST_VERSION = VersionInfo(
version="0.0",
python_beta=_FAKE_PAST_DATE,
python_start=_FAKE_PAST_DATE,
python_eol=_FAKE_PAST_DATE,
)
_FAKE_FUTURE_DATE = datetime.date.max - datetime.timedelta(days=900)
_FAKE_FUTURE_VERSION = VersionInfo(
version="999.0",
python_beta=_FAKE_FUTURE_DATE,
python_start=_FAKE_FUTURE_DATE,
python_eol=_FAKE_FUTURE_DATE,
)
DEPRECATION_WARNING_PERIOD = datetime.timedelta(days=365)
EOL_GRACE_PERIOD = datetime.timedelta(weeks=1)
def _flatten_message(text: str) -> str:
"""Dedent a multi-line string and flatten it into a single line."""
return " ".join(textwrap.dedent(text).strip().split())
# TODO(https://github.com/googleapis/python-api-core/issues/835): Remove once we
# no longer support Python 3.7
if sys.version_info < (3, 8):
def _get_pypi_package_name(module_name): # pragma: NO COVER
"""Determine the PyPI package name for a given module name."""
return None
else:
from importlib import metadata
def _get_pypi_package_name(module_name):
"""Determine the PyPI package name for a given module name."""
try:
# Get the mapping of modules to distributions
module_to_distributions = metadata.packages_distributions()
# Check if the module is found in the mapping
if module_name in module_to_distributions: # pragma: NO COVER
# The value is a list of distribution names, take the first one
return module_to_distributions[module_name][0]
else:
return None # Module not found in the mapping
except Exception as e:
print(f"An error occurred: {e}")
return None
def _get_distribution_and_import_packages(import_package: str) -> Tuple[str, Any]:
"""Return a pretty string with distribution & import package names."""
distribution_package = _get_pypi_package_name(import_package)
dependency_distribution_and_import_packages = (
f"package {distribution_package} ({import_package})"
if distribution_package
else import_package
)
return dependency_distribution_and_import_packages, distribution_package
def check_python_version(
package: str = "this package", today: Optional[datetime.date] = None
) -> PythonVersionStatus:
"""Check the running Python version and issue a support warning if needed.
Args:
today: The date to check against. Defaults to the current date.
Returns:
The support status of the current Python version.
"""
today = today or datetime.date.today()
package_label, _ = _get_distribution_and_import_packages(package)
python_version = sys.version_info
version_tuple = (python_version.major, python_version.minor)
py_version_str = sys.version.split()[0]
version_info = PYTHON_VERSION_INFO.get(version_tuple)
if not version_info:
if version_tuple < LOWEST_TRACKED_VERSION:
version_info = _FAKE_PAST_VERSION
else:
version_info = _FAKE_FUTURE_VERSION
gapic_deprecation = version_info.gapic_deprecation or (
version_info.python_eol - DEPRECATION_WARNING_PERIOD
)
gapic_end = version_info.gapic_end or (version_info.python_eol + EOL_GRACE_PERIOD)
def min_python(date: datetime.date) -> str:
"""Find the minimum supported Python version for a given date."""
for version, info in sorted(PYTHON_VERSION_INFO.items()):
if info.python_start <= date < info.python_eol:
return f"{version[0]}.{version[1]}"
return "at a currently supported version [https://devguide.python.org/versions]"
if gapic_end < today:
message = _flatten_message(
f"""
You are using a non-supported Python version ({py_version_str}).
Google will not post any further updates to {package_label}
supporting this Python version. Please upgrade to the latest Python
version, or at least Python {min_python(today)}, and then update
{package_label}.
"""
)
warnings.warn(message, FutureWarning)
return PythonVersionStatus.PYTHON_VERSION_UNSUPPORTED
eol_date = version_info.python_eol + EOL_GRACE_PERIOD
if eol_date <= today <= gapic_end:
message = _flatten_message(
f"""
You are using a Python version ({py_version_str})
past its end of life. Google will update {package_label}
with critical bug fixes on a best-effort basis, but not
with any other fixes or features. Please upgrade
to the latest Python version, or at least Python
{min_python(today)}, and then update {package_label}.
"""
)
warnings.warn(message, FutureWarning)
return PythonVersionStatus.PYTHON_VERSION_EOL
if gapic_deprecation <= today <= gapic_end:
message = _flatten_message(
f"""
You are using a Python version ({py_version_str}) which Google will
stop supporting in new releases of {package_label} once it reaches
its end of life ({version_info.python_eol}). Please upgrade to the
latest Python version, or at least Python
{min_python(version_info.python_eol)}, to continue receiving updates
for {package_label} past that date.
"""
)
warnings.warn(message, FutureWarning)
return PythonVersionStatus.PYTHON_VERSION_DEPRECATED
return PythonVersionStatus.PYTHON_VERSION_SUPPORTED
You can’t perform that action at this time.
