Skip to content

Commit

Permalink
treat collection types in package as immutable
Browse files Browse the repository at this point in the history
  • Loading branch information
dimbleby committed Jan 21, 2024
1 parent 50a7723 commit a93aed2
Show file tree
Hide file tree
Showing 7 changed files with 40 additions and 27 deletions.
14 changes: 10 additions & 4 deletions src/poetry/inspection/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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


Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -214,19 +218,21 @@ 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)

if req not in seen_requirements:
package.add_dependency(dependency)
seen_requirements.add(req)

package.extras = package_extras

return package

@classmethod
Expand Down
9 changes: 7 additions & 2 deletions src/poetry/packages/locker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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"])
Expand Down
5 changes: 3 additions & 2 deletions src/poetry/repositories/pypi_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
Expand All @@ -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")
Expand Down
6 changes: 3 additions & 3 deletions tests/installation/test_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/packages/test_locker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down
2 changes: 1 addition & 1 deletion tests/puzzle/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
29 changes: 15 additions & 14 deletions tests/puzzle/test_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"})
)
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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})
)
Expand Down Expand Up @@ -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})
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit a93aed2

Please sign in to comment.