diff --git a/poetry.lock b/poetry.lock index 0828293362c..8506a0aeb17 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "build" @@ -1595,4 +1595,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "85d42389685870cb956cc70109a816d5a68c1ea2987121679180ddd1aa4c4498" +content-hash = "f1a7e48d5159bf5194d129c4f536aea468509ad55b178090290ce4da7bdac125" diff --git a/pyproject.toml b/pyproject.toml index 4d43988ad18..60cde576e23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,6 @@ poetry-plugin-export = "^1.6.0" build = "^1.0.3" cachecontrol = { version = "^0.14.0", extras = ["filecache"] } cleo = "^2.1.0" -crashtest = "^0.4.1" dulwich = "^0.21.2" fastjsonschema = "^2.18.0" importlib-metadata = { version = ">=4.4", python = "<3.10" } diff --git a/src/poetry/console/application.py b/src/poetry/console/application.py index 3286c73f80b..07b02f771d0 100644 --- a/src/poetry/console/application.py +++ b/src/poetry/console/application.py @@ -30,9 +30,6 @@ from cleo.io.inputs.input import Input from cleo.io.io import IO from cleo.io.outputs.output import Output - from crashtest.solution_providers.solution_provider_repository import ( - SolutionProviderRepository, - ) from poetry.console.commands.installer_command import InstallerCommand from poetry.poetry import Poetry @@ -174,13 +171,6 @@ def create_io( return io - def render_error(self, error: Exception, io: IO) -> None: - # We set the solution provider repository here to load providers - # only when an error occurs - self.set_solution_provider_repository(self._get_solution_provider_repository()) - - super().render_error(error, io) - def _run(self, io: IO) -> int: self._disable_plugins = io.input.parameter_option("--no-plugins") self._disable_cache = io.input.has_parameter_option("--no-cache") @@ -392,20 +382,6 @@ def _default_definition(self) -> Definition: return definition - def _get_solution_provider_repository(self) -> SolutionProviderRepository: - from crashtest.solution_providers.solution_provider_repository import ( - SolutionProviderRepository, - ) - - from poetry.mixology.solutions.providers.python_requirement_solution_provider import ( - PythonRequirementSolutionProvider, - ) - - repository = SolutionProviderRepository() - repository.register_solution_providers([PythonRequirementSolutionProvider]) - - return repository - def main() -> int: exit_code: int = Application().run() diff --git a/src/poetry/mixology/failure.py b/src/poetry/mixology/failure.py index 8c7cebbc518..84a7fb0642d 100644 --- a/src/poetry/mixology/failure.py +++ b/src/poetry/mixology/failure.py @@ -35,10 +35,22 @@ def __init__(self, root: Incompatibility) -> None: def write(self) -> str: buffer = [] - + version_solutions = [] required_python_version_notification = False for incompatibility in self._root.external_incompatibilities: if isinstance(incompatibility.cause, PythonCause): + root_constraint = parse_constraint( + incompatibility.cause.root_python_version + ) + constraint = parse_constraint(incompatibility.cause.python_version) + + version_solutions.append( + "For " + f"{incompatibility.terms[0].dependency.name}," + " a possible solution would be to set the" + " `python` property to" + f' "{root_constraint.intersect(constraint)}"' + ) if not required_python_version_notification: buffer.append( "The current project's supported Python range" @@ -91,7 +103,31 @@ def write(self) -> str: message = " " * padding + message buffer.append(message) + if required_python_version_notification: + # Add suggested solution + links = ",".join( + f"\n https://python-poetry.org/docs/dependency-specification/#{section}" + for section in [ + "python-restricted-dependencies", + "using-environment-markers", + ] + ) + + description = ( + "The Python requirement can be specified via the" + " `python` or" + " `markers` properties" + ) + if version_solutions: + description += "\n\n " + "\n".join(version_solutions) + + description = description.strip(" ") + buffer.append( + f"\n * " + f"Check your dependencies Python requirement:" + f" {description}\n{links}\n", + ) return "\n".join(buffer) def _write( diff --git a/src/poetry/mixology/solutions/__init__.py b/src/poetry/mixology/solutions/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/poetry/mixology/solutions/providers/__init__.py b/src/poetry/mixology/solutions/providers/__init__.py deleted file mode 100644 index cfbd1873848..00000000000 --- a/src/poetry/mixology/solutions/providers/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -from poetry.mixology.solutions.providers.python_requirement_solution_provider import ( - PythonRequirementSolutionProvider, -) - - -__all__ = ["PythonRequirementSolutionProvider"] diff --git a/src/poetry/mixology/solutions/providers/python_requirement_solution_provider.py b/src/poetry/mixology/solutions/providers/python_requirement_solution_provider.py deleted file mode 100644 index 040e325b98d..00000000000 --- a/src/poetry/mixology/solutions/providers/python_requirement_solution_provider.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -import re - -from typing import TYPE_CHECKING - -from crashtest.contracts.has_solutions_for_exception import HasSolutionsForException - -from poetry.puzzle.exceptions import SolverProblemError - - -if TYPE_CHECKING: - from crashtest.contracts.solution import Solution - - -class PythonRequirementSolutionProvider(HasSolutionsForException): - def can_solve(self, exception: Exception) -> bool: - if not isinstance(exception, SolverProblemError): - return False - - m = re.match( - "^The current project's supported Python range (.+) is not compatible " - "with some of the required packages Python requirement", - str(exception), - ) - - return bool(m) - - def get_solutions(self, exception: Exception) -> list[Solution]: - from poetry.mixology.solutions.solutions.python_requirement_solution import ( - PythonRequirementSolution, - ) - - assert isinstance(exception, SolverProblemError) - return [PythonRequirementSolution(exception)] diff --git a/src/poetry/mixology/solutions/solutions/__init__.py b/src/poetry/mixology/solutions/solutions/__init__.py deleted file mode 100644 index e78e9a53361..00000000000 --- a/src/poetry/mixology/solutions/solutions/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -from poetry.mixology.solutions.solutions.python_requirement_solution import ( - PythonRequirementSolution, -) - - -__all__ = ["PythonRequirementSolution"] diff --git a/src/poetry/mixology/solutions/solutions/python_requirement_solution.py b/src/poetry/mixology/solutions/solutions/python_requirement_solution.py deleted file mode 100644 index 9856ca30eef..00000000000 --- a/src/poetry/mixology/solutions/solutions/python_requirement_solution.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING - -from crashtest.contracts.solution import Solution - - -if TYPE_CHECKING: - from poetry.mixology.failure import SolveFailure - from poetry.puzzle.exceptions import SolverProblemError - - -class PythonRequirementSolution(Solution): - def __init__(self, exception: SolverProblemError) -> None: - from poetry.core.constraints.version import parse_constraint - - from poetry.mixology.incompatibility_cause import PythonCause - - self._title = "Check your dependencies Python requirement." - - failure: SolveFailure = exception.error - version_solutions = [] - for incompatibility in failure._incompatibility.external_incompatibilities: - if isinstance(incompatibility.cause, PythonCause): - root_constraint = parse_constraint( - incompatibility.cause.root_python_version - ) - constraint = parse_constraint(incompatibility.cause.python_version) - - version_solutions.append( - "For " - f"{incompatibility.terms[0].dependency.name}," - " a possible solution would be to set the" - " `python` property to" - f' "{root_constraint.intersect(constraint)}"' - ) - - description = ( - "The Python requirement can be specified via the" - " `python` or" - " `markers` properties" - ) - if version_solutions: - description += "\n\n" + "\n".join(version_solutions) - - description += "\n" - - self._description = description - - @property - def solution_title(self) -> str: - return self._title - - @property - def solution_description(self) -> str: - return self._description - - @property - def documentation_links(self) -> list[str]: - return [ - "https://python-poetry.org/docs/dependency-specification/#python-restricted-dependencies", - "https://python-poetry.org/docs/dependency-specification/#using-environment-markers", - ] diff --git a/tests/mixology/solutions/__init__.py b/tests/mixology/solutions/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/mixology/solutions/providers/__init__.py b/tests/mixology/solutions/providers/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/mixology/solutions/providers/test_python_requirement_solution_provider.py b/tests/mixology/solutions/providers/test_python_requirement_solution_provider.py deleted file mode 100644 index d7d2aefd71b..00000000000 --- a/tests/mixology/solutions/providers/test_python_requirement_solution_provider.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -from poetry.core.packages.dependency import Dependency - -from poetry.mixology.failure import SolveFailure -from poetry.mixology.incompatibility import Incompatibility -from poetry.mixology.incompatibility_cause import NoVersionsCause -from poetry.mixology.incompatibility_cause import PythonCause -from poetry.mixology.term import Term -from poetry.puzzle.exceptions import SolverProblemError - - -def test_it_can_solve_python_incompatibility_solver_errors() -> None: - from poetry.mixology.solutions.providers import PythonRequirementSolutionProvider - from poetry.mixology.solutions.solutions import PythonRequirementSolution - - incompatibility = Incompatibility( - [Term(Dependency("foo", "^1.0"), True)], PythonCause("^3.5", ">=3.6") - ) - exception = SolverProblemError(SolveFailure(incompatibility)) - provider = PythonRequirementSolutionProvider() - - assert provider.can_solve(exception) - assert isinstance(provider.get_solutions(exception)[0], PythonRequirementSolution) - - -def test_it_cannot_solve_other_solver_errors() -> None: - from poetry.mixology.solutions.providers import PythonRequirementSolutionProvider - - incompatibility = Incompatibility( - [Term(Dependency("foo", "^1.0"), True)], NoVersionsCause() - ) - exception = SolverProblemError(SolveFailure(incompatibility)) - provider = PythonRequirementSolutionProvider() - - assert not provider.can_solve(exception) diff --git a/tests/mixology/solutions/solutions/__init__.py b/tests/mixology/solutions/solutions/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/mixology/solutions/solutions/test_python_requirement_solution.py b/tests/mixology/solutions/solutions/test_python_requirement_solution.py deleted file mode 100644 index 6eafb44938d..00000000000 --- a/tests/mixology/solutions/solutions/test_python_requirement_solution.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from cleo.io.buffered_io import BufferedIO -from poetry.core.packages.dependency import Dependency - -from poetry.mixology.failure import SolveFailure -from poetry.mixology.incompatibility import Incompatibility -from poetry.mixology.incompatibility_cause import PythonCause -from poetry.mixology.term import Term -from poetry.puzzle.exceptions import SolverProblemError - - -def test_it_provides_the_correct_solution() -> None: - from poetry.mixology.solutions.solutions import PythonRequirementSolution - - incompatibility = Incompatibility( - [Term(Dependency("foo", "^1.0"), True)], PythonCause("^3.5", ">=3.6") - ) - exception = SolverProblemError(SolveFailure(incompatibility)) - solution = PythonRequirementSolution(exception) - - title = "Check your dependencies Python requirement." - description = """\ -The Python requirement can be specified via the `python` or `markers` properties - -For foo, a possible solution would be to set the `python` property to ">=3.6,<4.0"\ -""" - links = [ - "https://python-poetry.org/docs/dependency-specification/#python-restricted-dependencies", - "https://python-poetry.org/docs/dependency-specification/#using-environment-markers", - ] - - assert title == solution.solution_title - assert ( - description == BufferedIO().remove_format(solution.solution_description).strip() - ) - assert links == solution.documentation_links diff --git a/tests/mixology/version_solver/test_python_constraint.py b/tests/mixology/version_solver/test_python_constraint.py index ed7d0f16568..8e687c3f969 100644 --- a/tests/mixology/version_solver/test_python_constraint.py +++ b/tests/mixology/version_solver/test_python_constraint.py @@ -29,6 +29,14 @@ def test_dependency_does_not_match_root_python_constraint( Because no versions of foo match !=1.0.0 and foo (1.0.0) requires Python <3.5, foo is forbidden. -So, because myapp depends on foo (*), version solving failed.""" +So, because myapp depends on foo (*), version solving failed. + + * Check your dependencies Python requirement: The Python requirement can be specified via the `python` or `markers` properties + + For foo, a possible solution would be to set the `python` property to "" + + https://python-poetry.org/docs/dependency-specification/#python-restricted-dependencies, + https://python-poetry.org/docs/dependency-specification/#using-environment-markers +""" check_solver_result(root, provider, error=error)