Skip to content

Commit

Permalink
locker: improve nested marker propagation
Browse files Browse the repository at this point in the history
Resolves: #3160
  • Loading branch information
abn committed Oct 12, 2020
1 parent 946393c commit a1060bc
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 25 deletions.
52 changes: 27 additions & 25 deletions poetry/packages/locker.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,18 @@ def __get_locked_package(

for dependency in project_requires:
dependency = deepcopy(dependency)
if pinned_versions:
locked_package = __get_locked_package(dependency)
if locked_package:
dependency.set_constraint(locked_package.to_dependency().constraint)
locked_package = __get_locked_package(dependency)
if locked_package:
locked_dependency = locked_package.to_dependency()
locked_dependency.marker = dependency.marker.intersect(
locked_package.marker
)

if not pinned_versions:
locked_dependency.set_constraint(dependency.constraint)

dependency = locked_dependency

project_level_dependencies.add(dependency.name)
dependencies.append(dependency)

Expand All @@ -228,7 +236,9 @@ def __get_locked_package(

nested_dependencies = dict()

def __walk_level(__dependencies): # type: (List[Dependency]) -> None
def __walk_level(
__dependencies, __level
): # type: (List[Dependency], int) -> None
if not __dependencies:
return

Expand All @@ -239,18 +249,19 @@ def __walk_level(__dependencies): # type: (List[Dependency]) -> None

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

require.marker = require.marker.intersect(
__locked_package.marker
)
_next_level.append(require)

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

Expand All @@ -272,20 +283,11 @@ def __walk_level(__dependencies): # type: (List[Dependency]) -> None
)

# dependencies use extra to indicate that it was activated via parent
# package's extras
marker = requirement.marker.without_extras()
for project_requirement in project_requires:
if (
pkg.name == project_requirement.name
and project_requirement.constraint.allows(pkg.version)
):
requirement.marker = marker.intersect(
project_requirement.marker
)
break
else:
# this dependency was not from a project requirement
requirement.marker = marker.intersect(pkg.marker)
# 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().intersect(
pkg.marker
)

key = (requirement.name, requirement.pretty_constraint)
if key not in nested_dependencies:
Expand All @@ -295,9 +297,9 @@ def __walk_level(__dependencies): # type: (List[Dependency]) -> None
key
].marker.intersect(requirement.marker)

return __walk_level(_next_level)
return __walk_level(_next_level, __level + 1)

__walk_level(dependencies)
__walk_level(dependencies, 0)

return sorted(
itertools.chain(dependencies, nested_dependencies.values()),
Expand Down
59 changes: 59 additions & 0 deletions tests/utils/test_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,65 @@ def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers(
assert expected == {}


def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers_any(
tmp_dir, poetry
):
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "a",
"version": "1.2.3",
"category": "main",
"optional": False,
"python-versions": "*",
},
{
"name": "b",
"version": "4.5.6",
"category": "dev",
"optional": False,
"python-versions": "*",
"dependencies": {"a": ">=1.2.3"},
},
],
"metadata": {
"python-versions": "*",
"content-hash": "123456789",
"hashes": {"a": [], "b": []},
},
}
)

poetry.package.requires = [
Factory.create_dependency(
name="a", constraint=dict(version="^1.2.3", python="<3.8")
),
]
poetry.package.dev_requires = [
Factory.create_dependency(
name="b", constraint=dict(version="^4.5.6"), category="dev"
),
Factory.create_dependency(name="a", constraint=dict(version="^1.2.3")),
]

exporter = Exporter(poetry)

exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True)

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

assert (
content
== """\
a==1.2.3
a==1.2.3; python_version < "3.8"
b==4.5.6
"""
)


def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes(
tmp_dir, poetry
):
Expand Down

0 comments on commit a1060bc

Please sign in to comment.