|
13 | 13 | import sysconfig |
14 | 14 | from importlib.machinery import EXTENSION_SUFFIXES |
15 | 15 | from typing import ( |
| 16 | + TYPE_CHECKING, |
16 | 17 | Any, |
17 | 18 | Iterable, |
18 | 19 | Iterator, |
19 | 20 | Sequence, |
20 | 21 | Tuple, |
| 22 | + TypeVar, |
21 | 23 | cast, |
22 | 24 | ) |
23 | 25 |
|
24 | 26 | from . import _manylinux, _musllinux |
25 | 27 |
|
| 28 | +if TYPE_CHECKING: |
| 29 | + from collections.abc import Callable, Collection, Iterable |
| 30 | + from typing import AbstractSet |
| 31 | + |
| 32 | + |
26 | 33 | __all__ = [ |
27 | 34 | "INTERPRETER_SHORT_NAMES", |
28 | 35 | "AppleVersion", |
@@ -50,6 +57,7 @@ def __dir__() -> list[str]: |
50 | 57 |
|
51 | 58 | PythonVersion = Sequence[int] |
52 | 59 | AppleVersion = Tuple[int, int] |
| 60 | +_T = TypeVar("_T") |
53 | 61 |
|
54 | 62 | INTERPRETER_SHORT_NAMES: dict[str, str] = { |
55 | 63 | "python": "py", # Generic. |
@@ -672,3 +680,43 @@ def sys_tags(*, warn: bool = False) -> Iterator[Tag]: |
672 | 680 | else: |
673 | 681 | interp = None |
674 | 682 | yield from compatible_tags(interpreter=interp) |
| 683 | + |
| 684 | + |
| 685 | +def create_compatible_tags_selector( |
| 686 | + tags: Iterable[Tag], |
| 687 | +) -> Callable[[Collection[tuple[_T, AbstractSet[Tag]]]], Sequence[_T]]: |
| 688 | + """Create a callable to select things compatible with supported tags. |
| 689 | +
|
| 690 | + This function accepts an ordered sequence of tags, with the preferred |
| 691 | + tags first. |
| 692 | +
|
| 693 | + The returned callable accepts a collection of tuples (thing, set[Tag]), |
| 694 | + and returns an ordered sequence of things, with the things with the best |
| 695 | + matching tags first. The returned sequence is empty if nothing matches. |
| 696 | +
|
| 697 | + Example to select compatible wheel filenames: |
| 698 | +
|
| 699 | + >>> filenames = ["foo-1.0-py3-none-any.whl", "foo-1.0-py2-none-any.whl"] |
| 700 | + >>> selector = create_compatible_tags_selector(tags.sys_tags()) |
| 701 | + >>> compatible_filenames = selector([ |
| 702 | + ... (filename, parse_wheel_filename(filename)[-1]) for filename in filenames |
| 703 | + ... ]) |
| 704 | + ["foo-1.0-py3-none-any.whl"] |
| 705 | + """ |
| 706 | + tag_ranks: dict[Tag, int] = {} |
| 707 | + for rank, tag in enumerate(tags): |
| 708 | + tag_ranks.setdefault(tag, rank) # ignore duplicate tags, keep first |
| 709 | + supported_tags = tag_ranks.keys() |
| 710 | + |
| 711 | + def selector( |
| 712 | + tagged_things: Collection[tuple[_T, AbstractSet[Tag]]], |
| 713 | + ) -> Sequence[_T]: |
| 714 | + ranked_things: list[tuple[_T, int]] = [] |
| 715 | + for thing, thing_tags in tagged_things: |
| 716 | + supported_thing_tags = thing_tags & supported_tags |
| 717 | + if supported_thing_tags: |
| 718 | + thing_rank = min(tag_ranks[t] for t in supported_thing_tags) |
| 719 | + ranked_things.append((thing, thing_rank)) |
| 720 | + return [thing for thing, _ in sorted(ranked_things, key=lambda rt: rt[1])] |
| 721 | + |
| 722 | + return selector |
0 commit comments