From a93aed2013a9204a3d53e504d64e36173a501c6e Mon Sep 17 00:00:00 2001 From: David Hotham Date: Tue, 14 Nov 2023 12:02:20 +0000 Subject: [PATCH] treat collection types in package as immutable --- src/poetry/inspection/info.py | 14 ++++++++--- src/poetry/packages/locker.py | 9 +++++-- src/poetry/repositories/pypi_repository.py | 5 ++-- tests/installation/test_installer.py | 6 ++--- tests/packages/test_locker.py | 2 +- tests/puzzle/test_provider.py | 2 +- tests/puzzle/test_solver.py | 29 +++++++++++----------- 7 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index 0c9cfb519d5..9d22961f727 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -9,6 +9,8 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any +from typing import Mapping +from typing import Sequence import pkginfo @@ -31,6 +33,7 @@ from collections.abc import Iterator from packaging.metadata import RawMetadata + from packaging.utils import NormalizedName from poetry.core.packages.project_package import ProjectPackage @@ -71,7 +74,7 @@ def __init__( summary: str | None = None, requires_dist: list[str] | None = None, requires_python: str | None = None, - files: list[dict[str, str]] | None = None, + files: Sequence[Mapping[str, str]] | None = None, yanked: str | bool = False, cache_version: str | None = None, ) -> None: @@ -187,6 +190,7 @@ def to_package( seen_requirements = set() + package_extras: dict[NormalizedName, list[Dependency]] = {} for req in self.requires_dist or []: try: # Attempt to parse the PEP-508 requirement string @@ -214,12 +218,12 @@ def to_package( if dependency.in_extras: # this dependency is required by an extra package for extra in dependency.in_extras: - if extra not in package.extras: + if extra not in package_extras: # this is the first time we encounter this extra for this # package - package.extras[extra] = [] + package_extras[extra] = [] - package.extras[extra].append(dependency) + package_extras[extra].append(dependency) req = dependency.to_pep_508(with_extras=True) @@ -227,6 +231,8 @@ def to_package( package.add_dependency(dependency) seen_requirements.add(req) + package.extras = package_extras + return package @classmethod diff --git a/src/poetry/packages/locker.py b/src/poetry/packages/locker.py index 822e0a59be5..0de62b97f66 100644 --- a/src/poetry/packages/locker.py +++ b/src/poetry/packages/locker.py @@ -31,6 +31,7 @@ if TYPE_CHECKING: + from packaging.utils import NormalizedName from poetry.core.packages.directory_dependency import DirectoryDependency from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.url_dependency import URLDependency @@ -167,11 +168,13 @@ def locked_repository(self) -> LockfileRepository: package.files = files package.python_versions = info["python-versions"] + + package_extras: dict[NormalizedName, list[Dependency]] = {} extras = info.get("extras", {}) if extras: for name, deps in extras.items(): name = canonicalize_name(name) - package.extras[name] = [] + package_extras[name] = [] for dep in deps: try: @@ -187,7 +190,9 @@ def locked_repository(self) -> LockfileRepository: dependency = Dependency( dep_name, constraint, extras=extras.split(",") ) - package.extras[name].append(dependency) + package_extras[name].append(dependency) + + package.extras = package_extras if "marker" in info: package.marker = parse_marker(info["marker"]) diff --git a/src/poetry/repositories/pypi_repository.py b/src/poetry/repositories/pypi_repository.py index 26a7d497c70..468dc910836 100644 --- a/src/poetry/repositories/pypi_repository.py +++ b/src/poetry/repositories/pypi_repository.py @@ -143,7 +143,6 @@ def _get_release_info( summary=info["summary"], requires_dist=info["requires_dist"], requires_python=info["requires_python"], - files=info.get("files", []), yanked=self._get_yanked(info), cache_version=str(self.CACHE_VERSION), ) @@ -153,12 +152,14 @@ def _get_release_info( except KeyError: version_info = [] + files = info.get("files", []) for file_info in version_info: if file_info["packagetype"] in SUPPORTED_PACKAGE_TYPES: - data.files.append({ + files.append({ "file": file_info["filename"], "hash": "sha256:" + file_info["digests"]["sha256"], }) + data.files = files if self._fallback and data.requires_dist is None: self._log("No dependencies found, downloading archives", level="debug") diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index b903348c4d9..27b4d8a3fc4 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -1006,7 +1006,7 @@ def test_run_installs_extras_with_deps_if_requested( with_extras: bool, do_sync: bool, ) -> None: - package.extras[canonicalize_name("foo")] = [get_dependency("C")] + package.extras = {canonicalize_name("foo"): [get_dependency("C")]} package_a = get_package("A", "1.0") package_b = get_package("B", "1.0") package_c = get_package("C", "1.0") @@ -1404,9 +1404,9 @@ def test_run_update_with_locked_extras( }, }) package_a = get_package("A", "1.0") - package_a.extras[canonicalize_name("foo")] = [get_dependency("B")] + package_a.extras = {canonicalize_name("foo"): [get_dependency("B")]} b_dependency = get_dependency("B", "^1.0", optional=True) - b_dependency.in_extras.append(canonicalize_name("foo")) + b_dependency._in_extras = [canonicalize_name("foo")] c_dependency = get_dependency("C", "^1.0") c_dependency.python_versions = "~2.7" package_a.add_dependency(b_dependency) diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index adf143d5cc5..4e2ff2909aa 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -552,7 +552,7 @@ def test_lock_file_should_not_have_mixed_types( Factory.create_dependency("B", {"version": ">=1.0.0", "optional": True}) ) package_a.requires[-1].activate() - package_a.extras[canonicalize_name("foo")] = [get_dependency("B", ">=1.0.0")] + package_a.extras = {canonicalize_name("foo"): [get_dependency("B", ">=1.0.0")]} locker.set_lock_data(root, [package_a]) diff --git a/tests/puzzle/test_provider.py b/tests/puzzle/test_provider.py index ebb7800ec60..9ee1f6fc644 100644 --- a/tests/puzzle/test_provider.py +++ b/tests/puzzle/test_provider.py @@ -770,7 +770,7 @@ def test_complete_package_fetches_optional_vcs_dependency_only_if_requested( ) package = Package("A", "1.0", features=["foo"] if with_extra else []) package.add_dependency(optional_vcs_dependency) - package.extras[canonicalize_name("foo")] = [optional_vcs_dependency] + package.extras = {canonicalize_name("foo"): [optional_vcs_dependency]} repository.add_package(package) spy = mocker.spy(provider, "_search_for_vcs") diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 8de5f34a76d..5f4a8f3c21e 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -449,7 +449,7 @@ def test_solver_solves_optional_and_compatible_packages( solver: Solver, repo: Repository, package: ProjectPackage ) -> None: set_package_python_versions(solver.provider, "~3.4") - package.extras[canonicalize_name("foo")] = [get_dependency("B")] + package.extras = {canonicalize_name("foo"): [get_dependency("B")]} package.add_dependency( Factory.create_dependency("A", {"version": "*", "python": "^3.4"}) ) @@ -563,11 +563,11 @@ def test_solver_returns_extras_only_requested( package_c20 = get_package("C", "2.0") dep10 = get_dependency("C", "1.0", optional=True) - dep10._in_extras.append(canonicalize_name("one")) + dep10._in_extras = [canonicalize_name("one")] dep10.marker = parse_marker("extra == 'one'") dep20 = get_dependency("C", "2.0", optional=True) - dep20._in_extras.append(canonicalize_name("two")) + dep20._in_extras = [canonicalize_name("two")] dep20.marker = parse_marker("extra == 'two'") package_b.extras = { @@ -622,8 +622,7 @@ def test_solver_returns_extras_when_multiple_extras_use_same_dependency( package_c = get_package("C", "1.0") dep = get_dependency("C", "*", optional=True) - dep._in_extras.append(canonicalize_name("one")) - dep._in_extras.append(canonicalize_name("two")) + dep._in_extras = [canonicalize_name("one"), canonicalize_name("two")] package_b.extras = { canonicalize_name("one"): [dep], @@ -675,11 +674,11 @@ def test_solver_returns_extras_only_requested_nested( package_c20 = get_package("C", "2.0") dep10 = get_dependency("C", "1.0", optional=True) - dep10._in_extras.append(canonicalize_name("one")) + dep10._in_extras = [canonicalize_name("one")] dep10.marker = parse_marker("extra == 'one'") dep20 = get_dependency("C", "2.0", optional=True) - dep20._in_extras.append(canonicalize_name("two")) + dep20._in_extras = [canonicalize_name("two")] dep20.marker = parse_marker("extra == 'two'") package_b.extras = { @@ -1075,7 +1074,7 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies( ) package_a = get_package("A", "1.0") - package_a.extras[canonicalize_name("foo")] = [get_dependency("C")] + package_a.extras = {canonicalize_name("foo"): [get_dependency("C")]} package_a.add_dependency( Factory.create_dependency("C", {"version": "^1.0", "optional": True}) ) @@ -1118,7 +1117,7 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies_with_one_more_ ) package_a = get_package("A", "1.0") - package_a.extras[canonicalize_name("foo")] = [get_dependency("C")] + package_a.extras = {canonicalize_name("foo"): [get_dependency("C")]} package_a.add_dependency( Factory.create_dependency("C", {"version": "^1.0", "optional": True}) ) @@ -3351,7 +3350,9 @@ def test_solver_does_not_loop_indefinitely_on_duplicate_constraints_with_extras( "idna", {"version": ">=2.0.0", "markers": "extra == 'security'"} ) ) - requests.extras[canonicalize_name("security")] = [get_dependency("idna", ">=2.0.0")] + requests.extras = { + canonicalize_name("security"): [get_dependency("idna", ">=2.0.0")] + } idna = get_package("idna", "2.8") repo.add_package(requests) @@ -3787,7 +3788,7 @@ def test_solver_can_resolve_transitive_extras( requests.add_dependency( Factory.create_dependency("PyOpenSSL", {"version": ">=0.14", "optional": True}) ) - requests.extras[canonicalize_name("security")] = [dep] + requests.extras = {canonicalize_name("security"): [dep]} pyota = get_package("PyOTA", "2.1.0") pyota.add_dependency( Factory.create_dependency( @@ -3828,9 +3829,9 @@ def test_solver_can_resolve_for_packages_with_missing_extras( django_anymail.add_dependency( Factory.create_dependency("boto3", {"version": "*", "optional": True}) ) - django_anymail.extras[canonicalize_name("amazon_ses")] = [ - Factory.create_dependency("boto3", "*") - ] + django_anymail.extras = { + canonicalize_name("amazon_ses"): [Factory.create_dependency("boto3", "*")] + } django = get_package("django", "2.2.0") boto3 = get_package("boto3", "1.0.0") requests = get_package("requests", "2.24.0")