diff options
Diffstat (limited to 'meta/recipes-devtools/python/python3/valid-dists.patch')
-rw-r--r-- | meta/recipes-devtools/python/python3/valid-dists.patch | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/meta/recipes-devtools/python/python3/valid-dists.patch b/meta/recipes-devtools/python/python3/valid-dists.patch new file mode 100644 index 0000000000..1b2c078c21 --- /dev/null +++ b/meta/recipes-devtools/python/python3/valid-dists.patch | |||
@@ -0,0 +1,160 @@ | |||
1 | From a65c29adc027b3615154cab73aaedd58a6aa23da Mon Sep 17 00:00:00 2001 | ||
2 | From: "Jason R. Coombs" <jaraco@jaraco.com> | ||
3 | Date: Tue, 23 Jul 2024 08:36:16 -0400 | ||
4 | Subject: [PATCH] Prioritize valid dists to invalid dists when retrieving by | ||
5 | name. | ||
6 | |||
7 | Closes python/importlib_metadata#489 | ||
8 | |||
9 | Upstream-Status: Backport [https://github.com/python/importlib_metadata/commit/a65c29adc027b3615154cab73aaedd58a6aa23da] | ||
10 | Signed-off-by: Ross Burton <ross.burton@arm.com> | ||
11 | |||
12 | diff --git i/Lib/importlib/metadata/__init__.py w/Lib/importlib/metadata/__init__.py | ||
13 | index 8ce62dd864f..085378caabc 100644 | ||
14 | --- i/Lib/importlib/metadata/__init__.py | ||
15 | +++ w/Lib/importlib/metadata/__init__.py | ||
16 | @@ -21,7 +21,7 @@ | ||
17 | from . import _meta | ||
18 | from ._collections import FreezableDefaultDict, Pair | ||
19 | from ._functools import method_cache, pass_none | ||
20 | -from ._itertools import always_iterable, unique_everseen | ||
21 | +from ._itertools import always_iterable, bucket, unique_everseen | ||
22 | from ._meta import PackageMetadata, SimplePath | ||
23 | |||
24 | from contextlib import suppress | ||
25 | @@ -404,7 +404,7 @@ def from_name(cls, name: str) -> Distribution: | ||
26 | if not name: | ||
27 | raise ValueError("A distribution name is required.") | ||
28 | try: | ||
29 | - return next(iter(cls.discover(name=name))) | ||
30 | + return next(iter(cls._prefer_valid(cls.discover(name=name)))) | ||
31 | except StopIteration: | ||
32 | raise PackageNotFoundError(name) | ||
33 | |||
34 | @@ -428,6 +428,16 @@ def discover( | ||
35 | resolver(context) for resolver in cls._discover_resolvers() | ||
36 | ) | ||
37 | |||
38 | + @staticmethod | ||
39 | + def _prefer_valid(dists: Iterable[Distribution]) -> Iterable[Distribution]: | ||
40 | + """ | ||
41 | + Prefer (move to the front) distributions that have metadata. | ||
42 | + | ||
43 | + Ref python/importlib_resources#489. | ||
44 | + """ | ||
45 | + buckets = bucket(dists, lambda dist: bool(dist.metadata)) | ||
46 | + return itertools.chain(buckets[True], buckets[False]) | ||
47 | + | ||
48 | @staticmethod | ||
49 | def at(path: str | os.PathLike[str]) -> Distribution: | ||
50 | """Return a Distribution for the indicated metadata path. | ||
51 | diff --git i/Lib/importlib/metadata/_itertools.py w/Lib/importlib/metadata/_itertools.py | ||
52 | index d4ca9b9140e..79d37198ce7 100644 | ||
53 | --- i/Lib/importlib/metadata/_itertools.py | ||
54 | +++ w/Lib/importlib/metadata/_itertools.py | ||
55 | @@ -1,3 +1,4 @@ | ||
56 | +from collections import defaultdict, deque | ||
57 | from itertools import filterfalse | ||
58 | |||
59 | |||
60 | @@ -71,3 +72,100 @@ def always_iterable(obj, base_type=(str, bytes)): | ||
61 | return iter(obj) | ||
62 | except TypeError: | ||
63 | return iter((obj,)) | ||
64 | + | ||
65 | + | ||
66 | +# Copied from more_itertools 10.3 | ||
67 | +class bucket: | ||
68 | + """Wrap *iterable* and return an object that buckets the iterable into | ||
69 | + child iterables based on a *key* function. | ||
70 | + | ||
71 | + >>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3'] | ||
72 | + >>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character | ||
73 | + >>> sorted(list(s)) # Get the keys | ||
74 | + ['a', 'b', 'c'] | ||
75 | + >>> a_iterable = s['a'] | ||
76 | + >>> next(a_iterable) | ||
77 | + 'a1' | ||
78 | + >>> next(a_iterable) | ||
79 | + 'a2' | ||
80 | + >>> list(s['b']) | ||
81 | + ['b1', 'b2', 'b3'] | ||
82 | + | ||
83 | + The original iterable will be advanced and its items will be cached until | ||
84 | + they are used by the child iterables. This may require significant storage. | ||
85 | + | ||
86 | + By default, attempting to select a bucket to which no items belong will | ||
87 | + exhaust the iterable and cache all values. | ||
88 | + If you specify a *validator* function, selected buckets will instead be | ||
89 | + checked against it. | ||
90 | + | ||
91 | + >>> from itertools import count | ||
92 | + >>> it = count(1, 2) # Infinite sequence of odd numbers | ||
93 | + >>> key = lambda x: x % 10 # Bucket by last digit | ||
94 | + >>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only | ||
95 | + >>> s = bucket(it, key=key, validator=validator) | ||
96 | + >>> 2 in s | ||
97 | + False | ||
98 | + >>> list(s[2]) | ||
99 | + [] | ||
100 | + | ||
101 | + """ | ||
102 | + | ||
103 | + def __init__(self, iterable, key, validator=None): | ||
104 | + self._it = iter(iterable) | ||
105 | + self._key = key | ||
106 | + self._cache = defaultdict(deque) | ||
107 | + self._validator = validator or (lambda x: True) | ||
108 | + | ||
109 | + def __contains__(self, value): | ||
110 | + if not self._validator(value): | ||
111 | + return False | ||
112 | + | ||
113 | + try: | ||
114 | + item = next(self[value]) | ||
115 | + except StopIteration: | ||
116 | + return False | ||
117 | + else: | ||
118 | + self._cache[value].appendleft(item) | ||
119 | + | ||
120 | + return True | ||
121 | + | ||
122 | + def _get_values(self, value): | ||
123 | + """ | ||
124 | + Helper to yield items from the parent iterator that match *value*. | ||
125 | + Items that don't match are stored in the local cache as they | ||
126 | + are encountered. | ||
127 | + """ | ||
128 | + while True: | ||
129 | + # If we've cached some items that match the target value, emit | ||
130 | + # the first one and evict it from the cache. | ||
131 | + if self._cache[value]: | ||
132 | + yield self._cache[value].popleft() | ||
133 | + # Otherwise we need to advance the parent iterator to search for | ||
134 | + # a matching item, caching the rest. | ||
135 | + else: | ||
136 | + while True: | ||
137 | + try: | ||
138 | + item = next(self._it) | ||
139 | + except StopIteration: | ||
140 | + return | ||
141 | + item_value = self._key(item) | ||
142 | + if item_value == value: | ||
143 | + yield item | ||
144 | + break | ||
145 | + elif self._validator(item_value): | ||
146 | + self._cache[item_value].append(item) | ||
147 | + | ||
148 | + def __iter__(self): | ||
149 | + for item in self._it: | ||
150 | + item_value = self._key(item) | ||
151 | + if self._validator(item_value): | ||
152 | + self._cache[item_value].append(item) | ||
153 | + | ||
154 | + yield from self._cache.keys() | ||
155 | + | ||
156 | + def __getitem__(self, value): | ||
157 | + if not self._validator(value): | ||
158 | + return iter(()) | ||
159 | + | ||
160 | + return self._get_values(value) | ||