44import os
55import sys
66import tempfile
7+ from functools import wraps
78from subprocess import run # nosec
89from typing import Deque , Iterable , Mapping , ValuesView
910
4041]
4142
4243
44+ def patch_match_markers () -> None :
45+ """
46+ Monkey patches ``pip._internal.req.InstallRequirement.match_markers`` to
47+ allow us to pass environment other than "extra".
48+ """
49+
50+ @wraps (InstallRequirement .match_markers )
51+ def match_markers (
52+ self : InstallRequirement ,
53+ extras_requested : Iterable [str ] | None = None ,
54+ environment : dict [str , str ] = {},
55+ ) -> bool :
56+ assert "extra" not in environment
57+
58+ if not extras_requested :
59+ # Provide an extra to safely evaluate the markers
60+ # without matching any extra
61+ extras_requested = ("" ,)
62+ if self .markers is not None :
63+ return any (
64+ self .markers .evaluate ({"extra" : extra , ** environment })
65+ for extra in extras_requested
66+ )
67+ else :
68+ return True
69+
70+ InstallRequirement .match_markers = match_markers
71+
72+
73+ patch_match_markers ()
74+
75+
4376def dependency_tree (
4477 installed_keys : Mapping [str , Distribution ], root_key : str
4578) -> set [str ]:
@@ -93,15 +126,17 @@ def get_dists_to_ignore(installed: Iterable[Distribution]) -> list[str]:
93126
94127
95128def merge (
96- requirements : Iterable [InstallRequirement ], ignore_conflicts : bool
129+ requirements : Iterable [InstallRequirement ],
130+ ignore_conflicts : bool ,
131+ environment : dict [str , str ] = {},
97132) -> ValuesView [InstallRequirement ]:
98133 by_key : dict [str , InstallRequirement ] = {}
99134
100135 for ireq in requirements :
101136 # Limitation: URL requirements are merged by precise string match, so
102137 # "file:///example.zip#egg=example", "file:///example.zip", and
103138 # "example==1.0" will not merge with each other
104- if ireq .match_markers ():
139+ if ireq .match_markers (environment = environment ):
105140 key = key_from_ireq (ireq )
106141
107142 if not ignore_conflicts :
@@ -158,6 +193,7 @@ def diff_key_from_req(req: Distribution) -> str:
158193def diff (
159194 compiled_requirements : Iterable [InstallRequirement ],
160195 installed_dists : Iterable [Distribution ],
196+ environment : dict [str , str ] = {},
161197) -> tuple [set [InstallRequirement ], set [str ]]:
162198 """
163199 Calculate which packages should be installed or uninstalled, given a set
@@ -172,13 +208,15 @@ def diff(
172208 pkgs_to_ignore = get_dists_to_ignore (installed_dists )
173209 for dist in installed_dists :
174210 key = diff_key_from_req (dist )
175- if key not in requirements_lut or not requirements_lut [key ].match_markers ():
211+ if key not in requirements_lut or not requirements_lut [key ].match_markers (
212+ environment = environment
213+ ):
176214 to_uninstall .add (key )
177215 elif requirements_lut [key ].specifier .contains (dist .version ):
178216 satisfied .add (key )
179217
180218 for key , requirement in requirements_lut .items ():
181- if key not in satisfied and requirement .match_markers ():
219+ if key not in satisfied and requirement .match_markers (environment = environment ):
182220 to_install .add (requirement )
183221
184222 # Make sure to not uninstall any packages that should be ignored
0 commit comments