Skip to content

Commit eb74d62

Browse files
radoeringdimbleby
andauthored
installer: respect source if the same version of a package has been locked from different sources (#8304)
Co-authored-by: David Hotham <[email protected]>
1 parent 36332d2 commit eb74d62

File tree

7 files changed

+226
-36
lines changed

7 files changed

+226
-36
lines changed

src/poetry/console/commands/show.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,7 @@ def _display_packages_information(
211211
from poetry.utils.helpers import get_package_version_display_string
212212

213213
locked_packages = locked_repository.packages
214-
pool = RepositoryPool(ignore_repository_names=True, config=self.poetry.config)
215-
pool.add_repository(locked_repository)
214+
pool = RepositoryPool.from_packages(locked_packages, self.poetry.config)
216215
solver = Solver(
217216
root,
218217
pool=pool,

src/poetry/installation/installer.py

+2-10
Original file line numberDiff line numberDiff line change
@@ -286,16 +286,8 @@ def _do_install(self) -> int:
286286
)
287287

288288
# We resolve again by only using the lock file
289-
pool = RepositoryPool(ignore_repository_names=True, config=self._config)
290-
291-
# Making a new repo containing the packages
292-
# newly resolved and the ones from the current lock file
293-
repo = Repository("poetry-repo")
294-
for package in lockfile_repo.packages + locked_repository.packages:
295-
if not package.is_direct_origin() and not repo.has_package(package):
296-
repo.add_package(package)
297-
298-
pool.add_repository(repo)
289+
packages = lockfile_repo.packages + locked_repository.packages
290+
pool = RepositoryPool.from_packages(packages, self._config)
299291

300292
solver = Solver(
301293
root,

src/poetry/repositories/repository_pool.py

+33-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from poetry.config.config import Config
1212
from poetry.repositories.abstract_repository import AbstractRepository
1313
from poetry.repositories.exceptions import PackageNotFound
14+
from poetry.repositories.repository import Repository
1415
from poetry.utils.cache import ArtifactCache
1516

1617

@@ -19,7 +20,7 @@
1920
from poetry.core.packages.dependency import Dependency
2021
from poetry.core.packages.package import Package
2122

22-
from poetry.repositories.repository import Repository
23+
_SENTINEL = object()
2324

2425

2526
class Priority(IntEnum):
@@ -42,13 +43,12 @@ class RepositoryPool(AbstractRepository):
4243
def __init__(
4344
self,
4445
repositories: list[Repository] | None = None,
45-
ignore_repository_names: bool = False,
46+
ignore_repository_names: object = _SENTINEL,
4647
*,
4748
config: Config | None = None,
4849
) -> None:
4950
super().__init__("poetry-repository-pool")
5051
self._repositories: OrderedDict[str, PrioritizedRepository] = OrderedDict()
51-
self._ignore_repository_names = ignore_repository_names
5252

5353
if repositories is None:
5454
repositories = []
@@ -59,6 +59,34 @@ def __init__(
5959
cache_dir=(config or Config.create()).artifacts_cache_directory
6060
)
6161

62+
if ignore_repository_names is not _SENTINEL:
63+
warnings.warn(
64+
"The 'ignore_repository_names' argument to 'RepositoryPool.__init__' is"
65+
" deprecated. It has no effect anymore and will be removed in a future"
66+
" version.",
67+
DeprecationWarning,
68+
stacklevel=2,
69+
)
70+
71+
@staticmethod
72+
def from_packages(packages: list[Package], config: Config | None) -> RepositoryPool:
73+
pool = RepositoryPool(config=config)
74+
for package in packages:
75+
if package.is_direct_origin():
76+
continue
77+
78+
repo_name = package.source_reference or "PyPI"
79+
try:
80+
repo = pool.repository(repo_name)
81+
except IndexError:
82+
repo = Repository(repo_name)
83+
pool.add_repository(repo)
84+
85+
if not repo.has_package(package):
86+
repo.add_package(package)
87+
88+
return pool
89+
6290
@property
6391
def repositories(self) -> list[Repository]:
6492
"""
@@ -166,7 +194,7 @@ def package(
166194
extras: list[str] | None = None,
167195
repository_name: str | None = None,
168196
) -> Package:
169-
if repository_name and not self._ignore_repository_names:
197+
if repository_name:
170198
return self.repository(repository_name).package(
171199
name, version, extras=extras
172200
)
@@ -180,7 +208,7 @@ def package(
180208

181209
def find_packages(self, dependency: Dependency) -> list[Package]:
182210
repository_name = dependency.source_name
183-
if repository_name and not self._ignore_repository_names:
211+
if repository_name:
184212
return self.repository(repository_name).find_packages(dependency)
185213

186214
packages: list[Package] = []

src/poetry/utils/env/mock_env.py

+8
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ class MockEnv(NullEnv):
1616
def __init__(
1717
self,
1818
version_info: tuple[int, int, int] = (3, 7, 0),
19+
*,
1920
python_implementation: str = "CPython",
2021
platform: str = "darwin",
22+
platform_machine: str = "amd64",
2123
os_name: str = "posix",
2224
is_venv: bool = False,
2325
pip_version: str = "19.1",
@@ -31,6 +33,7 @@ def __init__(
3133
self._version_info = version_info
3234
self._python_implementation = python_implementation
3335
self._platform = platform
36+
self._platform_machine = platform_machine
3437
self._os_name = os_name
3538
self._is_venv = is_venv
3639
self._pip_version: Version = Version.parse(pip_version)
@@ -42,6 +45,10 @@ def __init__(
4245
def platform(self) -> str:
4346
return self._platform
4447

48+
@property
49+
def platform_machine(self) -> str:
50+
return self._platform_machine
51+
4552
@property
4653
def os(self) -> str:
4754
return self._os_name
@@ -67,6 +74,7 @@ def get_marker_env(self) -> dict[str, Any]:
6774
marker_env["python_version"] = ".".join(str(v) for v in self._version_info[:2])
6875
marker_env["python_full_version"] = ".".join(str(v) for v in self._version_info)
6976
marker_env["sys_platform"] = self._platform
77+
marker_env["platform_machine"] = self._platform_machine
7078
marker_env["interpreter_name"] = self._python_implementation.lower()
7179
marker_env["interpreter_version"] = "cp" + "".join(
7280
str(v) for v in self._version_info[:2]

tests/console/commands/test_add.py

+9-14
Original file line numberDiff line numberDiff line change
@@ -902,21 +902,16 @@ def test_add_constraint_with_source(
902902
mocker: MockerFixture,
903903
) -> None:
904904
repo = LegacyRepository(name="my-index", url="https://my-index.fake")
905-
repo.add_package(get_package("cachy", "0.2.0"))
906-
mocker.patch.object(
907-
repo,
908-
"_find_packages",
909-
wraps=lambda _, name: [
910-
Package(
911-
"cachy",
912-
Version.parse("0.2.0"),
913-
source_type="legacy",
914-
source_reference=repo.name,
915-
source_url=repo._url,
916-
yanked=False,
917-
)
918-
],
905+
package = Package(
906+
"cachy",
907+
Version.parse("0.2.0"),
908+
source_type="legacy",
909+
source_reference=repo.name,
910+
source_url=repo._url,
911+
yanked=False,
919912
)
913+
mocker.patch.object(repo, "package", return_value=package)
914+
mocker.patch.object(repo, "_find_packages", wraps=lambda _, name: [package])
920915

921916
poetry.pool.add_repository(repo)
922917

tests/installation/test_installer.py

+162-5
Original file line numberDiff line numberDiff line change
@@ -2558,9 +2558,8 @@ def test_installer_should_use_the_locked_version_of_git_dependencies_without_ref
25582558
)
25592559

25602560

2561-
# https://github.com/python-poetry/poetry/issues/6710
25622561
@pytest.mark.parametrize("env_platform", ["darwin", "linux"])
2563-
def test_installer_distinguishes_locked_packages_by_source(
2562+
def test_installer_distinguishes_locked_packages_with_local_version_by_source(
25642563
pool: RepositoryPool,
25652564
locker: Locker,
25662565
installed: CustomInstalledRepository,
@@ -2569,6 +2568,7 @@ def test_installer_distinguishes_locked_packages_by_source(
25692568
package: ProjectPackage,
25702569
env_platform: str,
25712570
) -> None:
2571+
"""https://github.com/python-poetry/poetry/issues/6710"""
25722572
# Require 1.11.0+cpu from pytorch for most platforms, but specify 1.11.0 and pypi on
25732573
# darwin.
25742574
package.add_dependency(
@@ -2661,6 +2661,110 @@ def test_installer_distinguishes_locked_packages_by_source(
26612661
)
26622662

26632663

2664+
@pytest.mark.parametrize("env_platform_machine", ["aarch64", "amd64"])
2665+
def test_installer_distinguishes_locked_packages_with_same_version_by_source(
2666+
pool: RepositoryPool,
2667+
locker: Locker,
2668+
installed: CustomInstalledRepository,
2669+
config: Config,
2670+
repo: Repository,
2671+
package: ProjectPackage,
2672+
env_platform_machine: str,
2673+
) -> None:
2674+
"""https://github.com/python-poetry/poetry/issues/8303"""
2675+
package.add_dependency(
2676+
Factory.create_dependency(
2677+
"kivy",
2678+
{
2679+
"version": "2.2.1",
2680+
"markers": "platform_machine == 'aarch64'",
2681+
"source": "pywheels",
2682+
},
2683+
)
2684+
)
2685+
package.add_dependency(
2686+
Factory.create_dependency(
2687+
"kivy",
2688+
{
2689+
"version": "2.2.1",
2690+
"markers": "platform_machine != 'aarch64'",
2691+
"source": "PyPI",
2692+
},
2693+
)
2694+
)
2695+
2696+
# Locking finds both the pypi and the pyhweels packages.
2697+
locker.locked(True)
2698+
locker.mock_lock_data(
2699+
{
2700+
"package": [
2701+
{
2702+
"name": "kivy",
2703+
"version": "2.2.1",
2704+
"optional": False,
2705+
"files": [],
2706+
"python-versions": "*",
2707+
},
2708+
{
2709+
"name": "kivy",
2710+
"version": "2.2.1",
2711+
"optional": False,
2712+
"files": [],
2713+
"python-versions": "*",
2714+
"source": {
2715+
"type": "legacy",
2716+
"url": "https://www.piwheels.org/simple",
2717+
"reference": "pywheels",
2718+
},
2719+
},
2720+
],
2721+
"metadata": {
2722+
"python-versions": "*",
2723+
"platform": "*",
2724+
"content-hash": "123456789",
2725+
},
2726+
}
2727+
)
2728+
installer = Installer(
2729+
NullIO(),
2730+
MockEnv(platform_machine=env_platform_machine),
2731+
package,
2732+
locker,
2733+
pool,
2734+
config,
2735+
installed=installed,
2736+
executor=Executor(
2737+
MockEnv(platform_machine=env_platform_machine),
2738+
pool,
2739+
config,
2740+
NullIO(),
2741+
),
2742+
)
2743+
result = installer.run()
2744+
assert result == 0
2745+
2746+
# Results of installation are consistent with the platform requirements.
2747+
version = "2.2.1"
2748+
if env_platform_machine == "aarch64":
2749+
source_type = "legacy"
2750+
source_url = "https://www.piwheels.org/simple"
2751+
source_reference = "pywheels"
2752+
else:
2753+
source_type = None
2754+
source_url = None
2755+
source_reference = None
2756+
2757+
assert isinstance(installer.executor, Executor)
2758+
assert len(installer.executor.installations) == 1
2759+
assert installer.executor.installations[0] == Package(
2760+
"kivy",
2761+
version,
2762+
source_type=source_type,
2763+
source_url=source_url,
2764+
source_reference=source_reference,
2765+
)
2766+
2767+
26642768
@pytest.mark.parametrize("env_platform", ["darwin", "linux"])
26652769
def test_explicit_source_dependency_with_direct_origin_dependency(
26662770
pool: RepositoryPool,
@@ -2675,12 +2779,13 @@ def test_explicit_source_dependency_with_direct_origin_dependency(
26752779
A dependency with explicit source should not be satisfied by
26762780
a direct origin dependency even if there is a version match.
26772781
"""
2782+
demo_url = "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
26782783
package.add_dependency(
26792784
Factory.create_dependency(
26802785
"demo",
26812786
{
26822787
"markers": "sys_platform != 'darwin'",
2683-
"url": "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl",
2788+
"url": demo_url,
26842789
},
26852790
)
26862791
)
@@ -2698,6 +2803,50 @@ def test_explicit_source_dependency_with_direct_origin_dependency(
26982803
repo.add_package(get_package("pendulum", "1.4.4"))
26992804
repo.add_package(get_package("demo", "0.1.0"))
27002805

2806+
# Locking finds both the direct origin and the explicit source packages.
2807+
locker.locked(True)
2808+
locker.mock_lock_data(
2809+
{
2810+
"package": [
2811+
{
2812+
"name": "demo",
2813+
"version": "0.1.0",
2814+
"optional": False,
2815+
"files": [],
2816+
"python-versions": "*",
2817+
"dependencies": {"pendulum": ">=1.4.4"},
2818+
"source": {
2819+
"type": "url",
2820+
"url": demo_url,
2821+
},
2822+
},
2823+
{
2824+
"name": "demo",
2825+
"version": "0.1.0",
2826+
"optional": False,
2827+
"files": [],
2828+
"python-versions": "*",
2829+
"source": {
2830+
"type": "legacy",
2831+
"url": "https://www.demo.org/simple",
2832+
"reference": "repo",
2833+
},
2834+
},
2835+
{
2836+
"name": "pendulum",
2837+
"version": "1.4.4",
2838+
"optional": False,
2839+
"files": [],
2840+
"python-versions": "*",
2841+
},
2842+
],
2843+
"metadata": {
2844+
"python-versions": "*",
2845+
"platform": "*",
2846+
"content-hash": "123456789",
2847+
},
2848+
}
2849+
)
27012850
installer = Installer(
27022851
NullIO(),
27032852
MockEnv(platform=env_platform),
@@ -2725,8 +2874,16 @@ def test_explicit_source_dependency_with_direct_origin_dependency(
27252874
"demo",
27262875
"0.1.0",
27272876
source_type="url",
2728-
source_url="https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl",
2877+
source_url=demo_url,
27292878
),
27302879
]
27312880
else:
2732-
assert installer.executor.installations == [Package("demo", "0.1.0")]
2881+
assert installer.executor.installations == [
2882+
Package(
2883+
"demo",
2884+
"0.1.0",
2885+
source_type="legacy",
2886+
source_url="https://www.demo.org/simple",
2887+
source_reference="repo",
2888+
)
2889+
]

0 commit comments

Comments
 (0)