about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib')
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py26
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py6
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py133
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/py.typed0
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py43
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py547
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py170
8 files changed, 925 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py
new file mode 100644
index 00000000..d92acc7b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py
@@ -0,0 +1,26 @@
+__all__ = [
+    "__version__",
+    "AbstractProvider",
+    "AbstractResolver",
+    "BaseReporter",
+    "InconsistentCandidate",
+    "Resolver",
+    "RequirementsConflicted",
+    "ResolutionError",
+    "ResolutionImpossible",
+    "ResolutionTooDeep",
+]
+
+__version__ = "1.0.1"
+
+
+from .providers import AbstractProvider, AbstractResolver
+from .reporters import BaseReporter
+from .resolvers import (
+    InconsistentCandidate,
+    RequirementsConflicted,
+    ResolutionError,
+    ResolutionImpossible,
+    ResolutionTooDeep,
+    Resolver,
+)
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py
new file mode 100644
index 00000000..1becc509
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py
@@ -0,0 +1,6 @@
+__all__ = ["Mapping", "Sequence"]
+
+try:
+    from collections.abc import Mapping, Sequence
+except ImportError:
+    from collections import Mapping, Sequence
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py
new file mode 100644
index 00000000..e99d87ee
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py
@@ -0,0 +1,133 @@
+class AbstractProvider(object):
+    """Delegate class to provide the required interface for the resolver."""
+
+    def identify(self, requirement_or_candidate):
+        """Given a requirement, return an identifier for it.
+
+        This is used to identify a requirement, e.g. whether two requirements
+        should have their specifier parts merged.
+        """
+        raise NotImplementedError
+
+    def get_preference(
+        self,
+        identifier,
+        resolutions,
+        candidates,
+        information,
+        backtrack_causes,
+    ):
+        """Produce a sort key for given requirement based on preference.
+
+        The preference is defined as "I think this requirement should be
+        resolved first". The lower the return value is, the more preferred
+        this group of arguments is.
+
+        :param identifier: An identifier as returned by ``identify()``. This
+            identifies the dependency matches which should be returned.
+        :param resolutions: Mapping of candidates currently pinned by the
+            resolver. Each key is an identifier, and the value is a candidate.
+            The candidate may conflict with requirements from ``information``.
+        :param candidates: Mapping of each dependency's possible candidates.
+            Each value is an iterator of candidates.
+        :param information: Mapping of requirement information of each package.
+            Each value is an iterator of *requirement information*.
+        :param backtrack_causes: Sequence of requirement information that were
+            the requirements that caused the resolver to most recently backtrack.
+
+        A *requirement information* instance is a named tuple with two members:
+
+        * ``requirement`` specifies a requirement contributing to the current
+          list of candidates.
+        * ``parent`` specifies the candidate that provides (depended on) the
+          requirement, or ``None`` to indicate a root requirement.
+
+        The preference could depend on various issues, including (not
+        necessarily in this order):
+
+        * Is this package pinned in the current resolution result?
+        * How relaxed is the requirement? Stricter ones should probably be
+          worked on first? (I don't know, actually.)
+        * How many possibilities are there to satisfy this requirement? Those
+          with few left should likely be worked on first, I guess?
+        * Are there any known conflicts for this requirement? We should
+          probably work on those with the most known conflicts.
+
+        A sortable value should be returned (this will be used as the ``key``
+        parameter of the built-in sorting function). The smaller the value is,
+        the more preferred this requirement is (i.e. the sorting function
+        is called with ``reverse=False``).
+        """
+        raise NotImplementedError
+
+    def find_matches(self, identifier, requirements, incompatibilities):
+        """Find all possible candidates that satisfy the given constraints.
+
+        :param identifier: An identifier as returned by ``identify()``. This
+            identifies the dependency matches of which should be returned.
+        :param requirements: A mapping of requirements that all returned
+            candidates must satisfy. Each key is an identifier, and the value
+            an iterator of requirements for that dependency.
+        :param incompatibilities: A mapping of known incompatibilities of
+            each dependency. Each key is an identifier, and the value an
+            iterator of incompatibilities known to the resolver. All
+            incompatibilities *must* be excluded from the return value.
+
+        This should try to get candidates based on the requirements' types.
+        For VCS, local, and archive requirements, the one-and-only match is
+        returned, and for a "named" requirement, the index(es) should be
+        consulted to find concrete candidates for this requirement.
+
+        The return value should produce candidates ordered by preference; the
+        most preferred candidate should come first. The return type may be one
+        of the following:
+
+        * A callable that returns an iterator that yields candidates.
+        * An collection of candidates.
+        * An iterable of candidates. This will be consumed immediately into a
+          list of candidates.
+        """
+        raise NotImplementedError
+
+    def is_satisfied_by(self, requirement, candidate):
+        """Whether the given requirement can be satisfied by a candidate.
+
+        The candidate is guaranteed to have been generated from the
+        requirement.
+
+        A boolean should be returned to indicate whether ``candidate`` is a
+        viable solution to the requirement.
+        """
+        raise NotImplementedError
+
+    def get_dependencies(self, candidate):
+        """Get dependencies of a candidate.
+
+        This should return a collection of requirements that `candidate`
+        specifies as its dependencies.
+        """
+        raise NotImplementedError
+
+
+class AbstractResolver(object):
+    """The thing that performs the actual resolution work."""
+
+    base_exception = Exception
+
+    def __init__(self, provider, reporter):
+        self.provider = provider
+        self.reporter = reporter
+
+    def resolve(self, requirements, **kwargs):
+        """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/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/py.typed b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/py.typed
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/py.typed
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py
new file mode 100644
index 00000000..688b5e10
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py
@@ -0,0 +1,43 @@
+class BaseReporter(object):
+    """Delegate class to provider progress reporting for the resolver."""
+
+    def starting(self):
+        """Called before the resolution actually starts."""
+
+    def starting_round(self, index):
+        """Called before each round of resolution starts.
+
+        The index is zero-based.
+        """
+
+    def ending_round(self, index, state):
+        """Called before each round of resolution ends.
+
+        This is NOT called if the resolution ends at this round. Use `ending`
+        if you want to report finalization. The index is zero-based.
+        """
+
+    def ending(self, state):
+        """Called before the resolution ends successfully."""
+
+    def adding_requirement(self, requirement, parent):
+        """Called when adding a new requirement into the resolve criteria.
+
+        :param requirement: The additional requirement to be applied to filter
+            the available candidaites.
+        :param parent: The candidate that requires ``requirement`` as a
+            dependency, or None if ``requirement`` is one of the root
+            requirements passed in from ``Resolver.resolve()``.
+        """
+
+    def resolving_conflicts(self, causes):
+        """Called when starting to attempt requirement conflict resolution.
+
+        :param causes: The information on the collision that caused the backtracking.
+        """
+
+    def rejecting_candidate(self, criterion, candidate):
+        """Called when rejecting a candidate during backtracking."""
+
+    def pinning(self, candidate):
+        """Called when adding a candidate to the potential solution."""
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py
new file mode 100644
index 00000000..2c3d0e30
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py
@@ -0,0 +1,547 @@
+import collections
+import itertools
+import operator
+
+from .providers import AbstractResolver
+from .structs import DirectedGraph, IteratorMapping, build_iter_view
+
+RequirementInformation = collections.namedtuple(
+    "RequirementInformation", ["requirement", "parent"]
+)
+
+
+class ResolverException(Exception):
+    """A base class for all exceptions raised by this module.
+
+    Exceptions derived by this class should all be handled in this module. Any
+    bubbling pass the resolver should be treated as a bug.
+    """
+
+
+class RequirementsConflicted(ResolverException):
+    def __init__(self, criterion):
+        super(RequirementsConflicted, self).__init__(criterion)
+        self.criterion = criterion
+
+    def __str__(self):
+        return "Requirements conflict: {}".format(
+            ", ".join(repr(r) for r in self.criterion.iter_requirement()),
+        )
+
+
+class InconsistentCandidate(ResolverException):
+    def __init__(self, candidate, criterion):
+        super(InconsistentCandidate, self).__init__(candidate, criterion)
+        self.candidate = candidate
+        self.criterion = criterion
+
+    def __str__(self):
+        return "Provided candidate {!r} does not satisfy {}".format(
+            self.candidate,
+            ", ".join(repr(r) for r in self.criterion.iter_requirement()),
+        )
+
+
+class Criterion(object):
+    """Representation of possible resolution results of a package.
+
+    This holds three attributes:
+
+    * `information` is a collection of `RequirementInformation` pairs.
+      Each pair is a requirement contributing to this criterion, and the
+      candidate that provides the requirement.
+    * `incompatibilities` is a collection of all known not-to-work candidates
+      to exclude from consideration.
+    * `candidates` is a collection containing all possible candidates deducted
+      from the union of contributing requirements and known incompatibilities.
+      It should never be empty, except when the criterion is an attribute of a
+      raised `RequirementsConflicted` (in which case it is always empty).
+
+    .. note::
+        This class is intended to be externally immutable. **Do not** mutate
+        any of its attribute containers.
+    """
+
+    def __init__(self, candidates, information, incompatibilities):
+        self.candidates = candidates
+        self.information = information
+        self.incompatibilities = incompatibilities
+
+    def __repr__(self):
+        requirements = ", ".join(
+            "({!r}, via={!r})".format(req, parent)
+            for req, parent in self.information
+        )
+        return "Criterion({})".format(requirements)
+
+    def iter_requirement(self):
+        return (i.requirement for i in self.information)
+
+    def iter_parent(self):
+        return (i.parent for i in self.information)
+
+
+class ResolutionError(ResolverException):
+    pass
+
+
+class ResolutionImpossible(ResolutionError):
+    def __init__(self, causes):
+        super(ResolutionImpossible, self).__init__(causes)
+        # causes is a list of RequirementInformation objects
+        self.causes = causes
+
+
+class ResolutionTooDeep(ResolutionError):
+    def __init__(self, round_count):
+        super(ResolutionTooDeep, self).__init__(round_count)
+        self.round_count = round_count
+
+
+# Resolution state in a round.
+State = collections.namedtuple("State", "mapping criteria backtrack_causes")
+
+
+class Resolution(object):
+    """Stateful resolution object.
+
+    This is designed as a one-off object that holds information to kick start
+    the resolution process, and holds the results afterwards.
+    """
+
+    def __init__(self, provider, reporter):
+        self._p = provider
+        self._r = reporter
+        self._states = []
+
+    @property
+    def state(self):
+        try:
+            return self._states[-1]
+        except IndexError:
+            raise AttributeError("state")
+
+    def _push_new_state(self):
+        """Push a new state into history.
+
+        This new state will be used to hold resolution results of the next
+        coming round.
+        """
+        base = self._states[-1]
+        state = State(
+            mapping=base.mapping.copy(),
+            criteria=base.criteria.copy(),
+            backtrack_causes=base.backtrack_causes[:],
+        )
+        self._states.append(state)
+
+    def _add_to_criteria(self, criteria, requirement, parent):
+        self._r.adding_requirement(requirement=requirement, parent=parent)
+
+        identifier = self._p.identify(requirement_or_candidate=requirement)
+        criterion = criteria.get(identifier)
+        if criterion:
+            incompatibilities = list(criterion.incompatibilities)
+        else:
+            incompatibilities = []
+
+        matches = self._p.find_matches(
+            identifier=identifier,
+            requirements=IteratorMapping(
+                criteria,
+                operator.methodcaller("iter_requirement"),
+                {identifier: [requirement]},
+            ),
+            incompatibilities=IteratorMapping(
+                criteria,
+                operator.attrgetter("incompatibilities"),
+                {identifier: incompatibilities},
+            ),
+        )
+
+        if criterion:
+            information = list(criterion.information)
+            information.append(RequirementInformation(requirement, parent))
+        else:
+            information = [RequirementInformation(requirement, parent)]
+
+        criterion = Criterion(
+            candidates=build_iter_view(matches),
+            information=information,
+            incompatibilities=incompatibilities,
+        )
+        if not criterion.candidates:
+            raise RequirementsConflicted(criterion)
+        criteria[identifier] = criterion
+
+    def _remove_information_from_criteria(self, criteria, parents):
+        """Remove information from parents of criteria.
+
+        Concretely, removes all values from each criterion's ``information``
+        field that have one of ``parents`` as provider of the requirement.
+
+        :param criteria: The criteria to update.
+        :param parents: Identifiers for which to remove information from all criteria.
+        """
+        if not parents:
+            return
+        for key, criterion in criteria.items():
+            criteria[key] = Criterion(
+                criterion.candidates,
+                [
+                    information
+                    for information in criterion.information
+                    if (
+                        information.parent is None
+                        or self._p.identify(information.parent) not in parents
+                    )
+                ],
+                criterion.incompatibilities,
+            )
+
+    def _get_preference(self, name):
+        return self._p.get_preference(
+            identifier=name,
+            resolutions=self.state.mapping,
+            candidates=IteratorMapping(
+                self.state.criteria,
+                operator.attrgetter("candidates"),
+            ),
+            information=IteratorMapping(
+                self.state.criteria,
+                operator.attrgetter("information"),
+            ),
+            backtrack_causes=self.state.backtrack_causes,
+        )
+
+    def _is_current_pin_satisfying(self, name, criterion):
+        try:
+            current_pin = self.state.mapping[name]
+        except KeyError:
+            return False
+        return all(
+            self._p.is_satisfied_by(requirement=r, candidate=current_pin)
+            for r in criterion.iter_requirement()
+        )
+
+    def _get_updated_criteria(self, candidate):
+        criteria = self.state.criteria.copy()
+        for requirement in self._p.get_dependencies(candidate=candidate):
+            self._add_to_criteria(criteria, requirement, parent=candidate)
+        return criteria
+
+    def _attempt_to_pin_criterion(self, name):
+        criterion = self.state.criteria[name]
+
+        causes = []
+        for candidate in criterion.candidates:
+            try:
+                criteria = self._get_updated_criteria(candidate)
+            except RequirementsConflicted as e:
+                self._r.rejecting_candidate(e.criterion, candidate)
+                causes.append(e.criterion)
+                continue
+
+            # Check the newly-pinned candidate actually works. This should
+            # always pass under normal circumstances, but in the case of a
+            # faulty provider, we will raise an error to notify the implementer
+            # to fix find_matches() and/or is_satisfied_by().
+            satisfied = all(
+                self._p.is_satisfied_by(requirement=r, candidate=candidate)
+                for r in criterion.iter_requirement()
+            )
+            if not satisfied:
+                raise InconsistentCandidate(candidate, criterion)
+
+            self._r.pinning(candidate=candidate)
+            self.state.criteria.update(criteria)
+
+            # 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)
+            self.state.mapping[name] = candidate
+
+            return []
+
+        # All candidates tried, nothing works. This criterion is a dead
+        # end, signal for backtracking.
+        return causes
+
+    def _backjump(self, causes):
+        """Perform backjumping.
+
+        When we enter here, the stack is like this::
+
+            [ state Z ]
+            [ state Y ]
+            [ state X ]
+            .... earlier states are irrelevant.
+
+        1. No pins worked for Z, so it does not have a pin.
+        2. We want to reset state Y to unpinned, and pin another candidate.
+        3. State X holds what state Y was before the pin, but does not
+           have the incompatibility information gathered in state Y.
+
+        Each iteration of the loop will:
+
+        1.  Identify Z. The incompatibility is not always caused by the latest
+            state. For example, given three requirements A, B and C, with
+            dependencies A1, B1 and C1, where A1 and B1 are incompatible: the
+            last state might be related to C, so we want to discard the
+            previous state.
+        2.  Discard Z.
+        3.  Discard Y but remember its incompatibility information gathered
+            previously, and the failure we're dealing with right now.
+        4.  Push a new state Y' based on X, and apply the incompatibility
+            information from Y to Y'.
+        5a. If this causes Y' to conflict, we need to backtrack again. Make Y'
+            the new Z and go back to step 2.
+        5b. If the incompatibilities apply cleanly, end backtracking.
+        """
+        incompatible_reqs = itertools.chain(
+            (c.parent for c in causes if c.parent is not None),
+            (c.requirement for c in causes),
+        )
+        incompatible_deps = {self._p.identify(r) for r in incompatible_reqs}
+        while len(self._states) >= 3:
+            # Remove the state that triggered backtracking.
+            del self._states[-1]
+
+            # Ensure to backtrack to a state that caused the incompatibility
+            incompatible_state = False
+            while not incompatible_state:
+                # Retrieve the last candidate pin and known incompatibilities.
+                try:
+                    broken_state = self._states.pop()
+                    name, candidate = broken_state.mapping.popitem()
+                except (IndexError, KeyError):
+                    raise ResolutionImpossible(causes)
+                current_dependencies = {
+                    self._p.identify(d)
+                    for d in self._p.get_dependencies(candidate)
+                }
+                incompatible_state = not current_dependencies.isdisjoint(
+                    incompatible_deps
+                )
+
+            incompatibilities_from_broken = [
+                (k, list(v.incompatibilities))
+                for k, v in broken_state.criteria.items()
+            ]
+
+            # Also mark the newly known incompatibility.
+            incompatibilities_from_broken.append((name, [candidate]))
+
+            # Create a new state from the last known-to-work one, and apply
+            # the previously gathered incompatibility information.
+            def _patch_criteria():
+                for k, incompatibilities in incompatibilities_from_broken:
+                    if not incompatibilities:
+                        continue
+                    try:
+                        criterion = self.state.criteria[k]
+                    except KeyError:
+                        continue
+                    matches = self._p.find_matches(
+                        identifier=k,
+                        requirements=IteratorMapping(
+                            self.state.criteria,
+                            operator.methodcaller("iter_requirement"),
+                        ),
+                        incompatibilities=IteratorMapping(
+                            self.state.criteria,
+                            operator.attrgetter("incompatibilities"),
+                            {k: incompatibilities},
+                        ),
+                    )
+                    candidates = build_iter_view(matches)
+                    if not candidates:
+                        return False
+                    incompatibilities.extend(criterion.incompatibilities)
+                    self.state.criteria[k] = Criterion(
+                        candidates=candidates,
+                        information=list(criterion.information),
+                        incompatibilities=incompatibilities,
+                    )
+                return True
+
+            self._push_new_state()
+            success = _patch_criteria()
+
+            # It works! Let's work on this new state.
+            if success:
+                return True
+
+            # State does not work after applying known incompatibilities.
+            # Try the still previous state.
+
+        # No way to backtrack anymore.
+        return False
+
+    def resolve(self, requirements, max_rounds):
+        if self._states:
+            raise RuntimeError("already resolved")
+
+        self._r.starting()
+
+        # Initialize the root state.
+        self._states = [
+            State(
+                mapping=collections.OrderedDict(),
+                criteria={},
+                backtrack_causes=[],
+            )
+        ]
+        for r in requirements:
+            try:
+                self._add_to_criteria(self.state.criteria, r, parent=None)
+            except RequirementsConflicted as e:
+                raise ResolutionImpossible(e.criterion.information)
+
+        # The root state is saved as a sentinel so the first ever pin can have
+        # something to backtrack to if it fails. The root state is basically
+        # pinning the virtual "root" package in the graph.
+        self._push_new_state()
+
+        for round_index in range(max_rounds):
+            self._r.starting_round(index=round_index)
+
+            unsatisfied_names = [
+                key
+                for key, criterion in self.state.criteria.items()
+                if not self._is_current_pin_satisfying(key, criterion)
+            ]
+
+            # All criteria are accounted for. Nothing more to pin, we are done!
+            if not unsatisfied_names:
+                self._r.ending(state=self.state)
+                return self.state
+
+            # keep track of satisfied names to calculate diff after pinning
+            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)
+            failure_causes = self._attempt_to_pin_criterion(name)
+
+            if failure_causes:
+                causes = [i for c in failure_causes for i in c.information]
+                # Backjump if pinning fails. The backjump process puts us in
+                # an unpinned state, so we can work on it in the next round.
+                self._r.resolving_conflicts(causes=causes)
+                success = self._backjump(causes)
+                self.state.backtrack_causes[:] = causes
+
+                # Dead ends everywhere. Give up.
+                if not success:
+                    raise ResolutionImpossible(self.state.backtrack_causes)
+            else:
+                # discard as information sources any invalidated names
+                # (unsatisfied names that were previously satisfied)
+                newly_unsatisfied_names = {
+                    key
+                    for key, criterion in self.state.criteria.items()
+                    if key in satisfied_names
+                    and not self._is_current_pin_satisfying(key, criterion)
+                }
+                self._remove_information_from_criteria(
+                    self.state.criteria, newly_unsatisfied_names
+                )
+                # Pinning was successful. Push a new state to do another pin.
+                self._push_new_state()
+
+            self._r.ending_round(index=round_index, state=self.state)
+
+        raise ResolutionTooDeep(max_rounds)
+
+
+def _has_route_to_root(criteria, key, all_keys, connected):
+    if key in connected:
+        return True
+    if key not in criteria:
+        return False
+    for p in criteria[key].iter_parent():
+        try:
+            pkey = all_keys[id(p)]
+        except KeyError:
+            continue
+        if pkey in connected:
+            connected.add(key)
+            return True
+        if _has_route_to_root(criteria, pkey, all_keys, connected):
+            connected.add(key)
+            return True
+    return False
+
+
+Result = collections.namedtuple("Result", "mapping graph criteria")
+
+
+def _build_result(state):
+    mapping = state.mapping
+    all_keys = {id(v): k for k, v in mapping.items()}
+    all_keys[id(None)] = None
+
+    graph = DirectedGraph()
+    graph.add(None)  # Sentinel as root dependencies' parent.
+
+    connected = {None}
+    for key, criterion in state.criteria.items():
+        if not _has_route_to_root(state.criteria, key, all_keys, connected):
+            continue
+        if key not in graph:
+            graph.add(key)
+        for p in criterion.iter_parent():
+            try:
+                pkey = all_keys[id(p)]
+            except KeyError:
+                continue
+            if pkey not in graph:
+                graph.add(pkey)
+            graph.connect(pkey, key)
+
+    return Result(
+        mapping={k: v for k, v in mapping.items() if k in connected},
+        graph=graph,
+        criteria=state.criteria,
+    )
+
+
+class Resolver(AbstractResolver):
+    """The thing that performs the actual resolution work."""
+
+    base_exception = ResolverException
+
+    def resolve(self, requirements, max_rounds=100):
+        """Take a collection of constraints, spit out the resolution result.
+
+        The return value is a representation to the final resolution result. It
+        is a tuple subclass with three public members:
+
+        * `mapping`: A dict of resolved candidates. Each key is an identifier
+            of a requirement (as returned by the provider's `identify` method),
+            and the value is the resolved candidate.
+        * `graph`: A `DirectedGraph` instance representing the dependency tree.
+            The vertices are keys of `mapping`, and each edge represents *why*
+            a particular package is included. A special vertex `None` is
+            included to represent parents of user-supplied requirements.
+        * `criteria`: A dict of "criteria" that hold detailed information on
+            how edges in the graph are derived. Each key is an identifier of a
+            requirement, and the value is a `Criterion` instance.
+
+        The following exceptions may be raised if a resolution cannot be found:
+
+        * `ResolutionImpossible`: A resolution cannot be found for the given
+            combination of requirements. The `causes` attribute of the
+            exception is a list of (requirement, parent), giving the
+            requirements that could not be satisfied.
+        * `ResolutionTooDeep`: The dependency tree is too deeply nested and
+            the resolver gave up. This is usually caused by a circular
+            dependency, but you can try to resolve this by increasing the
+            `max_rounds` argument.
+        """
+        resolution = Resolution(self.provider, self.reporter)
+        state = resolution.resolve(requirements, max_rounds=max_rounds)
+        return _build_result(state)
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py
new file mode 100644
index 00000000..359a34f6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py
@@ -0,0 +1,170 @@
+import itertools
+
+from .compat import collections_abc
+
+
+class DirectedGraph(object):
+    """A graph structure with directed edges."""
+
+    def __init__(self):
+        self._vertices = set()
+        self._forwards = {}  # <key> -> Set[<key>]
+        self._backwards = {}  # <key> -> Set[<key>]
+
+    def __iter__(self):
+        return iter(self._vertices)
+
+    def __len__(self):
+        return len(self._vertices)
+
+    def __contains__(self, key):
+        return key in self._vertices
+
+    def copy(self):
+        """Return a shallow copy of this graph."""
+        other = DirectedGraph()
+        other._vertices = set(self._vertices)
+        other._forwards = {k: set(v) for k, v in self._forwards.items()}
+        other._backwards = {k: set(v) for k, v in self._backwards.items()}
+        return other
+
+    def add(self, key):
+        """Add a new vertex to the graph."""
+        if key in self._vertices:
+            raise ValueError("vertex exists")
+        self._vertices.add(key)
+        self._forwards[key] = set()
+        self._backwards[key] = set()
+
+    def remove(self, key):
+        """Remove a vertex from the graph, disconnecting all edges from/to it."""
+        self._vertices.remove(key)
+        for f in self._forwards.pop(key):
+            self._backwards[f].remove(key)
+        for t in self._backwards.pop(key):
+            self._forwards[t].remove(key)
+
+    def connected(self, f, t):
+        return f in self._backwards[t] and t in self._forwards[f]
+
+    def connect(self, f, t):
+        """Connect two existing vertices.
+
+        Nothing happens if the vertices are already connected.
+        """
+        if t not in self._vertices:
+            raise KeyError(t)
+        self._forwards[f].add(t)
+        self._backwards[t].add(f)
+
+    def iter_edges(self):
+        for f, children in self._forwards.items():
+            for t in children:
+                yield f, t
+
+    def iter_children(self, key):
+        return iter(self._forwards[key])
+
+    def iter_parents(self, key):
+        return iter(self._backwards[key])
+
+
+class IteratorMapping(collections_abc.Mapping):
+    def __init__(self, mapping, accessor, appends=None):
+        self._mapping = mapping
+        self._accessor = accessor
+        self._appends = appends or {}
+
+    def __repr__(self):
+        return "IteratorMapping({!r}, {!r}, {!r})".format(
+            self._mapping,
+            self._accessor,
+            self._appends,
+        )
+
+    def __bool__(self):
+        return bool(self._mapping or self._appends)
+
+    __nonzero__ = __bool__  # XXX: Python 2.
+
+    def __contains__(self, key):
+        return key in self._mapping or key in self._appends
+
+    def __getitem__(self, k):
+        try:
+            v = self._mapping[k]
+        except KeyError:
+            return iter(self._appends[k])
+        return itertools.chain(self._accessor(v), self._appends.get(k, ()))
+
+    def __iter__(self):
+        more = (k for k in self._appends if k not in self._mapping)
+        return itertools.chain(self._mapping, more)
+
+    def __len__(self):
+        more = sum(1 for k in self._appends if k not in self._mapping)
+        return len(self._mapping) + more
+
+
+class _FactoryIterableView(object):
+    """Wrap an iterator factory returned by `find_matches()`.
+
+    Calling `iter()` on this class would invoke the underlying iterator
+    factory, making it a "collection with ordering" that can be iterated
+    through multiple times, but lacks random access methods presented in
+    built-in Python sequence types.
+    """
+
+    def __init__(self, factory):
+        self._factory = factory
+        self._iterable = None
+
+    def __repr__(self):
+        return "{}({})".format(type(self).__name__, list(self))
+
+    def __bool__(self):
+        try:
+            next(iter(self))
+        except StopIteration:
+            return False
+        return True
+
+    __nonzero__ = __bool__  # XXX: Python 2.
+
+    def __iter__(self):
+        iterable = (
+            self._factory() if self._iterable is None else self._iterable
+        )
+        self._iterable, current = itertools.tee(iterable)
+        return current
+
+
+class _SequenceIterableView(object):
+    """Wrap an iterable returned by find_matches().
+
+    This is essentially just a proxy to the underlying sequence that provides
+    the same interface as `_FactoryIterableView`.
+    """
+
+    def __init__(self, sequence):
+        self._sequence = sequence
+
+    def __repr__(self):
+        return "{}({})".format(type(self).__name__, self._sequence)
+
+    def __bool__(self):
+        return bool(self._sequence)
+
+    __nonzero__ = __bool__  # XXX: Python 2.
+
+    def __iter__(self):
+        return iter(self._sequence)
+
+
+def build_iter_view(matches):
+    """Build an iterable view from the value returned by `find_matches()`."""
+    if callable(matches):
+        return _FactoryIterableView(matches)
+    if not isinstance(matches, collections_abc.Sequence):
+        matches = list(matches)
+    return _SequenceIterableView(matches)