Skip to content

Commit c78042e

Browse files
kdestinscbedd
andauthored
(engsys) feat: Make mindependency aware of environment_markers (#37357)
* refactor: Return full Requirements object instead of [name, specifier] * feat: Filter out requirements that apply to the current environment * feat: Add a log message when a requirement is skipped * small update covering a couple extra usages of parse_require --------- Co-authored-by: Scott Beddall <[email protected]>
1 parent 2ed5d31 commit c78042e

File tree

7 files changed

+39
-32
lines changed

7 files changed

+39
-32
lines changed

eng/tox/install_depend_packages.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import re
1414
from subprocess import check_call
1515
from typing import TYPE_CHECKING
16-
from pkg_resources import parse_version
16+
from pkg_resources import parse_version, Requirement
1717
from pypi_tools.pypi import PyPIClient
1818
from packaging.specifiers import SpecifierSet
1919
from packaging.version import Version, parse
@@ -231,7 +231,18 @@ def process_requirement(req, dependency_type, orig_pkg_name):
231231
# this method finds either latest or minimum version of a package that is available on PyPI
232232

233233
# find package name and requirement specifier from requires
234-
pkg_name, spec = parse_require(req)
234+
requirement = parse_require(req)
235+
pkg_name = requirement.key
236+
spec = requirement.specifier if len(requirement.specifier) else None
237+
238+
# Filter out requirements with environment markers that don't match the current environment
239+
# e.g. `; python_version > 3.10` when running on Python3.9
240+
if not (requirement.marker is None or requirement.marker.evaluate()):
241+
logging.info(
242+
f"Skipping requirement {req!r}. Environment marker {str(requirement.marker)!r} "
243+
+ "does not apply to current environment."
244+
)
245+
return ""
235246

236247
# get available versions on PyPI
237248
client = PyPIClient()

scripts/devops_tasks/common_tasks.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,13 @@ def find_packages_missing_on_pypi(path: str) -> Iterable[str]:
225225
requires = ParsedSetup.from_path(path).requires
226226

227227
# parse pkg name and spec
228-
pkg_spec_dict = dict(parse_require(req) for req in requires)
228+
pkg_spec_dict = [parse_require(req) for req in requires]
229229
logging.info("Package requirement: {}".format(pkg_spec_dict))
230230
# find if version is available on pypi
231231
missing_packages = [
232-
"{0}{1}".format(pkg, pkg_spec_dict[pkg])
233-
for pkg in pkg_spec_dict.keys()
234-
if not is_required_version_on_pypi(pkg, pkg_spec_dict[pkg])
232+
f"{pkg.key}{pkg.specifier}"
233+
for pkg in pkg_spec_dict
234+
if not is_required_version_on_pypi(pkg.key, str(pkg.specifier))
235235
]
236236
if missing_packages:
237237
logging.error("Packages not found on PyPI: {}".format(missing_packages))

scripts/devops_tasks/test_regression.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ def find_package_dependency(glob_string, repo_root_dir, dependent_service):
328328
parsed = ParsedSetup.from_path(pkg_root)
329329

330330
# Get a list of package names from install requires
331-
required_pkgs = [parse_require(r)[0] for r in parsed.requires]
331+
required_pkgs = [parse_require(r).key for r in parsed.requires]
332332
required_pkgs = [p for p in required_pkgs if p.startswith("azure")]
333333

334334
for req_pkg in required_pkgs:

tools/azure-sdk-tools/ci_tools/dependency_analysis.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ def get_lib_deps(base_dir: str) -> Tuple[Dict[str, Dict[str, Any]], Dict[str, Di
6868
packages[lib_name] = {"version": version, "source": lib_dir, "deps": []}
6969

7070
for req in requires:
71-
req_name, spec = parse_require(req)
71+
req_obj = parse_require(req)
72+
req_name = req_obj.key
73+
spec = req_obj.specifier if len(req_obj.specifier) else None
7274
if spec is None:
7375
spec = ""
7476

@@ -97,7 +99,10 @@ def get_wheel_deps(wheel_dir: str) -> Tuple[Dict[str, Dict[str, Any]], Dict[str,
9799
for req in requires:
98100
req = req.split(";")[0] # Extras conditions appear after a semicolon
99101
req = re.sub(r"[\s\(\)]", "", req) # Version specifiers appear in parentheses
100-
req_name, spec = parse_require(req)
102+
req_obj = parse_require(req)
103+
104+
req_name = req_obj.key
105+
spec = req_obj.specifier if len(req_obj.specifier) else None
101106
if spec is None:
102107
spec = ""
103108

tools/azure-sdk-tools/ci_tools/parsing/parse_functions.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def setup(*args, **kwargs):
272272
classifiers = kwargs.get("classifiers", [])
273273
keywords = kwargs.get("keywords", [])
274274

275-
is_new_sdk = name in NEW_REQ_PACKAGES or any(map(lambda x: (parse_require(x)[0] in NEW_REQ_PACKAGES), requires))
275+
is_new_sdk = name in NEW_REQ_PACKAGES or any(map(lambda x: (parse_require(x).key in NEW_REQ_PACKAGES), requires))
276276

277277
ext_package = kwargs.get("ext_package", None)
278278
ext_modules = kwargs.get("ext_modules", [])
@@ -301,23 +301,13 @@ def get_install_requires(setup_path: str) -> List[str]:
301301
return ParsedSetup.from_path(setup_path).requires
302302

303303

304-
def parse_require(req: str) -> Tuple[str, SpecifierSet]:
304+
def parse_require(req: str) -> Requirement:
305305
"""
306306
Parses the incoming version specification and returns a tuple of the requirement name and specifier.
307307
308308
"azure-core<2.0.0,>=1.11.0" -> [azure-core, <2.0.0,>=1.11.0]
309309
"""
310-
req_object = Requirement.parse(req.split(";")[0].lower())
311-
pkg_name = req_object.key
312-
313-
# we were not passed a full requirement. Instead we were passed a value of "readme-renderer" or another string without a version.
314-
if not req_object.specifier:
315-
return [pkg_name, None]
316-
317-
# regex details ripped from https://peps.python.org/pep-0508/
318-
isolated_spec = re.sub(r"^([a-zA-Z0-9\-\_\.]+)(\[[a-zA-Z0-9\-\_\.\,]*\])?", "", str(req_object))
319-
spec = SpecifierSet(isolated_spec)
320-
return (pkg_name, spec)
310+
return Requirement.parse(req)
321311

322312

323313
def parse_freeze_output(file_location: str) -> Dict[str, str]:

tools/azure-sdk-tools/ci_tools/scenario/generation.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ def create_package_and_install(
133133
logging.info("Installed packages: {}".format(installed_pkgs))
134134

135135
# parse the specifier
136-
req_name, req_specifier = parse_require(req)
136+
requirement = parse_require(req)
137+
req_name = requirement.key
138+
req_specifier = requirement.specifier if len(requirement.specifier) else None
137139

138140
# if we have the package already present...
139141
if req_name in installed_pkgs:
@@ -160,8 +162,8 @@ def create_package_and_install(
160162
env=dict(os.environ, PIP_EXTRA_INDEX_URL=""),
161163
)
162164
except subprocess.CalledProcessError as e:
163-
req_name, req_specifier = parse_require(addition)
164-
non_present_reqs.append(req_name)
165+
requirement = parse_require(addition)
166+
non_present_reqs.append(requirement.key)
165167

166168
additional_downloaded_reqs = [
167169
os.path.abspath(os.path.join(tmp_dl_folder, pth)) for pth in os.listdir(tmp_dl_folder)

tools/azure-sdk-tools/tests/test_parse_functionality.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@ def test_parse_require():
2424

2525
for scenario in test_scenarios:
2626
result = parse_require(scenario[0])
27-
assert result[0] is not None
27+
assert result.key is not None
2828
if scenario[2] is not None:
29-
assert result[1] is not None
30-
assert isinstance(result[1], SpecifierSet)
31-
assert result[0] == scenario[1]
32-
assert result[1] == scenario[2]
29+
assert len(result.specifier) != 0
30+
assert result.key == scenario[1]
31+
assert str(result.specifier) == (scenario[2] or "")
3332

3433

3534
def test_parse_require_with_no_spec():
@@ -38,8 +37,8 @@ def test_parse_require_with_no_spec():
3837
for scenario in spec_scenarios:
3938
result = parse_require(scenario)
4039

41-
assert result[0] == scenario.replace("_", "-")
42-
assert result[1] is None
40+
assert result.key == scenario.replace("_", "-")
41+
assert len(result.specifier) == 0
4342

4443

4544
@patch("ci_tools.parsing.parse_functions.read_setup_py_content")

0 commit comments

Comments
 (0)