8
8
import os
9
9
import platform
10
10
import sys
11
- from typing import Any , Callable , TypedDict , cast
11
+ from typing import AbstractSet , Any , Callable , Literal , TypedDict , Union , cast
12
12
13
13
from ._parser import MarkerAtom , MarkerList , Op , Value , Variable
14
14
from ._parser import parse_marker as _parse_marker
17
17
from .utils import canonicalize_name
18
18
19
19
__all__ = [
20
+ "EvaluateContext" ,
20
21
"InvalidMarker" ,
21
22
"Marker" ,
22
23
"UndefinedComparison" ,
23
24
"UndefinedEnvironmentName" ,
24
25
"default_environment" ,
25
26
]
26
27
27
- Operator = Callable [[str , str ], bool ]
28
+ Operator = Callable [[str , Union [str , AbstractSet [str ]]], bool ]
29
+ EvaluateContext = Literal ["metadata" , "lock_file" , "requirement" ]
30
+ MARKERS_ALLOWING_SET = {"extras" , "dependency_groups" }
28
31
29
32
30
33
class InvalidMarker (ValueError ):
@@ -174,13 +177,14 @@ def _format_marker(
174
177
}
175
178
176
179
177
- def _eval_op (lhs : str , op : Op , rhs : str ) -> bool :
178
- try :
179
- spec = Specifier ("" .join ([op .serialize (), rhs ]))
180
- except InvalidSpecifier :
181
- pass
182
- else :
183
- return spec .contains (lhs , prereleases = True )
180
+ def _eval_op (lhs : str , op : Op , rhs : str | AbstractSet [str ]) -> bool :
181
+ if isinstance (rhs , str ):
182
+ try :
183
+ spec = Specifier ("" .join ([op .serialize (), rhs ]))
184
+ except InvalidSpecifier :
185
+ pass
186
+ else :
187
+ return spec .contains (lhs , prereleases = True )
184
188
185
189
oper : Operator | None = _operators .get (op .serialize ())
186
190
if oper is None :
@@ -189,19 +193,29 @@ def _eval_op(lhs: str, op: Op, rhs: str) -> bool:
189
193
return oper (lhs , rhs )
190
194
191
195
192
- def _normalize (* values : str , key : str ) -> tuple [str , ...]:
196
+ def _normalize (
197
+ lhs : str , rhs : str | AbstractSet [str ], key : str
198
+ ) -> tuple [str , str | AbstractSet [str ]]:
193
199
# PEP 685 – Comparison of extra names for optional distribution dependencies
194
200
# https://peps.python.org/pep-0685/
195
201
# > When comparing extra names, tools MUST normalize the names being
196
202
# > compared using the semantics outlined in PEP 503 for names
197
203
if key == "extra" :
198
- return tuple (canonicalize_name (v ) for v in values )
204
+ assert isinstance (rhs , str ), "extra value must be a string"
205
+ return (canonicalize_name (lhs ), canonicalize_name (rhs ))
206
+ if key in MARKERS_ALLOWING_SET :
207
+ if isinstance (rhs , str ): # pragma: no cover
208
+ return (canonicalize_name (lhs ), canonicalize_name (rhs ))
209
+ else :
210
+ return (canonicalize_name (lhs ), {canonicalize_name (v ) for v in rhs })
199
211
200
212
# other environment markers don't have such standards
201
- return values
213
+ return lhs , rhs
202
214
203
215
204
- def _evaluate_markers (markers : MarkerList , environment : dict [str , str ]) -> bool :
216
+ def _evaluate_markers (
217
+ markers : MarkerList , environment : dict [str , str | AbstractSet [str ]]
218
+ ) -> bool :
205
219
groups : list [list [bool ]] = [[]]
206
220
207
221
for marker in markers :
@@ -220,7 +234,7 @@ def _evaluate_markers(markers: MarkerList, environment: dict[str, str]) -> bool:
220
234
lhs_value = lhs .value
221
235
environment_key = rhs .value
222
236
rhs_value = environment [environment_key ]
223
-
237
+ assert isinstance ( lhs_value , str ), "lhs must be a string"
224
238
lhs_value , rhs_value = _normalize (lhs_value , rhs_value , key = environment_key )
225
239
groups [- 1 ].append (_eval_op (lhs_value , op , rhs_value ))
226
240
else :
@@ -298,34 +312,51 @@ def __eq__(self, other: Any) -> bool:
298
312
299
313
return str (self ) == str (other )
300
314
301
- def evaluate (self , environment : dict [str , str ] | None = None ) -> bool :
315
+ def evaluate (
316
+ self ,
317
+ environment : dict [str , str ] | None = None ,
318
+ context : EvaluateContext = "metadata" ,
319
+ ) -> bool :
302
320
"""Evaluate a marker.
303
321
304
322
Return the boolean from evaluating the given marker against the
305
323
environment. environment is an optional argument to override all or
306
- part of the determined environment.
324
+ part of the determined environment. The *context* parameter specifies what
325
+ context the markers are being evaluated for, which influences what markers
326
+ are considered valid. Acceptable values are "metadata" (for core metadata;
327
+ default), "lock_file", and "requirement" (i.e. all other situations).
307
328
308
329
The environment is determined from the current Python process.
309
330
"""
310
- current_environment = cast ("dict[str, str]" , default_environment ())
311
- current_environment ["extra" ] = ""
331
+ current_environment = cast (
332
+ "dict[str, str | AbstractSet[str]]" , default_environment ()
333
+ )
334
+ if context == "lock_file" :
335
+ current_environment .update (
336
+ extras = frozenset (), dependency_groups = frozenset ()
337
+ )
338
+ elif context == "metadata" :
339
+ current_environment ["extra" ] = ""
312
340
if environment is not None :
313
341
current_environment .update (environment )
314
342
# The API used to allow setting extra to None. We need to handle this
315
343
# case for backwards compatibility.
316
- if current_environment ["extra" ] is None :
344
+ if "extra" in current_environment and current_environment ["extra" ] is None :
317
345
current_environment ["extra" ] = ""
318
346
319
347
return _evaluate_markers (
320
348
self ._markers , _repair_python_full_version (current_environment )
321
349
)
322
350
323
351
324
- def _repair_python_full_version (env : dict [str , str ]) -> dict [str , str ]:
352
+ def _repair_python_full_version (
353
+ env : dict [str , str | AbstractSet [str ]],
354
+ ) -> dict [str , str | AbstractSet [str ]]:
325
355
"""
326
356
Work around platform.python_version() returning something that is not PEP 440
327
357
compliant for non-tagged Python builds.
328
358
"""
329
- if env ["python_full_version" ].endswith ("+" ):
330
- env ["python_full_version" ] += "local"
359
+ python_full_version = cast (str , env ["python_full_version" ])
360
+ if python_full_version .endswith ("+" ):
361
+ env ["python_full_version" ] = f"{ python_full_version } local"
331
362
return env
0 commit comments