From 33c4896dbaeda2fd7a5fef701431dea05bb83bab Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 10 Jul 2024 15:34:09 -0400 Subject: [PATCH 1/7] Exclude pytest-ruff (and thus ruff), which cannot build on cygwin. Ref pypa/setuptools#3921 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ad67d3b1..1307e1fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ test = [ "pytest-cov", "pytest-mypy", "pytest-enabler >= 2.2", - "pytest-ruff >= 0.2.1", + "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'", # local ] From f087fb4ca05eb08c46abdd2cd67b18a3f33e3c79 Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:32:46 +0200 Subject: [PATCH 2/7] "preserve" does not require preview any more (jaraco/skeleton#133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * "preserve" does not require preview any more * Update URL in ruff.toml comment --------- Co-authored-by: Bartosz Sławecki --- ruff.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ruff.toml b/ruff.toml index 70612985..7da4bee7 100644 --- a/ruff.toml +++ b/ruff.toml @@ -22,7 +22,5 @@ ignore = [ ] [format] -# Enable preview, required for quote-style = "preserve" -preview = true -# https://docs.astral.sh/ruff/settings/#format-quote-style +# https://docs.astral.sh/ruff/settings/#format_quote-style quote-style = "preserve" From 30f940e74b599400347d1162b7096f184cc46d31 Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:34:53 +0200 Subject: [PATCH 3/7] Enforce ruff/Perflint rule PERF401 (jaraco/skeleton#132) --- ruff.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/ruff.toml b/ruff.toml index 7da4bee7..f1d03f83 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,6 +1,7 @@ [lint] extend-select = [ "C901", + "PERF401", "W", ] ignore = [ From ab34814ca3ffe511ad63bb9589da06fd76758db8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 12:33:01 -0400 Subject: [PATCH 4/7] Re-enable preview, this time not for one specific feature, but for all features in preview. Ref jaraco/skeleton#133 --- ruff.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ruff.toml b/ruff.toml index f1d03f83..922aa1f1 100644 --- a/ruff.toml +++ b/ruff.toml @@ -23,5 +23,8 @@ ignore = [ ] [format] +# Enable preview to get hugged parenthesis unwrapping and other nice surprises +# See https://github.com/jaraco/skeleton/pull/133#issuecomment-2239538373 +preview = true # https://docs.astral.sh/ruff/settings/#format_quote-style quote-style = "preserve" From 48f6b143419941be07dc53330a4702ff8c9398c4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 23 Jul 2024 08:56:52 -0400 Subject: [PATCH 5/7] Add test capturing failed expectation. Ref #489. --- tests/test_main.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_main.py b/tests/test_main.py index f1c12855..a18c2d17 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -130,6 +130,32 @@ def test_unique_distributions(self): assert len(after) == len(before) +class InvalidMetadataTests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase): + @staticmethod + def make_pkg(name, files=dict(METADATA="VERSION: 1.0")): + """ + Create metadata for a dist-info package with name and files. + """ + return { + f'{name}.dist-info': files, + } + + @__import__('pytest').mark.xfail(reason="#489") + def test_valid_dists_preferred(self): + """ + Dists with metadata should be preferred when discovered by name. + + Ref python/importlib_metadata#489. + """ + # create three dists with the valid one in the middle (lexicographically) + # such that on most file systems, the valid one is never naturally first. + fixtures.build_files(self.make_pkg('foo-4.0', files={}), self.site_dir) + fixtures.build_files(self.make_pkg('foo-4.1'), self.site_dir) + fixtures.build_files(self.make_pkg('foo-4.2', files={}), self.site_dir) + dist = Distribution.from_name('foo') + assert dist.version == "1.0" + + class NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase): @staticmethod def pkg_with_non_ascii_description(site_dir): From a65c29adc027b3615154cab73aaedd58a6aa23da Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 23 Jul 2024 08:36:16 -0400 Subject: [PATCH 6/7] Prioritize valid dists to invalid dists when retrieving by name. Closes python/importlib_metadata#489 --- importlib_metadata/__init__.py | 14 ++++- importlib_metadata/_itertools.py | 98 ++++++++++++++++++++++++++++++++ newsfragments/489.feature.rst | 1 + tests/test_main.py | 1 - 4 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 newsfragments/489.feature.rst diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index ed481355..b9fc04f1 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -25,7 +25,7 @@ install, ) from ._functools import method_cache, pass_none -from ._itertools import always_iterable, unique_everseen +from ._itertools import always_iterable, bucket, unique_everseen from ._meta import PackageMetadata, SimplePath from contextlib import suppress @@ -388,7 +388,7 @@ def from_name(cls, name: str) -> Distribution: if not name: raise ValueError("A distribution name is required.") try: - return next(iter(cls.discover(name=name))) + return next(iter(cls._prefer_valid(cls.discover(name=name)))) except StopIteration: raise PackageNotFoundError(name) @@ -412,6 +412,16 @@ def discover( resolver(context) for resolver in cls._discover_resolvers() ) + @staticmethod + def _prefer_valid(dists: Iterable[Distribution]) -> Iterable[Distribution]: + """ + Prefer (move to the front) distributions that have metadata. + + Ref python/importlib_resources#489. + """ + buckets = bucket(dists, lambda dist: bool(dist.metadata)) + return itertools.chain(buckets[True], buckets[False]) + @staticmethod def at(path: str | os.PathLike[str]) -> Distribution: """Return a Distribution for the indicated metadata path. diff --git a/importlib_metadata/_itertools.py b/importlib_metadata/_itertools.py index d4ca9b91..79d37198 100644 --- a/importlib_metadata/_itertools.py +++ b/importlib_metadata/_itertools.py @@ -1,3 +1,4 @@ +from collections import defaultdict, deque from itertools import filterfalse @@ -71,3 +72,100 @@ def always_iterable(obj, base_type=(str, bytes)): return iter(obj) except TypeError: return iter((obj,)) + + +# Copied from more_itertools 10.3 +class bucket: + """Wrap *iterable* and return an object that buckets the iterable into + child iterables based on a *key* function. + + >>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3'] + >>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character + >>> sorted(list(s)) # Get the keys + ['a', 'b', 'c'] + >>> a_iterable = s['a'] + >>> next(a_iterable) + 'a1' + >>> next(a_iterable) + 'a2' + >>> list(s['b']) + ['b1', 'b2', 'b3'] + + The original iterable will be advanced and its items will be cached until + they are used by the child iterables. This may require significant storage. + + By default, attempting to select a bucket to which no items belong will + exhaust the iterable and cache all values. + If you specify a *validator* function, selected buckets will instead be + checked against it. + + >>> from itertools import count + >>> it = count(1, 2) # Infinite sequence of odd numbers + >>> key = lambda x: x % 10 # Bucket by last digit + >>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only + >>> s = bucket(it, key=key, validator=validator) + >>> 2 in s + False + >>> list(s[2]) + [] + + """ + + def __init__(self, iterable, key, validator=None): + self._it = iter(iterable) + self._key = key + self._cache = defaultdict(deque) + self._validator = validator or (lambda x: True) + + def __contains__(self, value): + if not self._validator(value): + return False + + try: + item = next(self[value]) + except StopIteration: + return False + else: + self._cache[value].appendleft(item) + + return True + + def _get_values(self, value): + """ + Helper to yield items from the parent iterator that match *value*. + Items that don't match are stored in the local cache as they + are encountered. + """ + while True: + # If we've cached some items that match the target value, emit + # the first one and evict it from the cache. + if self._cache[value]: + yield self._cache[value].popleft() + # Otherwise we need to advance the parent iterator to search for + # a matching item, caching the rest. + else: + while True: + try: + item = next(self._it) + except StopIteration: + return + item_value = self._key(item) + if item_value == value: + yield item + break + elif self._validator(item_value): + self._cache[item_value].append(item) + + def __iter__(self): + for item in self._it: + item_value = self._key(item) + if self._validator(item_value): + self._cache[item_value].append(item) + + yield from self._cache.keys() + + def __getitem__(self, value): + if not self._validator(value): + return iter(()) + + return self._get_values(value) diff --git a/newsfragments/489.feature.rst b/newsfragments/489.feature.rst new file mode 100644 index 00000000..f0e1b091 --- /dev/null +++ b/newsfragments/489.feature.rst @@ -0,0 +1 @@ +Prioritize valid dists to invalid dists when retrieving by name. \ No newline at end of file diff --git a/tests/test_main.py b/tests/test_main.py index a18c2d17..dc248492 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -140,7 +140,6 @@ def make_pkg(name, files=dict(METADATA="VERSION: 1.0")): f'{name}.dist-info': files, } - @__import__('pytest').mark.xfail(reason="#489") def test_valid_dists_preferred(self): """ Dists with metadata should be preferred when discovered by name. From 4bcda08ca2b1bf91437619d4f99810ecbf83b2b2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 23 Jul 2024 09:03:10 -0400 Subject: [PATCH 7/7] Finalize --- NEWS.rst | 9 +++++++++ newsfragments/489.feature.rst | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) delete mode 100644 newsfragments/489.feature.rst diff --git a/NEWS.rst b/NEWS.rst index a49f5ec9..0f1b63a4 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,12 @@ +v8.1.0 +====== + +Features +-------- + +- Prioritize valid dists to invalid dists when retrieving by name. (#489) + + v8.0.0 ====== diff --git a/newsfragments/489.feature.rst b/newsfragments/489.feature.rst deleted file mode 100644 index f0e1b091..00000000 --- a/newsfragments/489.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Prioritize valid dists to invalid dists when retrieving by name. \ No newline at end of file pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy