Commit 1159148b by Arun Babu Neelicattu

export: ensure propagated markers are exported

Resolves: #2662
parent d82c8082
import os
from typing import Union from typing import Union
from clikit.api.io import IO from clikit.api.io import IO
from poetry.core.packages.directory_dependency import DirectoryDependency
from poetry.core.packages.file_dependency import FileDependency
from poetry.core.packages.url_dependency import URLDependency
from poetry.core.packages.vcs_dependency import VCSDependency
from poetry.poetry import Poetry from poetry.poetry import Poetry
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import decode from poetry.utils._compat import decode
...@@ -60,75 +54,54 @@ class Exporter(object): ...@@ -60,75 +54,54 @@ class Exporter(object):
): # type: (Path, Union[IO, str], bool, bool, bool) -> None ): # type: (Path, Union[IO, str], bool, bool, bool) -> None
indexes = set() indexes = set()
content = "" content = ""
packages = self._poetry.locker.locked_repository(dev).packages repository = self._poetry.locker.locked_repository(dev)
# Build a set of all packages required by our selected extras # Build a set of all packages required by our selected extras
extra_package_names = set( extra_package_names = set(
get_extra_package_names( get_extra_package_names(
packages, self._poetry.locker.lock_data.get("extras", {}), extras or () repository.packages,
self._poetry.locker.lock_data.get("extras", {}),
extras or (),
) )
) )
for package in sorted(packages, key=lambda p: p.name): dependency_lines = set()
for dependency in self._poetry.locker.get_project_dependencies(
project_requires=self._poetry.package.requires
if not dev
else self._poetry.package.all_requires,
with_nested=True,
):
package = repository.find_packages(dependency=dependency)[0]
# If a package is optional and we haven't opted in to it, continue # If a package is optional and we haven't opted in to it, continue
if package.optional and package.name not in extra_package_names: if package.optional and package.name not in extra_package_names:
continue continue
if package.source_type == "git": line = ""
dependency = VCSDependency(
package.name,
package.source_type,
package.source_url,
package.source_reference,
)
dependency.marker = package.marker
line = "-e git+{}@{}#egg={}".format(
package.source_url, package.source_reference, package.name
)
elif package.source_type in ["directory", "file", "url"]:
url = package.source_url
if package.source_type == "file":
dependency = FileDependency(
package.name,
Path(package.source_url),
base=self._poetry.locker.lock.path.parent,
)
url = Path(
os.path.relpath(
url, self._poetry.locker.lock.path.parent.as_posix()
)
).as_posix()
elif package.source_type == "directory":
dependency = DirectoryDependency(
package.name,
Path(package.source_url),
base=self._poetry.locker.lock.path.parent,
)
url = Path(
os.path.relpath(
url, self._poetry.locker.lock.path.parent.as_posix()
)
).as_posix()
else:
dependency = URLDependency(package.name, package.source_url)
dependency.marker = package.marker if package.develop:
line += "-e "
line = "{}".format(url) requirement = dependency.to_pep_508(with_extras=False)
if package.develop and package.source_type == "directory": is_direct_reference = (
line = "-e " + line dependency.is_vcs()
or dependency.is_url()
or dependency.is_file()
or dependency.is_directory()
)
if is_direct_reference:
line = requirement
else: else:
dependency = package.to_dependency()
line = "{}=={}".format(package.name, package.version) line = "{}=={}".format(package.name, package.version)
if ";" in requirement:
markers = requirement.split(";", 1)[1].strip()
if markers:
line += "; {}".format(markers)
requirement = dependency.to_pep_508() if not is_direct_reference and package.source_url:
if ";" in requirement:
line += "; {}".format(requirement.split(";")[1].strip())
if (
package.source_type not in {"git", "directory", "file", "url"}
and package.source_url
):
indexes.add(package.source_url) indexes.add(package.source_url)
if package.files and with_hashes: if package.files and with_hashes:
...@@ -150,9 +123,10 @@ class Exporter(object): ...@@ -150,9 +123,10 @@ class Exporter(object):
line += " --hash={}{}".format( line += " --hash={}{}".format(
h, " \\\n" if i < len(hashes) - 1 else "" h, " \\\n" if i < len(hashes) - 1 else ""
) )
dependency_lines.add(line)
line += "\n" content += "\n".join(sorted(dependency_lines))
content += line content += "\n"
if indexes: if indexes:
# If we have extra indexes, we add them to the beginning of the output # If we have extra indexes, we add them to the beginning of the output
......
...@@ -34,6 +34,18 @@ class Locker(BaseLocker): ...@@ -34,6 +34,18 @@ class Locker(BaseLocker):
return "123456789" return "123456789"
@pytest.fixture
def working_directory():
return Path(__file__).parent.parent.parent
@pytest.fixture(autouse=True)
def mock_path_cwd(mocker, working_directory):
yield mocker.patch(
"poetry.core.utils._compat.Path.cwd", return_value=working_directory
)
@pytest.fixture() @pytest.fixture()
def locker(): def locker():
return Locker() return Locker()
...@@ -47,7 +59,19 @@ def poetry(fixture_dir, locker): ...@@ -47,7 +59,19 @@ def poetry(fixture_dir, locker):
return p return p
def test_exporter_can_export_requirements_txt_with_standard_packages(tmp_dir, poetry): def set_package_requires(poetry):
packages = poetry.locker.locked_repository(with_dev_reqs=True).packages
poetry.package.requires = [
pkg.to_dependency() for pkg in packages if pkg.category == "main"
]
poetry.package.dev_requires = [
pkg.to_dependency() for pkg in packages if pkg.category == "dev"
]
def test_exporter_can_export_requirements_txt_with_standard_packages(
tmp_dir, poetry, mocker
):
poetry.locker.mock_lock_data( poetry.locker.mock_lock_data(
{ {
"package": [ "package": [
...@@ -73,6 +97,8 @@ def test_exporter_can_export_requirements_txt_with_standard_packages(tmp_dir, po ...@@ -73,6 +97,8 @@ def test_exporter_can_export_requirements_txt_with_standard_packages(tmp_dir, po
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")
...@@ -110,14 +136,24 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_markers ...@@ -110,14 +136,24 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_markers
"python-versions": "*", "python-versions": "*",
"marker": "extra =='foo'", "marker": "extra =='foo'",
}, },
{
"name": "baz",
"version": "7.8.9",
"category": "main",
"optional": False,
"python-versions": "*",
"marker": "sys_platform == 'win32'",
},
], ],
"metadata": { "metadata": {
"python-versions": "*", "python-versions": "*",
"content-hash": "123456789", "content-hash": "123456789",
"hashes": {"foo": [], "bar": []}, "hashes": {"foo": [], "bar": [], "baz": []},
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")
...@@ -126,7 +162,8 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_markers ...@@ -126,7 +162,8 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_markers
content = f.read() content = f.read()
expected = """\ expected = """\
bar==4.5.6; extra == "foo" bar==4.5.6
baz==7.8.9; sys_platform == "win32"
foo==1.2.3; python_version < "3.7" foo==1.2.3; python_version < "3.7"
""" """
...@@ -161,6 +198,8 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes( ...@@ -161,6 +198,8 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes(
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")
...@@ -206,6 +245,8 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes_ ...@@ -206,6 +245,8 @@ def test_exporter_can_export_requirements_txt_with_standard_packages_and_hashes_
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export( exporter.export(
...@@ -251,6 +292,8 @@ def test_exporter_exports_requirements_txt_without_dev_packages_by_default( ...@@ -251,6 +292,8 @@ def test_exporter_exports_requirements_txt_without_dev_packages_by_default(
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")
...@@ -294,6 +337,8 @@ def test_exporter_exports_requirements_txt_with_dev_packages_if_opted_in( ...@@ -294,6 +337,8 @@ def test_exporter_exports_requirements_txt_with_dev_packages_if_opted_in(
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True)
...@@ -337,6 +382,8 @@ def test_exporter_exports_requirements_txt_without_optional_packages(tmp_dir, po ...@@ -337,6 +382,8 @@ def test_exporter_exports_requirements_txt_without_optional_packages(tmp_dir, po
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True)
...@@ -389,6 +436,8 @@ def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in( ...@@ -389,6 +436,8 @@ def test_exporter_exports_requirements_txt_with_optional_packages_if_opted_in(
"extras": {"feature_bar": ["bar"]}, "extras": {"feature_bar": ["bar"]},
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export( exporter.export(
...@@ -438,6 +487,8 @@ def test_exporter_can_export_requirements_txt_with_git_packages(tmp_dir, poetry) ...@@ -438,6 +487,8 @@ def test_exporter_can_export_requirements_txt_with_git_packages(tmp_dir, poetry)
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")
...@@ -446,7 +497,7 @@ def test_exporter_can_export_requirements_txt_with_git_packages(tmp_dir, poetry) ...@@ -446,7 +497,7 @@ def test_exporter_can_export_requirements_txt_with_git_packages(tmp_dir, poetry)
content = f.read() content = f.read()
expected = """\ expected = """\
-e git+https://github.com/foo/foo.git@123456#egg=foo foo @ git+https://github.com/foo/foo.git@123456
""" """
assert expected == content assert expected == content
...@@ -479,6 +530,8 @@ def test_exporter_can_export_requirements_txt_with_git_packages_and_markers( ...@@ -479,6 +530,8 @@ def test_exporter_can_export_requirements_txt_with_git_packages_and_markers(
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")
...@@ -487,13 +540,15 @@ def test_exporter_can_export_requirements_txt_with_git_packages_and_markers( ...@@ -487,13 +540,15 @@ def test_exporter_can_export_requirements_txt_with_git_packages_and_markers(
content = f.read() content = f.read()
expected = """\ expected = """\
-e git+https://github.com/foo/foo.git@123456#egg=foo; python_version < "3.7" foo @ git+https://github.com/foo/foo.git@123456 ; python_version < "3.7"
""" """
assert expected == content assert expected == content
def test_exporter_can_export_requirements_txt_with_directory_packages(tmp_dir, poetry): def test_exporter_can_export_requirements_txt_with_directory_packages(
tmp_dir, poetry, working_directory
):
poetry.locker.mock_lock_data( poetry.locker.mock_lock_data(
{ {
"package": [ "package": [
...@@ -517,6 +572,8 @@ def test_exporter_can_export_requirements_txt_with_directory_packages(tmp_dir, p ...@@ -517,6 +572,8 @@ def test_exporter_can_export_requirements_txt_with_directory_packages(tmp_dir, p
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")
...@@ -525,14 +582,16 @@ def test_exporter_can_export_requirements_txt_with_directory_packages(tmp_dir, p ...@@ -525,14 +582,16 @@ def test_exporter_can_export_requirements_txt_with_directory_packages(tmp_dir, p
content = f.read() content = f.read()
expected = """\ expected = """\
-e tests/fixtures/sample_project foo @ {}/tests/fixtures/sample_project
""" """.format(
working_directory.as_posix()
)
assert expected == content assert expected == content
def test_exporter_can_export_requirements_txt_with_directory_packages_and_markers( def test_exporter_can_export_requirements_txt_with_directory_packages_and_markers(
tmp_dir, poetry tmp_dir, poetry, working_directory
): ):
poetry.locker.mock_lock_data( poetry.locker.mock_lock_data(
{ {
...@@ -558,6 +617,8 @@ def test_exporter_can_export_requirements_txt_with_directory_packages_and_marker ...@@ -558,6 +617,8 @@ def test_exporter_can_export_requirements_txt_with_directory_packages_and_marker
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")
...@@ -566,13 +627,17 @@ def test_exporter_can_export_requirements_txt_with_directory_packages_and_marker ...@@ -566,13 +627,17 @@ def test_exporter_can_export_requirements_txt_with_directory_packages_and_marker
content = f.read() content = f.read()
expected = """\ expected = """\
-e tests/fixtures/sample_project; python_version < "3.7" foo @ {}/tests/fixtures/sample_project; python_version < "3.7"
""" """.format(
working_directory.as_posix()
)
assert expected == content assert expected == content
def test_exporter_can_export_requirements_txt_with_file_packages(tmp_dir, poetry): def test_exporter_can_export_requirements_txt_with_file_packages(
tmp_dir, poetry, working_directory
):
poetry.locker.mock_lock_data( poetry.locker.mock_lock_data(
{ {
"package": [ "package": [
...@@ -596,6 +661,8 @@ def test_exporter_can_export_requirements_txt_with_file_packages(tmp_dir, poetry ...@@ -596,6 +661,8 @@ def test_exporter_can_export_requirements_txt_with_file_packages(tmp_dir, poetry
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")
...@@ -604,14 +671,16 @@ def test_exporter_can_export_requirements_txt_with_file_packages(tmp_dir, poetry ...@@ -604,14 +671,16 @@ def test_exporter_can_export_requirements_txt_with_file_packages(tmp_dir, poetry
content = f.read() content = f.read()
expected = """\ expected = """\
tests/fixtures/distributions/demo-0.1.0.tar.gz foo @ {}/tests/fixtures/distributions/demo-0.1.0.tar.gz
""" """.format(
working_directory.as_uri()
)
assert expected == content assert expected == content
def test_exporter_can_export_requirements_txt_with_file_packages_and_markers( def test_exporter_can_export_requirements_txt_with_file_packages_and_markers(
tmp_dir, poetry tmp_dir, poetry, working_directory
): ):
poetry.locker.mock_lock_data( poetry.locker.mock_lock_data(
{ {
...@@ -637,6 +706,8 @@ def test_exporter_can_export_requirements_txt_with_file_packages_and_markers( ...@@ -637,6 +706,8 @@ def test_exporter_can_export_requirements_txt_with_file_packages_and_markers(
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt") exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt")
...@@ -645,8 +716,10 @@ def test_exporter_can_export_requirements_txt_with_file_packages_and_markers( ...@@ -645,8 +716,10 @@ def test_exporter_can_export_requirements_txt_with_file_packages_and_markers(
content = f.read() content = f.read()
expected = """\ expected = """\
tests/fixtures/distributions/demo-0.1.0.tar.gz; python_version < "3.7" foo @ {}/tests/fixtures/distributions/demo-0.1.0.tar.gz; python_version < "3.7"
""" """.format(
working_directory.as_uri()
)
assert expected == content assert expected == content
...@@ -685,6 +758,8 @@ def test_exporter_exports_requirements_txt_with_legacy_packages(tmp_dir, poetry) ...@@ -685,6 +758,8 @@ def test_exporter_exports_requirements_txt_with_legacy_packages(tmp_dir, poetry)
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True)
...@@ -758,6 +833,8 @@ def test_exporter_exports_requirements_txt_with_legacy_packages_and_duplicate_so ...@@ -758,6 +833,8 @@ def test_exporter_exports_requirements_txt_with_legacy_packages_and_duplicate_so
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True) exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True)
...@@ -822,6 +899,8 @@ def test_exporter_exports_requirements_txt_with_legacy_packages_and_credentials( ...@@ -822,6 +899,8 @@ def test_exporter_exports_requirements_txt_with_legacy_packages_and_credentials(
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export( exporter.export(
...@@ -873,6 +952,8 @@ def test_exporter_exports_requirements_txt_to_standard_output(tmp_dir, poetry, c ...@@ -873,6 +952,8 @@ def test_exporter_exports_requirements_txt_to_standard_output(tmp_dir, poetry, c
}, },
} }
) )
set_package_requires(poetry)
exporter = Exporter(poetry) exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), sys.stdout) exporter.export("requirements.txt", Path(tmp_dir), sys.stdout)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment