From d131a95217c7c476dd4c539df7b191b197e549ff Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Sat, 9 Dec 2023 14:07:02 +0000 Subject: [PATCH 1/6] Consolidate imports in `test_resolvers.py` This does not need two separate `if TYPE_CHECKING` blocks, splitting the imports across two sections. --- tests/test_resolvers.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/test_resolvers.py b/tests/test_resolvers.py index 46d0f5d5..8659e51f 100644 --- a/tests/test_resolvers.py +++ b/tests/test_resolvers.py @@ -1,10 +1,8 @@ from __future__ import annotations +from collections import namedtuple from typing import TYPE_CHECKING, Any, Iterator, Sequence, Tuple -if TYPE_CHECKING: - from typing import Iterable, Mapping - import pytest from packaging.requirements import Requirement from packaging.version import Version @@ -16,18 +14,17 @@ ResolutionImpossible, Resolver, ) +from resolvelib.resolvers import Resolution if TYPE_CHECKING: + from typing import Iterable, Mapping + from resolvelib.resolvers import ( Criterion, RequirementInformation, RequirementsConflicted, ) -from collections import namedtuple - -from resolvelib.resolvers import Resolution - def test_candidate_inconsistent_error(): requirement = "foo" From 0d093b44038300f68bdb683b27f1fda326c08eb7 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Sat, 9 Dec 2023 14:15:18 +0000 Subject: [PATCH 2/6] Make `resolvelib.resolvers` a sub-package This makes the `Resolver` object live in a diffferent module than the `AbstractResolver`, clarifying their separation and more cleanly accomodating additional resolver implementations. --- src/resolvelib/__init__.py | 4 +- src/resolvelib/resolvers/__init__.py | 0 src/resolvelib/resolvers/abstract.py | 49 ++++++++++++++++ .../{resolvers.py => resolvers/criterion.py} | 57 ++----------------- .../test_resolvers_swift.py | 2 +- tests/test_resolvers.py | 6 +- 6 files changed, 61 insertions(+), 57 deletions(-) create mode 100644 src/resolvelib/resolvers/__init__.py create mode 100644 src/resolvelib/resolvers/abstract.py rename src/resolvelib/{resolvers.py => resolvers/criterion.py} (93%) diff --git a/src/resolvelib/__init__.py b/src/resolvelib/__init__.py index fd9f2492..4b28aaac 100644 --- a/src/resolvelib/__init__.py +++ b/src/resolvelib/__init__.py @@ -16,8 +16,8 @@ from .providers import AbstractProvider from .reporters import BaseReporter -from .resolvers import ( - AbstractResolver, +from .resolvers.abstract import AbstractResolver +from .resolvers.criterion import ( InconsistentCandidate, RequirementsConflicted, ResolutionError, diff --git a/src/resolvelib/resolvers/__init__.py b/src/resolvelib/resolvers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/resolvelib/resolvers/abstract.py b/src/resolvelib/resolvers/abstract.py new file mode 100644 index 00000000..f18324a8 --- /dev/null +++ b/src/resolvelib/resolvers/abstract.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +import collections +from typing import TYPE_CHECKING, Any, Generic, Iterable, Mapping, NamedTuple + +from resolvelib.providers import AbstractProvider +from resolvelib.reporters import BaseReporter + +from ..structs import CT, KT, RT, Criterion, DirectedGraph + +if TYPE_CHECKING: + + class Result(NamedTuple, Generic[RT, CT, KT]): + mapping: Mapping[KT, CT] + graph: DirectedGraph[KT | None] + criteria: Mapping[KT, Criterion[RT, CT]] + +else: + Result = collections.namedtuple("Result", ["mapping", "graph", "criteria"]) + + +class AbstractResolver(Generic[RT, CT, KT]): + """The thing that performs the actual resolution work.""" + + base_exception = Exception + + def __init__( + self, + provider: AbstractProvider[RT, CT, KT], + reporter: BaseReporter[RT, CT, KT], + ) -> None: + self.provider = provider + self.reporter = reporter + + def resolve( + self, requirements: Iterable[RT], **kwargs: Any + ) -> Result[RT, CT, KT]: + """Take a collection of constraints, spit out the resolution result. + + This returns a representation of the final resolution state, with one + guarenteed attribute ``mapping`` that contains resolved candidates as + values. The keys are their respective identifiers. + + :param requirements: A collection of constraints. + :param kwargs: Additional keyword arguments that subclasses may accept. + + :raises: ``self.base_exception`` or its subclass. + """ + raise NotImplementedError diff --git a/src/resolvelib/resolvers.py b/src/resolvelib/resolvers/criterion.py similarity index 93% rename from src/resolvelib/resolvers.py rename to src/resolvelib/resolvers/criterion.py index 579ba8f7..bab6e6a7 100644 --- a/src/resolvelib/resolvers.py +++ b/src/resolvelib/resolvers/criterion.py @@ -3,19 +3,11 @@ import collections import itertools import operator -from typing import ( - TYPE_CHECKING, - Any, - Collection, - Generic, - Iterable, - Mapping, - NamedTuple, -) +from typing import TYPE_CHECKING, Collection, Generic, Iterable, Mapping -from .providers import AbstractProvider -from .reporters import BaseReporter -from .structs import ( +from ..providers import AbstractProvider +from ..reporters import BaseReporter +from ..structs import ( CT, KT, RT, @@ -27,17 +19,10 @@ State, build_iter_view, ) +from .abstract import AbstractResolver, Result if TYPE_CHECKING: - from .providers import Preference - - class Result(NamedTuple, Generic[RT, CT, KT]): - mapping: Mapping[KT, CT] - graph: DirectedGraph[KT | None] - criteria: Mapping[KT, Criterion[RT, CT]] - -else: - Result = collections.namedtuple("Result", ["mapping", "graph", "criteria"]) + from ..providers import Preference class ResolverException(Exception): @@ -539,36 +524,6 @@ def _build_result(state: State[RT, CT, KT]) -> Result[RT, CT, KT]: ) -class AbstractResolver(Generic[RT, CT, KT]): - """The thing that performs the actual resolution work.""" - - base_exception = Exception - - def __init__( - self, - provider: AbstractProvider[RT, CT, KT], - reporter: BaseReporter[RT, CT, KT], - ) -> None: - self.provider = provider - self.reporter = reporter - - def resolve( - self, requirements: Iterable[RT], **kwargs: Any - ) -> Result[RT, CT, KT]: - """Take a collection of constraints, spit out the resolution result. - - This returns a representation of the final resolution state, with one - guarenteed attribute ``mapping`` that contains resolved candidates as - values. The keys are their respective identifiers. - - :param requirements: A collection of constraints. - :param kwargs: Additional keyword arguments that subclasses may accept. - - :raises: ``self.base_exception`` or its subclass. - """ - raise NotImplementedError - - class Resolver(AbstractResolver[RT, CT, KT]): """The thing that performs the actual resolution work.""" diff --git a/tests/functional/swift-package-manager/test_resolvers_swift.py b/tests/functional/swift-package-manager/test_resolvers_swift.py index ad0e48f0..673ae45a 100644 --- a/tests/functional/swift-package-manager/test_resolvers_swift.py +++ b/tests/functional/swift-package-manager/test_resolvers_swift.py @@ -5,8 +5,8 @@ import pytest +from resolvelib import Resolver from resolvelib.providers import AbstractProvider -from resolvelib.resolvers import Resolver Requirement = collections.namedtuple("Requirement", "container constraint") Candidate = collections.namedtuple("Candidate", "container version") diff --git a/tests/test_resolvers.py b/tests/test_resolvers.py index 8659e51f..c4440b11 100644 --- a/tests/test_resolvers.py +++ b/tests/test_resolvers.py @@ -14,12 +14,12 @@ ResolutionImpossible, Resolver, ) -from resolvelib.resolvers import Resolution +from resolvelib.resolvers.criterion import Resolution if TYPE_CHECKING: from typing import Iterable, Mapping - from resolvelib.resolvers import ( + from resolvelib.resolvers.criterion import ( Criterion, RequirementInformation, RequirementsConflicted, @@ -261,7 +261,7 @@ def get_updated_criteria_patch(self, candidate): result = resolver.resolve(["child", "parent"]) def get_child_versions( - information: Iterable[RequirementInformation[str, Candidate]] + information: Iterable[RequirementInformation[str, Candidate]], ) -> set[str]: return { str(inf.parent[1]) From 662fd4b8c156a6720ab9d1d7a08e21b7ff3f6ac4 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Thu, 21 Dec 2023 09:40:35 -0600 Subject: [PATCH 3/6] Export a clearly defined subset from `resolvers` subpackage This prevents users from relying on all possible details of this package. --- src/resolvelib/resolvers/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/resolvelib/resolvers/__init__.py b/src/resolvelib/resolvers/__init__.py index e69de29b..a2c5a426 100644 --- a/src/resolvelib/resolvers/__init__.py +++ b/src/resolvelib/resolvers/__init__.py @@ -0,0 +1,10 @@ +from ..structs import RequirementInformation +from .abstract import AbstractResolver, Result +from .criterion import ( + InconsistentCandidate, + RequirementsConflicted, + ResolutionError, + ResolutionImpossible, + ResolutionTooDeep, + ResolverException, +) From ecc4c968912d14fa9ea3d1c75e3dcde8f0e884d6 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Thu, 1 Aug 2024 09:59:29 +0800 Subject: [PATCH 4/6] Merge branch 'typing-refactor' into pr/pradyunsg/149 Signed-off-by: Frost Ming --- src/resolvelib/__init__.py | 4 ++-- src/resolvelib/resolvers/__init__.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/resolvelib/__init__.py b/src/resolvelib/__init__.py index 4b28aaac..fd9f2492 100644 --- a/src/resolvelib/__init__.py +++ b/src/resolvelib/__init__.py @@ -16,8 +16,8 @@ from .providers import AbstractProvider from .reporters import BaseReporter -from .resolvers.abstract import AbstractResolver -from .resolvers.criterion import ( +from .resolvers import ( + AbstractResolver, InconsistentCandidate, RequirementsConflicted, ResolutionError, diff --git a/src/resolvelib/resolvers/__init__.py b/src/resolvelib/resolvers/__init__.py index a2c5a426..621dd053 100644 --- a/src/resolvelib/resolvers/__init__.py +++ b/src/resolvelib/resolvers/__init__.py @@ -8,3 +8,18 @@ ResolutionTooDeep, ResolverException, ) + +__all__ = [ + "AbstractProvider", + "AbstractResolver", + "BaseReporter", + "InconsistentCandidate", + "Resolver", + "RequirementsConflicted", + "ResolutionError", + "ResolutionImpossible", + "ResolutionTooDeep", + "RequirementInformation", + "ResolverException", + "Result", +] From 569084fe93627cdf6aff853c169735820efd1657 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Thu, 1 Aug 2024 10:07:24 +0800 Subject: [PATCH 5/6] fix: lint errors Signed-off-by: Frost Ming --- src/resolvelib/resolvers/__init__.py | 3 +-- tests/functional/python/py2index.py | 10 +--------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/resolvelib/resolvers/__init__.py b/src/resolvelib/resolvers/__init__.py index 621dd053..1f0ddccd 100644 --- a/src/resolvelib/resolvers/__init__.py +++ b/src/resolvelib/resolvers/__init__.py @@ -6,13 +6,12 @@ ResolutionError, ResolutionImpossible, ResolutionTooDeep, + Resolver, ResolverException, ) __all__ = [ - "AbstractProvider", "AbstractResolver", - "BaseReporter", "InconsistentCandidate", "Resolver", "RequirementsConflicted", diff --git a/tests/functional/python/py2index.py b/tests/functional/python/py2index.py index fc6a0bc1..710831fe 100644 --- a/tests/functional/python/py2index.py +++ b/tests/functional/python/py2index.py @@ -29,15 +29,7 @@ import re import sys import urllib.parse -from typing import ( - Dict, - Iterable, - Iterator, - List, - NamedTuple, - Tuple, - Union, -) +from typing import Dict, Iterable, Iterator, List, NamedTuple, Tuple, Union import html5lib import packaging.requirements From ac8ed2648308c6f25dd3ce5229bb6b4e409a0efb Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Thu, 1 Aug 2024 10:30:26 +0800 Subject: [PATCH 6/6] fix typing Signed-off-by: Frost Ming --- examples/reporter_demo.py | 7 ++--- examples/visualization/reporter.py | 20 ++++--------- examples/visualization/run.py | 4 +-- noxfile.py | 4 +-- pyproject.toml | 15 ++-------- src/resolvelib/providers.py | 5 ++-- src/resolvelib/reporters.py | 4 +-- src/resolvelib/resolvers/abstract.py | 4 +-- src/resolvelib/resolvers/criterion.py | 24 +++++----------- src/resolvelib/structs.py | 27 +++++++----------- .../cocoapods/test_resolvers_cocoapods.py | 9 ++---- tests/functional/python/py2index.py | 4 +-- .../python/test_resolvers_python.py | 15 +++------- .../test_resolvers_swift.py | 3 +- tests/test_resolvers.py | 28 +++++-------------- 15 files changed, 49 insertions(+), 124 deletions(-) diff --git a/examples/reporter_demo.py b/examples/reporter_demo.py index a2795630..4ff04301 100644 --- a/examples/reporter_demo.py +++ b/examples/reporter_demo.py @@ -1,10 +1,9 @@ from collections import namedtuple +import resolvelib from packaging.specifiers import SpecifierSet from packaging.version import Version -import resolvelib - index = """ first 1.0.0 second == 1.0.0 @@ -53,9 +52,7 @@ def read_spec(lines): candidates[latest] = set() else: if latest is None: - raise RuntimeError( - "Spec has dependencies before first candidate" - ) + raise RuntimeError("Spec has dependencies before first candidate") name, specifier = splitstrip(line, 2) specifier = SpecifierSet(specifier) candidates[latest].add(Requirement(name, specifier)) diff --git a/examples/visualization/reporter.py b/examples/visualization/reporter.py index 53c395af..ed6f80a4 100644 --- a/examples/visualization/reporter.py +++ b/examples/visualization/reporter.py @@ -62,9 +62,7 @@ def _get_subgraph(self, name, *, must_exist_already=True): if subgraph is None: if must_exist_already: existing = [s.name for s in self.graph.subgraphs_iter()] - raise RuntimeError( - f"Graph for {name} not found. Existing: {existing}" - ) + raise RuntimeError(f"Graph for {name} not found. Existing: {existing}") else: subgraph = self.graph.add_subgraph(name=c_name, label=name) @@ -151,9 +149,7 @@ def adding_requirement(self, req, parent): # We're seeing the parent candidate (which is being "evaluated"), so # color all "active" requirements pointing to the it. # TODO: How does this interact with revisited candidates? - for parent_req in self._active_requirements[ - canonicalize_name(parent.name) - ]: + for parent_req in self._active_requirements[canonicalize_name(parent.name)]: self._ensure_edge(parent_req, to=parent, color="#80CC80") def backtracking(self, candidate, internal=False): @@ -175,9 +171,7 @@ def backtracking(self, candidate, internal=False): # Trim "active" requirements to remove anything not relevant now. for requirement in self._dependencies[candidate]: - active = self._active_requirements[ - canonicalize_name(requirement.name) - ] + active = self._active_requirements[canonicalize_name(requirement.name)] active[requirement] -= 1 if not active[requirement]: del active[requirement] @@ -194,12 +188,8 @@ def pinning(self, candidate): node.attr.update(color="#80CC80") # Requirement -> Candidate edges, from this candidate. - for req in self._active_requirements[ - canonicalize_name(candidate.name) - ]: - self._ensure_edge( - req, to=candidate, arrowhead="vee", color="#80CC80" - ) + for req in self._active_requirements[canonicalize_name(candidate.name)]: + self._ensure_edge(req, to=candidate, arrowhead="vee", color="#80CC80") # Candidate -> Requirement edges, from this candidate. for edge in self.graph.out_edges_iter([node_name]): diff --git a/examples/visualization/run.py b/examples/visualization/run.py index c405163d..47ae833d 100644 --- a/examples/visualization/run.py +++ b/examples/visualization/run.py @@ -25,9 +25,7 @@ def process_arguments(function, args): to_convert, _, args = args.partition(", ") value = int(to_convert) elif arg_type == "requirement": - match = re.match( - r"^(.*)", args - ) + match = re.match(r"^(.*)", args) assert match, repr(args) name, spec, args = match.groups() value = Requirement(name, spec) diff --git a/noxfile.py b/noxfile.py index c788ea6a..2d90cb1d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -15,9 +15,7 @@ @nox.session def lint(session): session.install(".[lint, test]") - - session.run("black", "--check", ".") - session.run("isort", ".") + session.run("ruff", "format", "--check", ".") session.run("ruff", "check", ".") session.run("mypy", "src", "tests") diff --git a/pyproject.toml b/pyproject.toml index 51c6a787..6dfb51d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,9 +25,7 @@ Homepage = "https://github.com/sarugaku/resolvelib" [project.optional-dependencies] lint = [ - "black==23.12.1", "ruff", - "isort", "mypy", "types-requests", ] @@ -57,16 +55,6 @@ version = {attr = "resolvelib.__version__"} [tool.distutils.bdist_wheel] universal = true - -[tool.black] -line-length = 79 -include = '^/(docs|examples|src|tasks|tests)/.+\.py$' - -[tool.isort] -profile = "black" -line_length = 79 -multi_line_output = 3 - [tool.towncrier] package = 'resolvelib' package_dir = 'src' @@ -108,6 +96,9 @@ exclude = [ "*.pyi" ] +[tool.ruff.lint.isort] +known-first-party = ["resolvelib"] + [tool.mypy] warn_unused_configs = true diff --git a/src/resolvelib/providers.py b/src/resolvelib/providers.py index f7e240f5..6d8bc470 100644 --- a/src/resolvelib/providers.py +++ b/src/resolvelib/providers.py @@ -15,8 +15,7 @@ from typing import Any, Protocol class Preference(Protocol): - def __lt__(self, __other: Any) -> bool: - ... + def __lt__(self, __other: Any) -> bool: ... class AbstractProvider(Generic[RT, CT, KT]): @@ -88,7 +87,7 @@ def find_matches( identifier: KT, requirements: Mapping[KT, Iterator[RT]], incompatibilities: Mapping[KT, Iterator[CT]], - ) -> Matches: + ) -> Matches[CT]: """Find all possible candidates that satisfy the given constraints. :param identifier: An identifier as returned by ``identify()``. All diff --git a/src/resolvelib/reporters.py b/src/resolvelib/reporters.py index 05c7226b..91afad31 100644 --- a/src/resolvelib/reporters.py +++ b/src/resolvelib/reporters.py @@ -53,9 +53,7 @@ def resolving_conflicts( :param causes: The information on the collision that caused the backtracking. """ - def rejecting_candidate( - self, criterion: Criterion[RT, CT], candidate: CT - ) -> None: + def rejecting_candidate(self, criterion: Criterion[RT, CT], candidate: CT) -> None: """Called when rejecting a candidate during backtracking.""" def pinning(self, candidate: CT) -> None: diff --git a/src/resolvelib/resolvers/abstract.py b/src/resolvelib/resolvers/abstract.py index f18324a8..047e2fd9 100644 --- a/src/resolvelib/resolvers/abstract.py +++ b/src/resolvelib/resolvers/abstract.py @@ -32,9 +32,7 @@ def __init__( self.provider = provider self.reporter = reporter - def resolve( - self, requirements: Iterable[RT], **kwargs: Any - ) -> Result[RT, CT, KT]: + def resolve(self, requirements: Iterable[RT], **kwargs: Any) -> Result[RT, CT, KT]: """Take a collection of constraints, spit out the resolution result. This returns a representation of the final resolution state, with one diff --git a/src/resolvelib/resolvers/criterion.py b/src/resolvelib/resolvers/criterion.py index bab6e6a7..bb1c7331 100644 --- a/src/resolvelib/resolvers/criterion.py +++ b/src/resolvelib/resolvers/criterion.py @@ -209,9 +209,7 @@ def _is_current_pin_satisfying( for r in criterion.iter_requirement() ) - def _get_updated_criteria( - self, candidate: CT - ) -> dict[KT, Criterion[RT, CT]]: + def _get_updated_criteria(self, candidate: CT) -> dict[KT, Criterion[RT, CT]]: criteria = self.state.criteria.copy() for requirement in self._p.get_dependencies(candidate=candidate): self._add_to_criteria(criteria, requirement, parent=candidate) @@ -245,7 +243,7 @@ def _attempt_to_pin_criterion(self, name: KT) -> list[Criterion[RT, CT]]: # Put newly-pinned candidate at the end. This is essential because # backtracking looks at this mapping to get the last pin. - self.state.mapping.pop(name, None) # type: ignore[arg-type] + self.state.mapping.pop(name, None) self.state.mapping[name] = candidate return [] @@ -347,8 +345,7 @@ def _backjump(self, causes: list[RequirementInformation[RT, CT]]) -> bool: # If the current dependencies and the incompatible dependencies # are overlapping then we have found a cause of the incompatibility current_dependencies = { - self._p.identify(d) - for d in self._p.get_dependencies(candidate) + self._p.identify(d) for d in self._p.get_dependencies(candidate) } if not current_dependencies.isdisjoint(incompatible_deps): break @@ -360,8 +357,7 @@ def _backjump(self, causes: list[RequirementInformation[RT, CT]]) -> bool: break incompatibilities_from_broken = [ - (k, list(v.incompatibilities)) - for k, v in broken_state.criteria.items() + (k, list(v.incompatibilities)) for k, v in broken_state.criteria.items() ] # Also mark the newly known incompatibility. @@ -384,13 +380,9 @@ def _extract_causes( self, criteron: list[Criterion[RT, CT]] ) -> list[RequirementInformation[RT, CT]]: """Extract causes from list of criterion and deduplicate""" - return list( - {id(i): i for c in criteron for i in c.information}.values() - ) + return list({id(i): i for c in criteron for i in c.information}.values()) - def resolve( - self, requirements: Iterable[RT], max_rounds: int - ) -> State[RT, CT, KT]: + def resolve(self, requirements: Iterable[RT], max_rounds: int) -> State[RT, CT, KT]: if self._states: raise RuntimeError("already resolved") @@ -430,9 +422,7 @@ def resolve( return self.state # keep track of satisfied names to calculate diff after pinning - satisfied_names = set(self.state.criteria.keys()) - set( - unsatisfied_names - ) + satisfied_names = set(self.state.criteria.keys()) - set(unsatisfied_names) # Choose the most preferred unpinned criterion to try. name = min(unsatisfied_names, key=self._get_preference) diff --git a/src/resolvelib/structs.py b/src/resolvelib/structs.py index 93ed3e1e..930767d1 100644 --- a/src/resolvelib/structs.py +++ b/src/resolvelib/structs.py @@ -1,13 +1,11 @@ from __future__ import annotations import itertools -from abc import ABCMeta from collections import namedtuple from typing import ( TYPE_CHECKING, Callable, Collection, - Container, Generic, Iterable, Iterator, @@ -150,7 +148,7 @@ def __len__(self) -> int: return len(self._mapping) + more -class _FactoryIterableView(Generic[RT]): +class _FactoryIterableView(Iterable[RT]): """Wrap an iterator factory returned by `find_matches()`. Calling `iter()` on this class would invoke the underlying iterator @@ -174,14 +172,12 @@ def __bool__(self) -> bool: return True def __iter__(self) -> Iterator[RT]: - iterable = ( - self._factory() if self._iterable is None else self._iterable - ) + iterable = self._factory() if self._iterable is None else self._iterable self._iterable, current = itertools.tee(iterable) return current -class _SequenceIterableView(Generic[RT]): +class _SequenceIterableView(Iterable[RT]): """Wrap an iterable returned by find_matches(). This is essentially just a proxy to the underlying sequence that provides @@ -201,19 +197,16 @@ def __iter__(self) -> Iterator[RT]: return iter(self._sequence) -class IterableView(Container[CT], Iterator[CT], metaclass=ABCMeta): - pass - - -def build_iter_view( - matches: Iterable[CT] | Callable[[], Iterable[CT]] -) -> IterableView[CT]: +def build_iter_view(matches: Matches[CT]) -> Iterable[CT]: """Build an iterable view from the value returned by `find_matches()`.""" if callable(matches): - return _FactoryIterableView(matches) # type: ignore[return-value] + return _FactoryIterableView(matches) if not isinstance(matches, Sequence): matches = list(matches) - return _SequenceIterableView(matches) # type: ignore[return-value] + return _SequenceIterableView(matches) + + +IterableView = Iterable class Criterion(Generic[RT, CT]): @@ -238,7 +231,7 @@ class Criterion(Generic[RT, CT]): def __init__( self, - candidates: IterableView[CT], + candidates: Iterable[CT], information: Collection[RequirementInformation[RT, CT]], incompatibilities: Collection[CT], ) -> None: diff --git a/tests/functional/cocoapods/test_resolvers_cocoapods.py b/tests/functional/cocoapods/test_resolvers_cocoapods.py index a7eee169..c1312cb8 100644 --- a/tests/functional/cocoapods/test_resolvers_cocoapods.py +++ b/tests/functional/cocoapods/test_resolvers_cocoapods.py @@ -165,8 +165,7 @@ def __init__(self, filename): for key, spec in case_data["requested"].items() ] self.pinned_versions = { - entry["name"]: Version(entry["version"]) - for entry in case_data["base"] + entry["name"]: Version(entry["version"]) for entry in case_data["base"] } self.expected_resolution = dict(_iter_resolved(case_data["resolved"])) self.expected_conflicts = set(case_data["conflicts"]) @@ -193,8 +192,7 @@ def _iter_matches(self, name, requirements, incompatibilities): for entry in data: version = Version(entry["version"]) if any( - not _version_in_specset(version, r.spec) - for r in requirements[name] + not _version_in_specset(version, r.spec) for r in requirements[name] ): continue if version in bad_versions: @@ -253,8 +251,7 @@ def _format_conflicts(exc): def _format_resolution(result): return { - identifier: candidate.ver - for identifier, candidate in result.mapping.items() + identifier: candidate.ver for identifier, candidate in result.mapping.items() } diff --git a/tests/functional/python/py2index.py b/tests/functional/python/py2index.py index 710831fe..86f875ee 100644 --- a/tests/functional/python/py2index.py +++ b/tests/functional/python/py2index.py @@ -251,9 +251,7 @@ def iter_package_entries(self, name: str) -> Iterator[PackageEntry]: dependencies: list[str] = data.get_all("Requires-Dist", []) yield PackageEntry(version, dependencies) - def process_package_entry( - self, name: str, entry: PackageEntry - ) -> set[str] | None: + def process_package_entry(self, name: str, entry: PackageEntry) -> set[str] | None: more = set() for dep in entry.dependencies: try: diff --git a/tests/functional/python/test_resolvers_python.py b/tests/functional/python/test_resolvers_python.py index 0847354b..18c15504 100644 --- a/tests/functional/python/test_resolvers_python.py +++ b/tests/functional/python/test_resolvers_python.py @@ -37,16 +37,13 @@ def __init__(self, filename): case_data = json.load(f) index_name = os.path.normpath( - os.path.join( - filename, "..", "..", "index", case_data["index"] + ".json" - ), + os.path.join(filename, "..", "..", "index", case_data["index"] + ".json"), ) with open(index_name) as f: self.index = json.load(f) self.root_requirements = [ - packaging.requirements.Requirement(r) - for r in case_data["requested"] + packaging.requirements.Requirement(r) for r in case_data["requested"] ] if "resolved" in case_data: @@ -182,17 +179,13 @@ def test_resolver(provider, reporter): if provider.expected_unvisited: visited_versions = defaultdict(set) for visited_candidate in reporter.visited: - visited_versions[visited_candidate.name].add( - str(visited_candidate.version) - ) + visited_versions[visited_candidate.name].add(str(visited_candidate.version)) for name, versions in provider.expected_unvisited.items(): if name not in visited_versions: continue - unexpected_versions = set(versions).intersection( - visited_versions[name] - ) + unexpected_versions = set(versions).intersection(visited_versions[name]) assert ( not unexpected_versions ), f"Unexpcted versions visited {name}: {', '.join(unexpected_versions)}" diff --git a/tests/functional/swift-package-manager/test_resolvers_swift.py b/tests/functional/swift-package-manager/test_resolvers_swift.py index 673ae45a..ce9b73e4 100644 --- a/tests/functional/swift-package-manager/test_resolvers_swift.py +++ b/tests/functional/swift-package-manager/test_resolvers_swift.py @@ -66,8 +66,7 @@ def __init__(self, filename): input_data = json.load(f) self.containers = { - container["identifier"]: container - for container in input_data["containers"] + container["identifier"]: container for container in input_data["containers"] } self.root_requirements = [ Requirement(self.containers[constraint["identifier"]], constraint) diff --git a/tests/test_resolvers.py b/tests/test_resolvers.py index c4440b11..3c0ff5c0 100644 --- a/tests/test_resolvers.py +++ b/tests/test_resolvers.py @@ -115,9 +115,7 @@ def test_resolving_conflicts(): Candidate = namedtuple( "Candidate", ["name", "version", "requirements"] ) # name, version, requirements - _Requirement = namedtuple( - "Requirement", ["name", "versions"] - ) # name, versions + _Requirement = namedtuple("Requirement", ["name", "versions"]) # name, versions a1 = Candidate("a", 1, [_Requirement("q", {1})]) a2 = Candidate("a", 2, [_Requirement("q", {2})]) b = Candidate("b", 1, [_Requirement("q", {1})]) @@ -151,9 +149,7 @@ def find_matches(self, identifier, requirements, incompatibilities): candidates = [ c for c in all_candidates[identifier] - if all( - c.version in r.versions for r in requirements[identifier] - ) + if all(c.version in r.versions for r in requirements[identifier]) and c.version not in bad_versions ] return sorted(candidates, key=lambda c: c.version, reverse=True) @@ -173,9 +169,7 @@ def run_resolver(*args): backtracking_causes = run_resolver( [_Requirement("a", {1, 2}), _Requirement("b", {1})] ) - exception_causes = run_resolver( - [_Requirement("a", {2}), _Requirement("b", {1})] - ) + exception_causes = run_resolver([_Requirement("a", {2}), _Requirement("b", {1})]) assert exception_causes == backtracking_causes @@ -210,9 +204,7 @@ def identify(self, requirement_or_candidate: str | Candidate) -> str: assert result in all_candidates, "unknown requirement_or_candidate" return result - def get_preference( - self, identifier: str, *args: Any, **kwargs: Any - ) -> str: + def get_preference(self, identifier: str, *args: Any, **kwargs: Any) -> str: # prefer child over parent (alphabetically) return identifier @@ -235,16 +227,12 @@ def find_matches( if candidate not in incompatibilities[identifier] ) - def is_satisfied_by( - self, requirement: str, candidate: Candidate - ) -> bool: + def is_satisfied_by(self, requirement: str, candidate: Candidate) -> bool: return candidate[1] in Requirement(requirement).specifier # patch Resolution._get_updated_criteria to collect rejected states rejected_criteria: list[Criterion] = [] - get_updated_criteria_orig = ( - Resolution._get_updated_criteria # type: ignore[attr-defined] - ) + get_updated_criteria_orig = Resolution._get_updated_criteria def get_updated_criteria_patch(self, candidate): try: @@ -253,9 +241,7 @@ def get_updated_criteria_patch(self, candidate): rejected_criteria.append(e.criterion) raise - monkeypatch.setattr( - Resolution, "_get_updated_criteria", get_updated_criteria_patch - ) + monkeypatch.setattr(Resolution, "_get_updated_criteria", get_updated_criteria_patch) resolver: Resolver[str, Candidate, str] = Resolver(Provider(), reporter) result = resolver.resolve(["child", "parent"])