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
Show 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 @@
...
@@ -10,6 +10,7 @@
### Changed
### Changed
-
Changed the dependency installation order, deepest dependencies are now installed first.
-
Changed the dependency installation order, deepest dependencies are now installed first.
-
Improved solver error messages.
### Fixed
### Fixed
...
...
poetry/mixology/failure.py
View file @
1a82099d
...
@@ -4,6 +4,7 @@ from typing import Tuple
...
@@ -4,6 +4,7 @@ from typing import Tuple
from
.incompatibility
import
Incompatibility
from
.incompatibility
import
Incompatibility
from
.incompatibility_cause
import
ConflictCause
from
.incompatibility_cause
import
ConflictCause
from
.incompatibility_cause
import
PythonCause
class
SolveFailure
(
Exception
):
class
SolveFailure
(
Exception
):
...
@@ -30,6 +31,20 @@ class _Writer:
...
@@ -30,6 +31,20 @@ class _Writer:
def
write
(
self
):
def
write
(
self
):
buffer
=
[]
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
):
if
isinstance
(
self
.
_root
.
cause
,
ConflictCause
):
self
.
_visit
(
self
.
_root
,
{})
self
.
_visit
(
self
.
_root
,
{})
else
:
else
:
...
@@ -40,7 +55,7 @@ class _Writer:
...
@@ -40,7 +55,7 @@ class _Writer:
padding
=
(
padding
=
(
0
0
if
not
self
.
_line_numbers
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
last_was_empty
=
False
...
@@ -79,7 +94,7 @@ class _Writer:
...
@@ -79,7 +94,7 @@ class _Writer:
self
,
incompatibility
,
details_for_incompatibility
,
conclusion
=
False
self
,
incompatibility
,
details_for_incompatibility
,
conclusion
=
False
):
# type: (Incompatibility, Dict, bool) -> None
):
# type: (Incompatibility, Dict, bool) -> None
numbered
=
conclusion
or
self
.
_derivations
[
incompatibility
]
>
1
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
)
incompatibility_string
=
str
(
incompatibility
)
cause
=
incompatibility
.
cause
# type: ConflictCause
cause
=
incompatibility
.
cause
# type: ConflictCause
...
...
poetry/mixology/incompatibility.py
View file @
1a82099d
...
@@ -84,6 +84,22 @@ class Incompatibility:
...
@@ -84,6 +84,22 @@ class Incompatibility:
def
cause
(
self
):
# type: () -> IncompatibilityCause
def
cause
(
self
):
# type: () -> IncompatibilityCause
return
self
.
_cause
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
def
is_failure
(
self
):
# type: () -> bool
return
len
(
self
.
_terms
)
==
0
or
(
return
len
(
self
.
_terms
)
==
0
or
(
len
(
self
.
_terms
)
==
1
and
self
.
_terms
[
0
]
.
dependency
.
is_root
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):
...
@@ -48,13 +48,18 @@ class PythonCause(IncompatibilityCause):
with the current python version.
with the current python version.
"""
"""
def
__init__
(
self
,
python_version
):
def
__init__
(
self
,
python_version
,
root_python_version
):
self
.
_python_version
=
python_version
self
.
_python_version
=
python_version
self
.
_root_python_version
=
root_python_version
@property
@property
def
python_version
(
self
):
def
python_version
(
self
):
return
self
.
_python_version
return
self
.
_python_version
@property
def
root_python_version
(
self
):
return
self
.
_root_python_version
class
PlatformCause
(
IncompatibilityCause
):
class
PlatformCause
(
IncompatibilityCause
):
"""
"""
...
...
poetry/puzzle/provider.py
View file @
1a82099d
...
@@ -279,7 +279,7 @@ class Provider:
...
@@ -279,7 +279,7 @@ class Provider:
return
[
return
[
Incompatibility
(
Incompatibility
(
[
Term
(
package
.
to_dependency
(),
True
)],
[
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):
...
@@ -225,6 +225,12 @@ class VersionUnion(VersionConstraint):
raise
ValueError
(
"Unknown VersionConstraint type {}"
.
format
(
constraint
))
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
):
def
__eq__
(
self
,
other
):
if
not
isinstance
(
other
,
VersionUnion
):
if
not
isinstance
(
other
,
VersionUnion
):
return
False
return
False
...
@@ -232,6 +238,11 @@ class VersionUnion(VersionConstraint):
...
@@ -232,6 +238,11 @@ class VersionUnion(VersionConstraint):
return
self
.
_ranges
==
other
.
ranges
return
self
.
_ranges
==
other
.
ranges
def
__str__
(
self
):
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
])
return
" || "
.
join
([
str
(
r
)
for
r
in
self
.
_ranges
])
def
__repr__
(
self
):
def
__repr__
(
self
):
...
...
tests/mixology/helpers.py
View file @
1a82099d
...
@@ -3,8 +3,10 @@ from poetry.mixology.failure import SolveFailure
...
@@ -3,8 +3,10 @@ from poetry.mixology.failure import SolveFailure
from
poetry.mixology.version_solver
import
VersionSolver
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
)
package
=
Package
(
name
,
version
)
if
python
:
package
.
python_versions
=
python
if
deps
:
if
deps
:
for
dep_name
,
dep_constraint
in
deps
.
items
():
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):
...
@@ -50,3 +50,101 @@ def test_backjumps_after_partial_satisfier(root, provider, repo):
add_to_repo
(
repo
,
"y"
,
"2.0.0"
)
add_to_repo
(
repo
,
"y"
,
"2.0.0"
)
check_solver_result
(
root
,
provider
,
{
"c"
:
"1.0.0"
,
"y"
:
"2.0.0"
},
tries
=
2
)
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):
...
@@ -79,7 +79,7 @@ def test_no_valid_solution(root, provider, repo):
error
=
"""
\
error
=
"""
\
Because no versions of b match <1.0.0 || >1.0.0,<2.0.0 || >2.0.0
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.
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.
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.
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(
...
@@ -841,7 +841,7 @@ def test_solver_duplicate_dependencies_different_constraints_same_requirements(
expected
=
"""
\
expected
=
"""
\
Because a (1.0) depends on both B (^1.0) and B (^2.0), a is forbidden.
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."""
and root depends on A (*), version solving failed."""
assert
str
(
e
.
value
)
==
expected
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):
...
@@ -160,9 +160,9 @@ def test_parse_constraints_negative_wildcard(input, constraint):
(
"1"
,
"1"
),
(
"1"
,
"1"
),
(
"1.2"
,
"1.2"
),
(
"1.2"
,
"1.2"
),
(
"1.2.3"
,
"1.2.3"
),
(
"1.2.3"
,
"1.2.3"
),
(
"!=1"
,
"
<1 || >
1"
),
(
"!=1"
,
"
!=
1"
),
(
"!=1.2"
,
"
<1.2 || >
1.2"
),
(
"!=1.2"
,
"
!=
1.2"
),
(
"!=1.2.3"
,
"
<1.2.3 || >
1.2.3"
),
(
"!=1.2.3"
,
"
!=
1.2.3"
),
(
"^1"
,
">=1,<2"
),
(
"^1"
,
">=1,<2"
),
(
"^1.0"
,
">=1.0,<2.0"
),
(
"^1.0"
,
">=1.0,<2.0"
),
(
"^1.0.0"
,
">=1.0.0,<2.0.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