Source code for thoth.solver.python.base
#!/usr/bin/env python3
# thoth-solver
# Copyright(C) 2018 Pavel Odvody
# Copyright(C) 2018 - 2021 Fridolin Pokorny
#
# This program is free software: you can redistribute it and / or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Classes for resolving dependencies as specified in each ecosystem."""
import logging
import attr
import abc
from thoth.python import Source
from ..exceptions import NoReleasesFound
from ..exceptions import SolverException
from .._typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING: # pragma: no cover
from typing import List, Tuple, Dict
from packaging.requirements import Requirement
_LOGGER = logging.getLogger(__name__)
[docs]@attr.s(slots=True)
class ReleasesFetcher:
"""A base class for fetching package releases."""
[docs] @abc.abstractmethod
def fetch_releases(self, package): # type: (str) -> Tuple[str, List[Tuple[str, str]]]
"""Abstract method for getting list of releases versions."""
[docs] @abc.abstractmethod
def index_url(self): # type: () -> str
"""Get URL to index from where releases are fetched."""
[docs]@attr.s(slots=True)
class DependencyParser:
"""Base class for Dependency parsing."""
[docs] @abc.abstractmethod
def parse(self, specs): # type: (List[str]) -> List[Requirement]
"""Abstract method for dependency parsing."""
[docs]@attr.s(slots=True)
class Solver:
"""Base class for resolving dependencies."""
dependency_parser = attr.ib(type=DependencyParser, kw_only=True)
releases_fetcher = attr.ib(type=ReleasesFetcher, kw_only=True)
[docs] def solve(self, dependencies, graceful=True): # type: (List[str], bool) -> Dict[str, List[Tuple[str, str]]]
"""Solve `dependencies` against a repository."""
solved = {} # type: Dict[str, List[Tuple[str, str]]]
for dep in self.dependency_parser.parse(dependencies):
_LOGGER.debug("Fetching releases for: %r", dep)
name, releases = self.releases_fetcher.fetch_releases(dep.name)
if name in solved:
raise SolverException("Dependency: {} is listed multiple times".format(name))
if not releases:
if graceful:
_LOGGER.info("No releases found for package %r", dep.name)
continue
else:
raise NoReleasesFound("No releases found for package {!r}".format(dep.name))
solved[name] = []
for release in releases:
if release[0] in dep.specifier:
solved[name].append(release)
_LOGGER.debug(" matching: %s", solved[name])
return solved
[docs]def get_ecosystem_solver(ecosystem_name): # type: (str) -> Solver
"""Get Solver subclass instance for particular ecosystem.
:param ecosystem_name: name of ecosystem for which solver should be get
:return: Solver
"""
from .python_solver import PythonSolver
from .python_solver import PythonReleasesFetcher
from .python_solver import PythonDependencyParser
if ecosystem_name.lower() == "pypi":
source = Source(url="https://pypi.org/simple", warehouse_api_url="https://pypi.org/pypi", warehouse=True)
return PythonSolver(
dependency_parser=PythonDependencyParser(), releases_fetcher=PythonReleasesFetcher(source=source),
)
raise NotImplementedError("Unknown ecosystem: {}".format(ecosystem_name))