diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index a486bf1c5d6..2a82fef816a 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -234,8 +234,15 @@ def __get_locked_package( # project level dependencies take precedence continue - # we make a copy to avoid any side-effects - requirement = deepcopy(requirement) + locked_package = __get_locked_package(requirement) + 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 + requirement = locked_package.to_dependency() + else: + # we make a copy to avoid any side-effects + requirement = deepcopy(requirement) + requirement._category = pkg.category if pinned_versions: diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index e26f448f6de..a75fb3da502 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -59,13 +59,18 @@ def poetry(fixture_dir, locker): return p -def set_package_requires(poetry): +def set_package_requires(poetry, skip=None): + skip = skip or set() packages = poetry.locker.locked_repository(with_dev_reqs=True).packages poetry.package.requires = [ - pkg.to_dependency() for pkg in packages if pkg.category == "main" + pkg.to_dependency() + for pkg in packages + if pkg.category == "main" and pkg.name not in skip ] poetry.package.dev_requires = [ - pkg.to_dependency() for pkg in packages if pkg.category == "dev" + pkg.to_dependency() + for pkg in packages + if pkg.category == "dev" and pkg.name not in skip ] @@ -503,6 +508,55 @@ def test_exporter_can_export_requirements_txt_with_git_packages(tmp_dir, poetry) assert expected == content +def test_exporter_can_export_requirements_txt_with_nested_packages(tmp_dir, poetry): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.3", + "category": "main", + "optional": False, + "python-versions": "*", + "source": { + "type": "git", + "url": "https://github.com/foo/foo.git", + "reference": "123456", + }, + }, + { + "name": "bar", + "version": "4.5.6", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": {"foo": "rev 123456"}, + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": []}, + }, + } + ) + set_package_requires(poetry, skip={"foo"}) + + 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 +foo @ git+https://github.com/foo/foo.git@123456 +""" + + assert expected == content + + def test_exporter_can_export_requirements_txt_with_git_packages_and_markers( tmp_dir, poetry ):