Skip to content

Commit

Permalink
locker: handle cyclic dependencies during walk
Browse files Browse the repository at this point in the history
Resolves: #3213
  • Loading branch information
abn committed Oct 20, 2020
1 parent 66d29f6 commit 3f66d5c
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 27 deletions.
52 changes: 25 additions & 27 deletions poetry/packages/locker.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,45 +217,43 @@ def __walk_dependency_level(
next_level_dependencies = []

for requirement in dependencies:
key = (requirement.name, requirement.pretty_constraint)
locked_package = cls.__get_locked_package(requirement, packages_by_name)

if locked_package:
for require in locked_package.requires:
if require.marker.is_empty():
require.marker = requirement.marker
else:
require.marker = require.marker.intersect(requirement.marker)
# create dependency from locked package to retain dependency metadata
# if this is not done, we can end-up with incorrect nested dependencies
marker = requirement.marker
requirement = locked_package.to_dependency()
requirement.marker = requirement.marker.intersect(marker)

key = (requirement.name, requirement.pretty_constraint)

if pinned_versions:
requirement.set_constraint(
locked_package.to_dependency().constraint
)

if key not in nested_dependencies:
for require in locked_package.requires:
if require.marker.is_empty():
require.marker = requirement.marker
else:
require.marker = require.marker.intersect(
requirement.marker
)

require.marker = require.marker.intersect(locked_package.marker)
next_level_dependencies.append(require)
require.marker = require.marker.intersect(locked_package.marker)
next_level_dependencies.append(require)

if requirement.name in project_level_dependencies and level == 0:
# project level dependencies take precedence
continue

if locked_package:
# create dependency from locked package to retain dependency metadata
# if this is not done, we can end-up with incorrect nested dependencies
marker = requirement.marker
requirement = locked_package.to_dependency()
requirement.marker = requirement.marker.intersect(marker)
else:
if not locked_package:
# we make a copy to avoid any side-effects
requirement = deepcopy(requirement)

if pinned_versions:
requirement.set_constraint(
cls.__get_locked_package(requirement, packages_by_name)
.to_dependency()
.constraint
)

# dependencies use extra to indicate that it was activated via parent
# package's extras, this is not required for nested exports as we assume
# the resolver already selected this dependency
requirement.marker = requirement.marker.without_extras()

key = (requirement.name, requirement.pretty_constraint)
if key not in nested_dependencies:
nested_dependencies[key] = requirement
else:
Expand Down
56 changes: 56 additions & 0 deletions tests/utils/test_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,62 @@ def test_exporter_can_export_requirements_txt_with_nested_packages(tmp_dir, poet
assert expected == content


def test_exporter_can_export_requirements_txt_with_nested_packages_cyclic(
tmp_dir, poetry
):
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "foo",
"version": "1.2.3",
"category": "main",
"optional": False,
"python-versions": "*",
"dependencies": {"bar": {"version": "4.5.6"}},
},
{
"name": "bar",
"version": "4.5.6",
"category": "main",
"optional": False,
"python-versions": "*",
"dependencies": {"baz": {"version": "7.8.9"}},
},
{
"name": "baz",
"version": "7.8.9",
"category": "main",
"optional": False,
"python-versions": "*",
"dependencies": {"foo": {"version": "1.2.3"}},
},
],
"metadata": {
"python-versions": "*",
"content-hash": "123456789",
"hashes": {"foo": [], "bar": [], "baz": []},
},
}
)
set_package_requires(poetry, skip={"bar", "baz"})

exporter = Exporter(poetry)

exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")

with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f:
content = f.read()

expected = """\
bar==4.5.6
baz==7.8.9
foo==1.2.3
"""

assert expected == content


def test_exporter_can_export_requirements_txt_with_git_packages_and_markers(
tmp_dir, poetry
):
Expand Down

0 comments on commit 3f66d5c

Please sign in to comment.