diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index 2b696b3450a..2283a2373d2 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -182,9 +182,9 @@ def locked_repository( return packages def get_project_dependencies( - self, project_requires, pinned_versions=False, with_nested=False - ): # type: (List[Dependency], bool, bool) -> Any - packages = self.locked_repository().packages + self, project_requires, pinned_versions=False, with_nested=False, with_dev=False + ): # type: (List[Dependency], bool, bool, bool) -> Any + packages = self.locked_repository(with_dev).packages # group packages entries by name, this is required because requirement might use # different constraints @@ -230,6 +230,10 @@ def __get_locked_package( # project level dependencies take precedence continue + # we make a copy to avoid any side-effects + requirement = deepcopy(requirement) + requirement._category = pkg.category + if pinned_versions: requirement.set_constraint( __get_locked_package(requirement).to_dependency().constraint diff --git a/poetry/utils/exporter.py b/poetry/utils/exporter.py index 19b48452346..ee8e751f95b 100644 --- a/poetry/utils/exporter.py +++ b/poetry/utils/exporter.py @@ -68,12 +68,14 @@ def _export_requirements_txt( 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, + project_requires=self._poetry.package.all_requires, with_nested=True, + with_dev=dev, ): - package = repository.find_packages(dependency=dependency)[0] + try: + package = repository.find_packages(dependency=dependency)[0] + except IndexError: + 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: diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index c2eee0d1f11..e26f448f6de 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -779,6 +779,68 @@ def test_exporter_exports_requirements_txt_with_legacy_packages(tmp_dir, poetry) assert expected == content +@pytest.mark.parametrize( + ("dev", "expected"), + [ + (True, ["bar==1.2.2", "baz==1.2.3", "foo==1.2.1"]), + (False, ["bar==1.2.2", "foo==1.2.1"]), + ], +) +def test_exporter_exports_requirements_txt_with_dev_extras( + tmp_dir, poetry, dev, expected +): + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "foo", + "version": "1.2.1", + "category": "main", + "optional": False, + "python-versions": "*", + }, + { + "name": "bar", + "version": "1.2.2", + "category": "main", + "optional": False, + "python-versions": "*", + "dependencies": { + "baz": { + "version": ">=0.1.0", + "optional": True, + "markers": "extra == 'baz'", + } + }, + "extras": {"baz": ["baz (>=0.1.0)"]}, + }, + { + "name": "baz", + "version": "1.2.3", + "category": "dev", + "optional": False, + "python-versions": "*", + }, + ], + "metadata": { + "python-versions": "*", + "content-hash": "123456789", + "hashes": {"foo": [], "bar": [], "baz": []}, + }, + } + ) + set_package_requires(poetry) + + exporter = Exporter(poetry) + + exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=dev) + + with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f: + content = f.read() + + assert content == "{}\n".format("\n".join(expected)) + + def test_exporter_exports_requirements_txt_with_legacy_packages_and_duplicate_sources( tmp_dir, poetry ):