diff --git a/poetry.lock b/poetry.lock index d2ab97bd590..f263a1a3caf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -24,7 +24,7 @@ description = "Atomic file writes." name = "atomicwrites" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.3.0" +version = "1.4.0" [[package]] category = "dev" @@ -139,10 +139,10 @@ description = "Cleo allows you to create beautiful and testable command-line int name = "cleo" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.8.0" +version = "0.8.1" [package.dependencies] -clikit = ">=0.5.0,<0.6.0" +clikit = ">=0.6.0,<0.7.0" [[package]] category = "dev" @@ -151,7 +151,7 @@ marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_v name = "click" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.1" +version = "7.1.2" [[package]] category = "main" @@ -159,12 +159,16 @@ description = "CliKit is a group of utilities to build beautiful and testable co name = "clikit" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.5.1" +version = "0.6.0" [package.dependencies] pastel = ">=0.2.0,<0.3.0" pylev = ">=1.3,<2.0" +[package.dependencies.crashtest] +python = ">=3.6,<4.0" +version = ">=0.3.0,<0.4.0" + [package.dependencies.enum34] python = ">=2.7,<2.8" version = ">=1.1,<2.0" @@ -177,14 +181,10 @@ version = ">=3.6,<4.0" python = ">=3.5.0,<3.5.4" version = ">=3.6,<4.0" -[package.dependencies.woops] -python = ">=3.6,<4.0" -version = ">=0.2.1,<0.3.0" - [[package]] category = "dev" description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\" and python_version != \"3.4\" or platform_system == \"Windows\"" +marker = "platform_system == \"Windows\" or sys_platform == \"win32\" and python_version != \"3.4\"" name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -193,7 +193,7 @@ version = "0.4.3" [[package]] category = "main" description = "Updated configparser from Python 3.7 for Python 2.6+." -marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version == \"2.7\" and python_version == \"2.7\" or python_version < \"3\"" +marker = "python_version < \"3\" or python_version >= \"2.7\" and python_version < \"2.8\"" name = "configparser" optional = false python-versions = ">=2.6" @@ -206,7 +206,7 @@ testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2)", "pytes [[package]] category = "main" description = "Backports and enhancements for the contextlib module" -marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.4\"" +marker = "python_version < \"3.4\" or python_version >= \"2.7\" and python_version < \"2.8\"" name = "contextlib2" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -223,6 +223,15 @@ version = "5.1" [package.extras] toml = ["toml"] +[[package]] +category = "main" +description = "Manage Python errors with ease" +marker = "python_version >= \"3.6\" and python_version < \"4.0\"" +name = "crashtest" +optional = false +python-versions = ">=3.6,<4.0" +version = "0.3.0" + [[package]] category = "main" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." @@ -379,7 +388,7 @@ version = "2.9" [[package]] category = "main" description = "Read metadata from Python packages" -marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.8\" or python_version >= \"3.5\" and python_version < \"3.6\" or python_version >= \"3.6\" and python_version < \"3.8\"" +marker = "python_version < \"3.8\" or python_version >= \"3.5\" and python_version < \"3.6\" or python_version >= \"2.7\" and python_version < \"2.8\"" name = "importlib-metadata" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" @@ -407,11 +416,11 @@ testing = ["packaging", "importlib-resources"] [[package]] category = "main" description = "Read resources from Python packages" -marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.7\"" +marker = "python_version < \"3.7\" or python_version >= \"2.7\" and python_version < \"2.8\"" name = "importlib-resources" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.4.0" +version = "1.5.0" [package.dependencies] [package.dependencies.contextlib2] @@ -681,7 +690,7 @@ version = "0.2.0" [[package]] category = "main" description = "Object-oriented filesystem paths" -marker = "python_version >= \"2.7\" and python_version < \"2.8\" and sys_platform != \"win32\" or python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.6\"" +marker = "python_version < \"3.6\" or python_version >= \"2.7\" and python_version < \"2.8\" and sys_platform != \"win32\" or python_version >= \"2.7\" and python_version < \"2.8\"" name = "pathlib2" optional = false python-versions = "*" @@ -959,7 +968,7 @@ description = "pytest-sugar is a plugin for pytest that changes the default look name = "pytest-sugar" optional = false python-versions = "*" -version = "0.9.2" +version = "0.9.3" [package.dependencies] packaging = ">=14.1" @@ -1257,19 +1266,10 @@ optional = false python-versions = "*" version = "0.5.1" -[[package]] -category = "main" -description = "Handle and manage Python errors with ease" -marker = "python_version >= \"3.6\" and python_version < \"4.0\"" -name = "woops" -optional = false -python-versions = ">=3.6,<4.0" -version = "0.2.1" - [[package]] category = "main" description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.8\" or python_version >= \"3.5\" and python_version < \"3.6\" or python_version >= \"3.6\" and python_version < \"3.8\"" +marker = "python_version < \"3.8\" or python_version >= \"3.5\" and python_version < \"3.6\" or python_version >= \"2.7\" and python_version < \"2.8\"" name = "zipp" optional = false python-versions = ">=2.7" @@ -1285,7 +1285,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] [metadata] -content-hash = "a451c3dc97c7797be12c637b9827087489eddd2e181946ef95c5f2a4bd710fb8" +content-hash = "cecb406f48f759fe212909612a098b7e91e0ea55bdd6acb986744a4ca9d5df51" python-versions = "~2.7 || ^3.5" [metadata.files] @@ -1298,8 +1298,8 @@ appdirs = [ {file = "aspy.yaml-1.3.0.tar.gz", hash = "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"}, ] atomicwrites = [ - {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, - {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, @@ -1360,16 +1360,16 @@ chardet = [ {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, ] cleo = [ - {file = "cleo-0.8.0-py2.py3-none-any.whl", hash = "sha256:dcb791b246c02d59ea8eaf05a05d986e547dbe8ba2496ed59048e5d4ab93b537"}, - {file = "cleo-0.8.0.tar.gz", hash = "sha256:b2d56e93b182358591d0ec46c4f787736378a6a8adf4a6512f72aeb0506de981"}, + {file = "cleo-0.8.1-py2.py3-none-any.whl", hash = "sha256:141cda6dc94a92343be626bb87a0b6c86ae291dfc732a57bf04310d4b4201753"}, + {file = "cleo-0.8.1.tar.gz", hash = "sha256:3d0e22d30117851b45970b6c14aca4ab0b18b1b53c8af57bed13208147e4069f"}, ] click = [ - {file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"}, - {file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"}, + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, ] clikit = [ - {file = "clikit-0.5.1-py2.py3-none-any.whl", hash = "sha256:f697b6e3125cf4bdc6394184c687f52fe3323152ca6bb99db444201f32904a64"}, - {file = "clikit-0.5.1.tar.gz", hash = "sha256:2e9cd4c87539d9a0f0275b26e3e50d2d531d7bfbcf4f91909aaa013cfac9d0e1"}, + {file = "clikit-0.6.0-py2.py3-none-any.whl", hash = "sha256:a666d0727b1ddbfdef9ed2bfbda00ad6f1dd1fa19255ff79b8ca05df77809b7b"}, + {file = "clikit-0.6.0.tar.gz", hash = "sha256:16b8aa2703413eae1138b50bdf825cb1465e0ee456efab4e8effe3bf03b7c2a4"}, ] colorama = [ {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, @@ -1416,6 +1416,10 @@ coverage = [ {file = "coverage-5.1-cp39-cp39-win_amd64.whl", hash = "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e"}, {file = "coverage-5.1.tar.gz", hash = "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"}, ] +crashtest = [ + {file = "crashtest-0.3.0-py3-none-any.whl", hash = "sha256:06069a9267c54be31c42b03574b72407bf780e13c82cb0238f24ea69cf25b6dd"}, + {file = "crashtest-0.3.0.tar.gz", hash = "sha256:e9c06cc96400939ab5327123a3f699078eaad8a6283247d7b2ae0f6afffadf14"}, +] cryptography = [ {file = "cryptography-2.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e"}, {file = "cryptography-2.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b"}, @@ -1489,8 +1493,8 @@ importlib-metadata = [ {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"}, ] importlib-resources = [ - {file = "importlib_resources-1.4.0-py2.py3-none-any.whl", hash = "sha256:dd98ceeef3f5ad2ef4cc287b8586da4ebad15877f351e9688987ad663a0a29b8"}, - {file = "importlib_resources-1.4.0.tar.gz", hash = "sha256:4019b6a9082d8ada9def02bece4a76b131518866790d58fdda0b5f8c603b36c2"}, + {file = "importlib_resources-1.5.0-py2.py3-none-any.whl", hash = "sha256:85dc0b9b325ff78c8bef2e4ff42616094e16b98ebd5e3b50fe7e2f0bbcdcde49"}, + {file = "importlib_resources-1.5.0.tar.gz", hash = "sha256:6f87df66833e1942667108628ec48900e02a4ab4ad850e25fbf07cb17cf734ca"}, ] ipaddress = [ {file = "ipaddress-1.0.23-py2.py3-none-any.whl", hash = "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc"}, @@ -1683,8 +1687,7 @@ pytest-mock = [ {file = "pytest_mock-1.13.0-py2.py3-none-any.whl", hash = "sha256:67e414b3caef7bff6fc6bd83b22b5bc39147e4493f483c2679bc9d4dc485a94d"}, ] pytest-sugar = [ - {file = "pytest-sugar-0.9.2.tar.gz", hash = "sha256:fcd87a74b2bce5386d244b49ad60549bfbc4602527797fac167da147983f58ab"}, - {file = "pytest_sugar-0.9.2-py2.py3-none-any.whl", hash = "sha256:26cf8289fe10880cbbc130bd77398c4e6a8b936d8393b116a5c16121d95ab283"}, + {file = "pytest-sugar-0.9.3.tar.gz", hash = "sha256:1630b5b7ea3624919b73fde37cffb87965c5087a4afab8a43074ff44e0d810c4"}, ] pywin32-ctypes = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, @@ -1842,10 +1845,6 @@ webencodings = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] -woops = [ - {file = "woops-0.2.1-py3-none-any.whl", hash = "sha256:77f7179941d0a3d354d923f71428ee2c2519e0b86f465ed5882b98a6d00ec7f0"}, - {file = "woops-0.2.1.tar.gz", hash = "sha256:f95dee22e055b61980209fdf481f33e743e02d5ef135853c9089d0061370f94a"}, -] zipp = [ {file = "zipp-1.2.0-py2.py3-none-any.whl", hash = "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"}, {file = "zipp-1.2.0.tar.gz", hash = "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1"}, diff --git a/poetry/console/config/application_config.py b/poetry/console/config/application_config.py index e8f752752fd..328d88d0ba5 100644 --- a/poetry/console/config/application_config.py +++ b/poetry/console/config/application_config.py @@ -29,6 +29,7 @@ from poetry.console.commands.env_command import EnvCommand from poetry.console.logging.io_formatter import IOFormatter from poetry.console.logging.io_handler import IOHandler +from poetry.utils._compat import PY36 class ApplicationConfig(BaseApplicationConfig): @@ -46,6 +47,15 @@ def configure(self): self.add_event_listener(PRE_HANDLE, self.register_command_loggers) self.add_event_listener(PRE_HANDLE, self.set_env) + if PY36: + from poetry.mixology.solutions.providers import ( + PythonRequirementSolutionProvider, + ) + + self._solution_provider_repository.register_solution_providers( + [PythonRequirementSolutionProvider] + ) + def register_command_loggers( self, event, event_name, _ ): # type: (PreHandleEvent, str, Any) -> None diff --git a/poetry/mixology/failure.py b/poetry/mixology/failure.py index e6854335af3..afa332085b3 100644 --- a/poetry/mixology/failure.py +++ b/poetry/mixology/failure.py @@ -2,6 +2,8 @@ from typing import List from typing import Tuple +from poetry.core.semver import parse_constraint + from .incompatibility import Incompatibility from .incompatibility_cause import ConflictCause from .incompatibility_cause import PythonCause @@ -44,10 +46,15 @@ def write(self): ) required_python_version_notification = True + root_constraint = parse_constraint( + incompatibility.cause.root_python_version + ) + constraint = parse_constraint(incompatibility.cause.python_version) buffer.append( - " - {} requires Python {}".format( + " - {} requires Python {}, so it will not be satisfied for Python {}".format( incompatibility.terms[0].dependency.name, incompatibility.cause.python_version, + root_constraint.difference(constraint), ) ) diff --git a/poetry/mixology/solutions/__init__.py b/poetry/mixology/solutions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/poetry/mixology/solutions/providers/__init__.py b/poetry/mixology/solutions/providers/__init__.py new file mode 100644 index 00000000000..3faec7b61e1 --- /dev/null +++ b/poetry/mixology/solutions/providers/__init__.py @@ -0,0 +1 @@ +from .python_requirement_solution_provider import PythonRequirementSolutionProvider diff --git a/poetry/mixology/solutions/providers/python_requirement_solution_provider.py b/poetry/mixology/solutions/providers/python_requirement_solution_provider.py new file mode 100644 index 00000000000..4c903677fd0 --- /dev/null +++ b/poetry/mixology/solutions/providers/python_requirement_solution_provider.py @@ -0,0 +1,30 @@ +import re + +from typing import List + +from crashtest.contracts.has_solutions_for_exception import HasSolutionsForException +from crashtest.contracts.solution import Solution + + +class PythonRequirementSolutionProvider(HasSolutionsForException): + def can_solve(self, exception): # type: (Exception) -> bool + from poetry.puzzle.exceptions import SolverProblemError + + if not isinstance(exception, SolverProblemError): + return False + + m = re.match( + "^The current project's Python requirement (.+) is not compatible " + "with some of the required packages Python requirement", + str(exception), + ) + + if not m: + return False + + return True + + def get_solutions(self, exception): # type: (Exception) -> List[Solution] + from ..solutions.python_requirement_solution import PythonRequirementSolution + + return [PythonRequirementSolution(exception)] diff --git a/poetry/mixology/solutions/solutions/__init__.py b/poetry/mixology/solutions/solutions/__init__.py new file mode 100644 index 00000000000..838e77b0194 --- /dev/null +++ b/poetry/mixology/solutions/solutions/__init__.py @@ -0,0 +1 @@ +from .python_requirement_solution import PythonRequirementSolution diff --git a/poetry/mixology/solutions/solutions/python_requirement_solution.py b/poetry/mixology/solutions/solutions/python_requirement_solution.py new file mode 100644 index 00000000000..35e051b3055 --- /dev/null +++ b/poetry/mixology/solutions/solutions/python_requirement_solution.py @@ -0,0 +1,52 @@ +from crashtest.contracts.solution import Solution + + +class PythonRequirementSolution(Solution): + def __init__(self, exception): + from poetry.mixology.incompatibility_cause import PythonCause + from poetry.core.semver import parse_constraint + + self._title = "Check your dependencies Python requirement." + + failure = 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 {}, a possible solution would be " + 'to set the `python` property to "{}"'.format( + incompatibility.terms[0].dependency.name, + 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): + return self._description + + @property + def documentation_links(self): + return [ + "https://python-poetry.org/docs/dependency-specification/#python-restricted-dependencies", + "https://python-poetry.org/docs/dependency-specification/#using-environment-markers", + ] diff --git a/poetry/mixology/version_solver.py b/poetry/mixology/version_solver.py index 5331478edc7..cc5f5a02574 100644 --- a/poetry/mixology/version_solver.py +++ b/poetry/mixology/version_solver.py @@ -11,7 +11,6 @@ from poetry.core.packages import ProjectPackage from poetry.core.semver import Version from poetry.core.semver import VersionRange -from poetry.puzzle.provider import Provider from .failure import SolveFailure from .incompatibility import Incompatibility @@ -40,7 +39,7 @@ class VersionSolver: def __init__( self, root, # type: ProjectPackage - provider, # type: Provider + provider, locked=None, # type: Dict[str, Package] use_latest=None, # type: List[str] ): diff --git a/pyproject.toml b/pyproject.toml index 71cf98801fe..bbf20b7bb56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,8 +25,9 @@ classifiers = [ python = "~2.7 || ^3.5" poetry-core = "^1.0.0a6" -cleo = "^0.8.0" -clikit = "^0.5.1" +cleo = "^0.8.1" +clikit = "^0.6.0" +crashtest = { version = "^0.3.0", python = "^3.6" } requests = "^2.18" cachy = "^0.3.0" requests-toolbelt = "^0.8.0" diff --git a/tests/mixology/solutions/__init__.py b/tests/mixology/solutions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/mixology/solutions/providers/__init__.py b/tests/mixology/solutions/providers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/mixology/solutions/providers/test_python_requirement_solution_provider.py b/tests/mixology/solutions/providers/test_python_requirement_solution_provider.py new file mode 100644 index 00000000000..81c11d215d3 --- /dev/null +++ b/tests/mixology/solutions/providers/test_python_requirement_solution_provider.py @@ -0,0 +1,42 @@ +import pytest + +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 +from poetry.utils._compat import PY36 + + +@pytest.mark.skipif( + not PY36, reason="Error solutions are only available for Python ^3.6" +) +def test_it_can_solve_python_incompatibility_solver_errors(): + 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) + + +@pytest.mark.skipif( + not PY36, reason="Error solutions are only available for Python ^3.6" +) +def test_it_cannot_solve_other_solver_errors(): + 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 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/mixology/solutions/solutions/test_python_requirement_solution.py b/tests/mixology/solutions/solutions/test_python_requirement_solution.py new file mode 100644 index 00000000000..e264ad8d04b --- /dev/null +++ b/tests/mixology/solutions/solutions/test_python_requirement_solution.py @@ -0,0 +1,41 @@ +import pytest + +from clikit.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 +from poetry.utils._compat import PY36 + + +@pytest.mark.skipif( + not PY36, reason="Error solutions are only available for Python ^3.6" +) +def test_it_provides_the_correct_solution(): + 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 910d51be784..a05dcd9c444 100644 --- a/tests/mixology/version_solver/test_python_constraint.py +++ b/tests/mixology/version_solver/test_python_constraint.py @@ -10,7 +10,7 @@ def test_dependency_does_not_match_root_python_constraint(root, provider, repo): error = """The current project's Python requirement (^3.6) \ is not compatible with some of the required packages Python requirement: - - foo requires Python <3.5 + - foo requires Python <3.5, so it will not be satisfied for Python >=3.6,<4.0 Because no versions of foo match !=1.0.0 and foo (1.0.0) requires Python <3.5, foo is forbidden.