forked from tox-dev/tox
-
Notifications
You must be signed in to change notification settings - Fork 0
/
util.py
71 lines (60 loc) · 2.67 KB
/
util.py
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
from __future__ import annotations
from copy import deepcopy
from typing import Optional, Set, cast
from packaging.markers import Marker, Op, Value, Variable # type: ignore[attr-defined]
from packaging.requirements import Requirement
def dependencies_with_extras(deps: list[Requirement], extras: set[str], package_name: str) -> list[Requirement]:
deps_with_markers = extract_extra_markers(deps)
result: list[Requirement] = []
found: set[str] = set()
todo: set[str | None] = extras | {None}
visited: set[str | None] = set()
while todo:
new_extras: set[str | None] = set()
for req, extra_markers in deps_with_markers:
if todo & extra_markers:
if req.name == package_name: # support for recursive extras
new_extras.update(req.extras or set())
else:
req_str = str(req)
if req_str not in found:
found.add(req_str)
result.append(req)
visited.update(todo)
todo = new_extras - visited
return result
def extract_extra_markers(deps: list[Requirement]) -> list[tuple[Requirement, set[str | None]]]:
"""
Extract extra markers from dependencies.
:param deps: the dependencies
:return: a list of requirement, extras set
"""
result = [_extract_extra_markers(d) for d in deps]
return result
def _extract_extra_markers(req: Requirement) -> tuple[Requirement, set[str | None]]:
req = deepcopy(req)
markers: list[str | tuple[Variable, Op, Variable]] = getattr(req.marker, "_markers", []) or []
new_markers: list[str | tuple[Variable, Op, Variable]] = []
extra_markers: set[str] = set() # markers that have a key of extra
marker = markers.pop(0) if markers else None
while marker:
extra = _get_extra(marker)
if extra is not None:
extra_markers.add(extra)
if new_markers and new_markers[-1] in ("and", "or"):
del new_markers[-1]
marker = markers.pop(0) if markers else None
if marker in ("and", "or"):
marker = markers.pop(0) if markers else None
else:
new_markers.append(marker)
marker = markers.pop(0) if markers else None
if new_markers:
cast(Marker, req.marker)._markers = new_markers
else:
req.marker = None
return req, cast(Set[Optional[str]], extra_markers) or {None}
def _get_extra(_marker: str | tuple[Variable, Op, Value]) -> str | None:
if isinstance(_marker, tuple) and len(_marker) == 3 and _marker[0].value == "extra" and _marker[1].value == "==":
return cast(str, _marker[2].value)
return None