@@ -27,10 +27,26 @@ def get_interpreter(
2727 try_first_with : Iterable [str ] | None = None ,
2828 cache : PyInfoCache | None = None ,
2929 env : Mapping [str , str ] | None = None ,
30+ predicate : Callable [[PythonInfo ], bool ] | None = None ,
3031) -> PythonInfo | None :
32+ """
33+ Find a Python interpreter matching *key*.
34+
35+ Iterates over one or more specification strings and returns the first interpreter that satisfies the spec and passes
36+ the optional *predicate*.
37+
38+ :param key: interpreter specification string(s) — an absolute path, a version (``3.12``), an implementation prefix
39+ (``cpython3.12``), or a PEP 440 specifier (``>=3.10``). When a sequence is given each entry is tried in order.
40+ :param try_first_with: executables to probe before the normal discovery search.
41+ :param cache: interpreter metadata cache; when ``None`` results are not cached.
42+ :param env: environment mapping for ``PATH`` lookup; defaults to :data:`os.environ`.
43+ :param predicate: optional callback applied after an interpreter matches the spec. Return ``True`` to accept the
44+ interpreter, ``False`` to skip it and continue searching.
45+ :return: the first matching interpreter, or ``None`` if no match is found.
46+ """
3147 specs = [key ] if isinstance (key , str ) else key
3248 for spec_str in specs :
33- if result := _find_interpreter (spec_str , try_first_with or (), cache , env ):
49+ if result := _find_interpreter (spec_str , try_first_with or (), cache , env , predicate ):
3450 return result
3551 return None
3652
@@ -40,6 +56,7 @@ def _find_interpreter(
4056 try_first_with : Iterable [str ],
4157 cache : PyInfoCache | None = None ,
4258 env : Mapping [str , str ] | None = None ,
59+ predicate : Callable [[PythonInfo ], bool ] | None = None ,
4360) -> PythonInfo | None :
4461 spec = PythonSpec .from_string_spec (key )
4562 _LOGGER .info ("find interpreter for spec %r" , spec )
@@ -52,7 +69,9 @@ def _find_interpreter(
5269 if proposed_key in proposed_paths :
5370 continue
5471 _LOGGER .info ("proposed %s" , interpreter )
55- if interpreter .satisfies (spec , impl_must_match = impl_must_match ):
72+ if interpreter .satisfies (spec , impl_must_match = impl_must_match ) and (
73+ predicate is None or predicate (interpreter )
74+ ):
5675 _LOGGER .debug ("accepted %s" , interpreter )
5776 return interpreter
5877 proposed_paths .add (proposed_key )
@@ -88,6 +107,14 @@ def propose_interpreters(
88107 cache : PyInfoCache | None = None ,
89108 env : Mapping [str , str ] | None = None ,
90109) -> Generator [tuple [PythonInfo | None , bool ], None , None ]:
110+ """
111+ Yield ``(interpreter, impl_must_match)`` candidates for *spec*.
112+
113+ :param spec: the parsed interpreter specification to match against.
114+ :param try_first_with: executable paths to probe before the standard search.
115+ :param cache: interpreter metadata cache; when ``None`` results are not cached.
116+ :param env: environment mapping for ``PATH`` lookup; defaults to :data:`os.environ`.
117+ """
91118 env = os .environ if env is None else env
92119 tested_exes : set [str ] = set ()
93120 if spec .is_abs and spec .path is not None :
0 commit comments