Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
python-poetry
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
open
python-poetry
Commits
1a82099d
Unverified
Commit
1a82099d
authored
Jun 25, 2018
by
Sébastien Eustace
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Improve solver error messages
parent
b4ab4d56
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
175 additions
and
10 deletions
+175
-10
CHANGELOG.md
+1
-0
poetry/mixology/failure.py
+17
-2
poetry/mixology/incompatibility.py
+16
-0
poetry/mixology/incompatibility_cause.py
+6
-1
poetry/puzzle/provider.py
+1
-1
poetry/semver/version_union.py
+11
-0
tests/mixology/helpers.py
+3
-1
tests/mixology/version_solver/test_backtracking.py
+98
-0
tests/mixology/version_solver/test_python_constraint.py
+17
-0
tests/mixology/version_solver/test_unsolvable.py
+1
-1
tests/puzzle/test_solver.py
+1
-1
tests/semver/test_main.py
+3
-3
No files found.
CHANGELOG.md
View file @
1a82099d
...
...
@@ -10,6 +10,7 @@
### Changed
-
Changed the dependency installation order, deepest dependencies are now installed first.
-
Improved solver error messages.
### Fixed
...
...
poetry/mixology/failure.py
View file @
1a82099d
...
...
@@ -4,6 +4,7 @@ from typing import Tuple
from
.incompatibility
import
Incompatibility
from
.incompatibility_cause
import
ConflictCause
from
.incompatibility_cause
import
PythonCause
class
SolveFailure
(
Exception
):
...
...
@@ -30,6 +31,20 @@ class _Writer:
def
write
(
self
):
buffer
=
[]
required_python_version
=
None
for
incompatibility
in
self
.
_root
.
external_incompatibilities
:
if
isinstance
(
incompatibility
.
cause
,
PythonCause
):
required_python_version
=
incompatibility
.
cause
.
root_python_version
break
if
required_python_version
is
not
None
:
buffer
.
append
(
"The current supported Python versions are {}"
.
format
(
required_python_version
)
)
buffer
.
append
(
""
)
if
isinstance
(
self
.
_root
.
cause
,
ConflictCause
):
self
.
_visit
(
self
.
_root
,
{})
else
:
...
...
@@ -40,7 +55,7 @@ class _Writer:
padding
=
(
0
if
not
self
.
_line_numbers
else
len
(
"({})"
.
format
(
list
(
self
.
_line_numbers
.
values
())[
-
1
]))
else
len
(
"({})
"
.
format
(
list
(
self
.
_line_numbers
.
values
())[
-
1
]))
)
last_was_empty
=
False
...
...
@@ -79,7 +94,7 @@ class _Writer:
self
,
incompatibility
,
details_for_incompatibility
,
conclusion
=
False
):
# type: (Incompatibility, Dict, bool) -> None
numbered
=
conclusion
or
self
.
_derivations
[
incompatibility
]
>
1
conjunction
=
conclusion
or
(
"So,"
if
incompatibility
==
self
.
_root
else
"And"
)
conjunction
=
"So,"
if
conclusion
or
incompatibility
==
self
.
_root
else
"And"
incompatibility_string
=
str
(
incompatibility
)
cause
=
incompatibility
.
cause
# type: ConflictCause
...
...
poetry/mixology/incompatibility.py
View file @
1a82099d
...
...
@@ -84,6 +84,22 @@ class Incompatibility:
def
cause
(
self
):
# type: () -> IncompatibilityCause
return
self
.
_cause
@property
def
external_incompatibilities
(
self
):
# type: () -> Generator[Incompatibility]
"""
Returns all external incompatibilities in this incompatibility's
derivation graph.
"""
if
isinstance
(
self
.
_cause
,
ConflictCause
):
cause
=
self
.
_cause
# type: ConflictCause
for
incompatibility
in
cause
.
conflict
.
external_incompatibilities
:
yield
incompatibility
for
incompatibility
in
cause
.
other
.
external_incompatibilities
:
yield
incompatibility
else
:
yield
self
def
is_failure
(
self
):
# type: () -> bool
return
len
(
self
.
_terms
)
==
0
or
(
len
(
self
.
_terms
)
==
1
and
self
.
_terms
[
0
]
.
dependency
.
is_root
...
...
poetry/mixology/incompatibility_cause.py
View file @
1a82099d
...
...
@@ -48,13 +48,18 @@ class PythonCause(IncompatibilityCause):
with the current python version.
"""
def
__init__
(
self
,
python_version
):
def
__init__
(
self
,
python_version
,
root_python_version
):
self
.
_python_version
=
python_version
self
.
_root_python_version
=
root_python_version
@property
def
python_version
(
self
):
return
self
.
_python_version
@property
def
root_python_version
(
self
):
return
self
.
_root_python_version
class
PlatformCause
(
IncompatibilityCause
):
"""
...
...
poetry/puzzle/provider.py
View file @
1a82099d
...
...
@@ -279,7 +279,7 @@ class Provider:
return
[
Incompatibility
(
[
Term
(
package
.
to_dependency
(),
True
)],
PythonCause
(
package
.
python_versions
),
PythonCause
(
package
.
python_versions
,
self
.
_package
.
python_versions
),
)
]
...
...
poetry/semver/version_union.py
View file @
1a82099d
...
...
@@ -225,6 +225,12 @@ class VersionUnion(VersionConstraint):
raise
ValueError
(
"Unknown VersionConstraint type {}"
.
format
(
constraint
))
def
_excludes_single_version
(
self
):
# type: () -> bool
from
.version
import
Version
from
.version_range
import
VersionRange
return
isinstance
(
VersionRange
()
.
difference
(
self
),
Version
)
def
__eq__
(
self
,
other
):
if
not
isinstance
(
other
,
VersionUnion
):
return
False
...
...
@@ -232,6 +238,11 @@ class VersionUnion(VersionConstraint):
return
self
.
_ranges
==
other
.
ranges
def
__str__
(
self
):
from
.version_range
import
VersionRange
if
self
.
_excludes_single_version
():
return
"!={}"
.
format
(
VersionRange
()
.
difference
(
self
))
return
" || "
.
join
([
str
(
r
)
for
r
in
self
.
_ranges
])
def
__repr__
(
self
):
...
...
tests/mixology/helpers.py
View file @
1a82099d
...
...
@@ -3,8 +3,10 @@ from poetry.mixology.failure import SolveFailure
from
poetry.mixology.version_solver
import
VersionSolver
def
add_to_repo
(
repository
,
name
,
version
,
deps
=
None
):
def
add_to_repo
(
repository
,
name
,
version
,
deps
=
None
,
python
=
None
):
package
=
Package
(
name
,
version
)
if
python
:
package
.
python_versions
=
python
if
deps
:
for
dep_name
,
dep_constraint
in
deps
.
items
():
...
...
tests/mixology/version_solver/test_backtracking.py
View file @
1a82099d
...
...
@@ -50,3 +50,101 @@ def test_backjumps_after_partial_satisfier(root, provider, repo):
add_to_repo
(
repo
,
"y"
,
"2.0.0"
)
check_solver_result
(
root
,
provider
,
{
"c"
:
"1.0.0"
,
"y"
:
"2.0.0"
},
tries
=
2
)
def
test_rolls_back_leaf_versions_first
(
root
,
provider
,
repo
):
# The latest versions of a and b disagree on c. An older version of either
# will resolve the problem. This test validates that b, which is farther
# in the dependency graph from myapp is downgraded first.
root
.
add_dependency
(
"a"
,
"*"
)
add_to_repo
(
repo
,
"a"
,
"1.0.0"
,
deps
=
{
"b"
:
"*"
})
add_to_repo
(
repo
,
"a"
,
"2.0.0"
,
deps
=
{
"b"
:
"*"
,
"c"
:
"2.0.0"
})
add_to_repo
(
repo
,
"b"
,
"1.0.0"
)
add_to_repo
(
repo
,
"b"
,
"2.0.0"
,
deps
=
{
"c"
:
"1.0.0"
})
add_to_repo
(
repo
,
"c"
,
"1.0.0"
)
add_to_repo
(
repo
,
"c"
,
"2.0.0"
)
check_solver_result
(
root
,
provider
,
{
"a"
:
"2.0.0"
,
"b"
:
"1.0.0"
,
"c"
:
"2.0.0"
})
def
test_simple_transitive
(
root
,
provider
,
repo
):
# Only one version of baz, so foo and bar will have to downgrade
# until they reach it
root
.
add_dependency
(
"foo"
,
"*"
)
add_to_repo
(
repo
,
"foo"
,
"1.0.0"
,
deps
=
{
"bar"
:
"1.0.0"
})
add_to_repo
(
repo
,
"foo"
,
"2.0.0"
,
deps
=
{
"bar"
:
"2.0.0"
})
add_to_repo
(
repo
,
"foo"
,
"3.0.0"
,
deps
=
{
"bar"
:
"3.0.0"
})
add_to_repo
(
repo
,
"bar"
,
"1.0.0"
,
deps
=
{
"baz"
:
"*"
})
add_to_repo
(
repo
,
"bar"
,
"2.0.0"
,
deps
=
{
"baz"
:
"2.0.0"
})
add_to_repo
(
repo
,
"bar"
,
"3.0.0"
,
deps
=
{
"baz"
:
"3.0.0"
})
add_to_repo
(
repo
,
"baz"
,
"1.0.0"
)
check_solver_result
(
root
,
provider
,
{
"foo"
:
"1.0.0"
,
"bar"
:
"1.0.0"
,
"baz"
:
"1.0.0"
},
tries
=
3
)
def
test_backjump_to_nearer_unsatisfied_package
(
root
,
provider
,
repo
):
# This ensures it doesn't exhaustively search all versions of b when it's
# a-2.0.0 whose dependency on c-2.0.0-nonexistent led to the problem. We
# make sure b has more versions than a so that the solver tries a first
# since it sorts sibling dependencies by number of versions.
root
.
add_dependency
(
"a"
,
"*"
)
root
.
add_dependency
(
"b"
,
"*"
)
add_to_repo
(
repo
,
"a"
,
"1.0.0"
,
deps
=
{
"c"
:
"1.0.0"
})
add_to_repo
(
repo
,
"a"
,
"2.0.0"
,
deps
=
{
"c"
:
"2.0.0-nonexistent"
})
add_to_repo
(
repo
,
"b"
,
"1.0.0"
)
add_to_repo
(
repo
,
"b"
,
"2.0.0"
)
add_to_repo
(
repo
,
"b"
,
"3.0.0"
)
add_to_repo
(
repo
,
"c"
,
"1.0.0"
)
check_solver_result
(
root
,
provider
,
{
"a"
:
"1.0.0"
,
"b"
:
"3.0.0"
,
"c"
:
"1.0.0"
},
tries
=
2
)
def
test_traverse_into_package_with_fewer_versions_first
(
root
,
provider
,
repo
):
# Dependencies are ordered so that packages with fewer versions are tried
# first. Here, there are two valid solutions (either a or b must be
# downgraded once). The chosen one depends on which dep is traversed first.
# Since b has fewer versions, it will be traversed first, which means a will
# come later. Since later selections are revised first, a gets downgraded.
root
.
add_dependency
(
"a"
,
"*"
)
root
.
add_dependency
(
"b"
,
"*"
)
add_to_repo
(
repo
,
"a"
,
"1.0.0"
,
deps
=
{
"c"
:
"*"
})
add_to_repo
(
repo
,
"a"
,
"2.0.0"
,
deps
=
{
"c"
:
"*"
})
add_to_repo
(
repo
,
"a"
,
"3.0.0"
,
deps
=
{
"c"
:
"*"
})
add_to_repo
(
repo
,
"a"
,
"4.0.0"
,
deps
=
{
"c"
:
"*"
})
add_to_repo
(
repo
,
"a"
,
"5.0.0"
,
deps
=
{
"c"
:
"1.0.0"
})
add_to_repo
(
repo
,
"b"
,
"1.0.0"
,
deps
=
{
"c"
:
"*"
})
add_to_repo
(
repo
,
"b"
,
"2.0.0"
,
deps
=
{
"c"
:
"*"
})
add_to_repo
(
repo
,
"b"
,
"3.0.0"
,
deps
=
{
"c"
:
"*"
})
add_to_repo
(
repo
,
"b"
,
"4.0.0"
,
deps
=
{
"c"
:
"2.0.0"
})
add_to_repo
(
repo
,
"c"
,
"1.0.0"
)
add_to_repo
(
repo
,
"c"
,
"2.0.0"
)
check_solver_result
(
root
,
provider
,
{
"a"
:
"4.0.0"
,
"b"
:
"4.0.0"
,
"c"
:
"2.0.0"
})
def
test_backjump_past_failed_package_on_disjoint_constraint
(
root
,
provider
,
repo
):
root
.
add_dependency
(
"a"
,
"*"
)
root
.
add_dependency
(
"foo"
,
">2.0.0"
)
add_to_repo
(
repo
,
"a"
,
"1.0.0"
,
deps
=
{
"foo"
:
"*"
})
# ok
add_to_repo
(
repo
,
"a"
,
"2.0.0"
,
deps
=
{
"foo"
:
"<1.0.0"
}
)
# disjoint with myapp's constraint on foo
add_to_repo
(
repo
,
"foo"
,
"2.0.0"
)
add_to_repo
(
repo
,
"foo"
,
"2.0.1"
)
add_to_repo
(
repo
,
"foo"
,
"2.0.2"
)
add_to_repo
(
repo
,
"foo"
,
"2.0.3"
)
add_to_repo
(
repo
,
"foo"
,
"2.0.4"
)
check_solver_result
(
root
,
provider
,
{
"a"
:
"1.0.0"
,
"foo"
:
"2.0.4"
})
tests/mixology/version_solver/test_python_constraint.py
0 → 100644
View file @
1a82099d
from
..helpers
import
add_to_repo
from
..helpers
import
check_solver_result
def
test_dependency_does_not_match_root_python_constraint
(
root
,
provider
,
repo
):
root
.
python_versions
=
"^3.6"
root
.
add_dependency
(
"foo"
,
"*"
)
add_to_repo
(
repo
,
"foo"
,
"1.0.0"
,
python
=
"<3.5"
)
error
=
"""The current supported Python versions are ^3.6
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."""
check_solver_result
(
root
,
provider
,
error
=
error
)
tests/mixology/version_solver/test_unsolvable.py
View file @
1a82099d
...
...
@@ -79,7 +79,7 @@ def test_no_valid_solution(root, provider, repo):
error
=
"""
\
Because no versions of b match <1.0.0 || >1.0.0,<2.0.0 || >2.0.0
and b (1.0.0) depends on a (2.0.0), b (
<2.0.0 || >
2.0.0) requires a (2.0.0).
and b (1.0.0) depends on a (2.0.0), b (
!=
2.0.0) requires a (2.0.0).
And because a (2.0.0) depends on b (2.0.0), b is forbidden.
Because b (2.0.0) depends on a (1.0.0) which depends on b (1.0.0), b is forbidden.
Thus, b is forbidden.
...
...
tests/puzzle/test_solver.py
View file @
1a82099d
...
...
@@ -841,7 +841,7 @@ def test_solver_duplicate_dependencies_different_constraints_same_requirements(
expected
=
"""
\
Because a (1.0) depends on both B (^1.0) and B (^2.0), a is forbidden.
So, because no versions of a match
<1.0 || >
1.0
So, because no versions of a match
!=
1.0
and root depends on A (*), version solving failed."""
assert
str
(
e
.
value
)
==
expected
...
...
tests/semver/test_main.py
View file @
1a82099d
...
...
@@ -160,9 +160,9 @@ def test_parse_constraints_negative_wildcard(input, constraint):
(
"1"
,
"1"
),
(
"1.2"
,
"1.2"
),
(
"1.2.3"
,
"1.2.3"
),
(
"!=1"
,
"
<1 || >
1"
),
(
"!=1.2"
,
"
<1.2 || >
1.2"
),
(
"!=1.2.3"
,
"
<1.2.3 || >
1.2.3"
),
(
"!=1"
,
"
!=
1"
),
(
"!=1.2"
,
"
!=
1.2"
),
(
"!=1.2.3"
,
"
!=
1.2.3"
),
(
"^1"
,
">=1,<2"
),
(
"^1.0"
,
">=1.0,<2.0"
),
(
"^1.0.0"
,
">=1.0.0,<2.0.0"
),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment