5
5
# Licensed under the MIT License. See License.txt in the project root for license information.
6
6
# --------------------------------------------------------------------------------------------
7
7
8
-
9
8
import argparse
10
9
import os
11
10
import sys
12
11
import logging
13
12
import re
13
+
14
14
from subprocess import check_call
15
- from typing import TYPE_CHECKING
15
+ from typing import TYPE_CHECKING , Callable , Optional
16
16
from pkg_resources import parse_version , Requirement
17
17
from pypi_tools .pypi import PyPIClient
18
18
from packaging .specifiers import SpecifierSet
19
- from packaging .version import Version , parse
19
+ from packaging .version import Version
20
20
21
21
from ci_tools .parsing import ParsedSetup , parse_require
22
- from ci_tools .functions import compare_python_version
22
+ from ci_tools .functions import compare_python_version , handle_incompatible_minimum_dev_reqs
23
23
24
24
from typing import List
25
25
59
59
"azure-eventhub-checkpointstoretable" : {"azure-core" : "1.25.0" , "azure-eventhub" : "5.11.0" },
60
60
"azure-identity" : {"msal" : "1.23.0" },
61
61
"azure-core-tracing-opentelemetry" : {"azure-core" : "1.28.0" },
62
- "azure-storage-file-datalake" : {"azure-storage-blob" : "12.22.0" }
62
+ "azure-storage-file-datalake" : {"azure-storage-blob" : "12.22.0" },
63
63
}
64
64
65
65
MAXIMUM_VERSION_SPECIFIC_OVERRIDES = {}
66
66
67
67
# PLATFORM SPECIFIC OVERRIDES provide additional generic (EG not tied to the package whos dependencies are being processed)
68
68
# filtering on a _per platform_ basis. Primarily used to limit certain packages due to platform compatbility
69
69
PLATFORM_SPECIFIC_MINIMUM_OVERRIDES = {
70
- ">=3.12.0" : {
71
- "azure-core" : "1.23.1" ,
72
- "aiohttp" : "3.8.6" ,
73
- "six" : "1.16.0" ,
74
- "requests" : "2.30.0"
75
- }
70
+ ">=3.12.0" : {"azure-core" : "1.23.1" , "aiohttp" : "3.8.6" , "six" : "1.16.0" , "requests" : "2.30.0" }
76
71
}
77
72
78
73
PLATFORM_SPECIFIC_MAXIMUM_OVERRIDES = {}
@@ -101,8 +96,15 @@ def install_dependent_packages(setup_py_file_path, dependency_type, temp_dir):
101
96
override_added_packages .extend (check_pkg_against_overrides (pkg_spec ))
102
97
103
98
logging .info ("%s released packages: %s" , dependency_type , released_packages )
104
- # filter released packages from dev_requirements and create a new file "new_dev_requirements.txt"
105
- dev_req_file_path = filter_dev_requirements (setup_py_file_path , released_packages , temp_dir , dependency_type )
99
+
100
+ additional_filter_fn = None
101
+ if dependency_type == "Minimum" :
102
+ additional_filter_fn = handle_incompatible_minimum_dev_reqs
103
+
104
+ # before september 2024, filter_dev_requirements only would remove any packages present in released_packages from the dev_requirements,
105
+ # then create a new file "new_dev_requirements.txt" without the problematic packages.
106
+ # after september 2024, filter_dev_requirements will also check for **compatibility** with the packages being installed when filtering the dev_requirements.
107
+ dev_req_file_path = filter_dev_requirements (setup_py_file_path , released_packages , temp_dir , additional_filter_fn )
106
108
107
109
if override_added_packages :
108
110
logging .info (f"Expanding the requirement set by the packages { override_added_packages } ." )
@@ -157,6 +159,7 @@ def find_released_packages(setup_py_path, dependency_type):
157
159
158
160
return avlble_packages
159
161
162
+
160
163
def process_bounded_versions (originating_pkg_name : str , pkg_name : str , versions : List [str ]) -> List [str ]:
161
164
"""
162
165
Processes a target package based on an originating package (target is a dep of originating) and the versions available from pypi for the target package.
@@ -180,9 +183,7 @@ def process_bounded_versions(originating_pkg_name: str, pkg_name: str, versions:
180
183
restrictions = PLATFORM_SPECIFIC_MINIMUM_OVERRIDES [platform_bound ]
181
184
182
185
if pkg_name in restrictions :
183
- versions = [
184
- v for v in versions if parse_version (v ) >= parse_version (restrictions [pkg_name ])
185
- ]
186
+ versions = [v for v in versions if parse_version (v ) >= parse_version (restrictions [pkg_name ])]
186
187
187
188
# lower bound package-specific
188
189
if (
@@ -207,9 +208,7 @@ def process_bounded_versions(originating_pkg_name: str, pkg_name: str, versions:
207
208
restrictions = PLATFORM_SPECIFIC_MAXIMUM_OVERRIDES [platform_bound ]
208
209
209
210
if pkg_name in restrictions :
210
- versions = [
211
- v for v in versions if parse_version (v ) <= parse_version (restrictions [pkg_name ])
212
- ]
211
+ versions = [v for v in versions if parse_version (v ) <= parse_version (restrictions [pkg_name ])]
213
212
214
213
# upper bound package-specific
215
214
if (
@@ -249,7 +248,6 @@ def process_requirement(req, dependency_type, orig_pkg_name):
249
248
250
249
# think of the various versions that come back from pypi as the top of a funnel
251
250
# We apply generic overrides -> platform specific overrides -> package specific overrides
252
-
253
251
versions = process_bounded_versions (orig_pkg_name , pkg_name , versions )
254
252
255
253
# Search from lowest to latest in case of finding minimum dependency
@@ -301,17 +299,20 @@ def check_req_against_exclusion(req, req_to_exclude):
301
299
302
300
return req_id == req_to_exclude
303
301
304
- # todo: remove when merging #37450
305
- def replace_identity (dev_requirement_line ) -> str :
306
- regex = r"azure[-_]identity"
307
-
308
- if re .search (regex , dev_requirement_line ):
309
- return "azure-identity==1.17.0\n "
310
- else :
311
- return dev_requirement_line
312
302
303
+ def filter_dev_requirements (
304
+ setup_py_path ,
305
+ released_packages ,
306
+ temp_dir ,
307
+ additional_filter_fn : Optional [Callable [[str , List [str ], List [Requirement ]], List [str ]]] = None ,
308
+ ):
309
+ """
310
+ This function takes an existing package path, a list of specific package specifiers that we have resolved, a temporary directory to write
311
+ the modified dev_requirements to, and an optional additional_filter_fn that can be used to further filter the dev_requirements file if necessary.
313
312
314
- def filter_dev_requirements (setup_py_path , released_packages , temp_dir , dependency_type ):
313
+ The function will filter out any requirements present in the dev_requirements file that are present in the released_packages list (aka are required
314
+ by the package).
315
+ """
315
316
# This method returns list of requirements from dev_requirements by filtering out packages in given list
316
317
dev_req_path = os .path .join (os .path .dirname (setup_py_path ), DEV_REQ_FILE )
317
318
requirements = []
@@ -320,12 +321,13 @@ def filter_dev_requirements(setup_py_path, released_packages, temp_dir, dependen
320
321
321
322
# filter out any package available on PyPI (released_packages)
322
323
# include packages without relative reference and packages not available on PyPI
323
- released_packages = [p .split ("==" )[0 ] for p in released_packages ]
324
+ released_packages = [parse_require (p ) for p in released_packages ]
325
+ released_package_names = [p .key for p in released_packages ]
324
326
# find prebuilt whl paths in dev requiremente
325
327
prebuilt_dev_reqs = [os .path .basename (req .replace ("\n " , "" )) for req in requirements if os .path .sep in req ]
326
328
# filter any req if wheel is for a released package
327
- req_to_exclude = [req for req in prebuilt_dev_reqs if req .split ("-" )[0 ].replace ("_" , "-" ) in released_packages ]
328
- req_to_exclude .extend (released_packages )
329
+ req_to_exclude = [req for req in prebuilt_dev_reqs if req .split ("-" )[0 ].replace ("_" , "-" ) in released_package_names ]
330
+ req_to_exclude .extend (released_package_names )
329
331
330
332
filtered_req = [
331
333
req
@@ -334,9 +336,9 @@ def filter_dev_requirements(setup_py_path, released_packages, temp_dir, dependen
334
336
and not any ([check_req_against_exclusion (req , i ) for i in req_to_exclude ])
335
337
]
336
338
337
- if dependency_type == "Minimum" :
338
- # replace identity with the minimum version of the package
339
- filtered_req = [ replace_identity ( req ) for req in filtered_req ]
339
+ if additional_filter_fn :
340
+ # this filter function handles the case where a dev requirement is incompatible with the current set of targeted packages
341
+ filtered_req = additional_filter_fn ( setup_py_path , filtered_req , released_packages )
340
342
341
343
logging .info ("Filtered dev requirements: %s" , filtered_req )
342
344
@@ -345,7 +347,7 @@ def filter_dev_requirements(setup_py_path, released_packages, temp_dir, dependen
345
347
# create new dev requirements file with different name for filtered requirements
346
348
new_dev_req_path = os .path .join (temp_dir , NEW_DEV_REQ_FILE )
347
349
with open (new_dev_req_path , "w" ) as dev_req_file :
348
- dev_req_file .writelines (filtered_req )
350
+ dev_req_file .writelines (line if line . endswith ( " \n " ) else line + " \n " for line in filtered_req )
349
351
350
352
return new_dev_req_path
351
353
0 commit comments