summaryrefslogtreecommitdiffstats
path: root/meta/recipes-devtools/python/python3/valid-dists.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta/recipes-devtools/python/python3/valid-dists.patch')
-rw-r--r--meta/recipes-devtools/python/python3/valid-dists.patch160
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 @@
1From a65c29adc027b3615154cab73aaedd58a6aa23da Mon Sep 17 00:00:00 2001
2From: "Jason R. Coombs" <jaraco@jaraco.com>
3Date: Tue, 23 Jul 2024 08:36:16 -0400
4Subject: [PATCH] Prioritize valid dists to invalid dists when retrieving by
5 name.
6
7Closes python/importlib_metadata#489
8
9Upstream-Status: Backport [https://github.com/python/importlib_metadata/commit/a65c29adc027b3615154cab73aaedd58a6aa23da]
10Signed-off-by: Ross Burton <ross.burton@arm.com>
11
12diff --git i/Lib/importlib/metadata/__init__.py w/Lib/importlib/metadata/__init__.py
13index 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.
51diff --git i/Lib/importlib/metadata/_itertools.py w/Lib/importlib/metadata/_itertools.py
52index 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)