Skip to content

Commit 46ae4f5

Browse files
b-kamphorstneersighted
authored andcommitted
refactor: simplify Pool logic
Introduces Priority enum to help in bookkeeping of repository types. Additionally specifies parameter 'repository' to 'repository_name' where relevant.
1 parent 2d2ee7e commit 46ae4f5

File tree

7 files changed

+88
-127
lines changed

7 files changed

+88
-127
lines changed

src/poetry/puzzle/provider.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ def complete_package(
555555
package.pretty_name,
556556
package.version,
557557
extras=list(dependency.extras),
558-
repository=dependency.source_name,
558+
repository_name=dependency.source_name,
559559
),
560560
)
561561
except PackageNotFound as e:

src/poetry/repositories/legacy_repository.py

+12
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ def __init__(
3333

3434
super().__init__(name, url.rstrip("/"), config, disable_cache)
3535

36+
@property
37+
def packages(self) -> list[Package]:
38+
# LegacyRepository._packages is not populated and other implementations
39+
# implicitly rely on this (e.g. Pool.search via
40+
# LegacyRepository.search). To avoid special-casing Pool or changing
41+
# behavior, we stub and return an empty list.
42+
#
43+
# TODO: Rethinking search behaviour and design.
44+
# Ref: https://github.com/python-poetry/poetry/issues/2446 and
45+
# https://github.com/python-poetry/poetry/pull/6669#discussion_r990874908.
46+
return []
47+
3648
def package(
3749
self, name: str, version: Version, extras: list[str] | None = None
3850
) -> Package:

src/poetry/repositories/pool.py

+60-120
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
from __future__ import annotations
22

3+
import enum
4+
5+
from collections import OrderedDict
6+
from enum import IntEnum
37
from typing import TYPE_CHECKING
48

59
from poetry.repositories.exceptions import PackageNotFound
@@ -13,53 +17,60 @@
1317
from poetry.repositories.repository import Repository
1418

1519

20+
class Priority(IntEnum):
21+
# The order of the members below dictates the actual priority. The first member has
22+
# top priority.
23+
DEFAULT = enum.auto()
24+
PRIMARY = enum.auto()
25+
SECONDARY = enum.auto()
26+
27+
1628
class Pool:
1729
def __init__(
1830
self,
1931
repositories: list[Repository] | None = None,
2032
ignore_repository_names: bool = False,
2133
) -> None:
2234
self._name = "poetry-pool"
35+
self._repositories: OrderedDict[
36+
str, tuple[Repository, RepositoryPriority]
37+
] = OrderedDict()
38+
self._ignore_repository_names = ignore_repository_names
2339

2440
if repositories is None:
2541
repositories = []
26-
27-
self._lookup: dict[str, int] = {}
28-
self._repositories: list[Repository] = []
29-
self._default = False
30-
self._has_primary_repositories = False
31-
self._secondary_start_idx: int | None = None
32-
3342
for repository in repositories:
3443
self.add_repository(repository)
3544

36-
self._ignore_repository_names = ignore_repository_names
37-
3845
@property
3946
def name(self) -> str:
4047
return self._name
4148

4249
@property
4350
def repositories(self) -> list[Repository]:
44-
return self._repositories
51+
unsorted_repositories = self._repositories.values()
52+
sorted_repositories = sorted(unsorted_repositories, key=lambda p: p[1].value)
53+
return [repo for (repo, _) in sorted_repositories]
4554

4655
def has_default(self) -> bool:
47-
return self._default
56+
return self._contains_priority(Priority.DEFAULT)
4857

4958
def has_primary_repositories(self) -> bool:
50-
return self._has_primary_repositories
59+
return self._contains_priority(Priority.PRIMARY)
60+
61+
def _contains_priority(self, priority: Priority) -> bool:
62+
return any(
63+
repo_prio is priority for _, repo_prio in self._repositories.values()
64+
)
5165

5266
def has_repository(self, name: str) -> bool:
53-
return name.lower() in self._lookup
67+
return name.lower() in self._repositories
5468

5569
def repository(self, name: str) -> Repository:
5670
name = name.lower()
57-
58-
lookup = self._lookup.get(name)
59-
if lookup is not None:
60-
return self._repositories[lookup]
61-
62-
raise ValueError(f'Repository "{name}" does not exist.')
71+
if self.has_repository(name):
72+
return self._repositories[name][0]
73+
raise IndexError(f'Repository "{name}" does not exist.')
6374

6475
def add_repository(
6576
self, repository: Repository, default: bool = False, secondary: bool = False
@@ -68,130 +79,59 @@ def add_repository(
6879
Adds a repository to the pool.
6980
"""
7081
repository_name = repository.name.lower()
71-
if repository_name in self._lookup:
72-
raise ValueError(f"{repository_name} already added")
73-
74-
if default:
75-
if self.has_default():
76-
raise ValueError("Only one repository can be the default")
77-
78-
self._default = True
79-
self._repositories.insert(0, repository)
80-
for name in self._lookup:
81-
self._lookup[name] += 1
82+
if self.has_repository(repository_name):
83+
raise ValueError(
84+
f"A repository with name {repository_name} was already added."
85+
)
8286

83-
if self._secondary_start_idx is not None:
84-
self._secondary_start_idx += 1
87+
if default and self.has_default():
88+
raise ValueError("Only one repository can be the default.")
8589

86-
self._lookup[repository_name] = 0
90+
priority = Priority.PRIMARY
91+
if default:
92+
priority = Priority.DEFAULT
8793
elif secondary:
88-
if self._secondary_start_idx is None:
89-
self._secondary_start_idx = len(self._repositories)
90-
91-
self._repositories.append(repository)
92-
self._lookup[repository_name] = len(self._repositories) - 1
93-
else:
94-
self._has_primary_repositories = True
95-
if self._secondary_start_idx is None:
96-
self._repositories.append(repository)
97-
self._lookup[repository_name] = len(self._repositories) - 1
98-
else:
99-
self._repositories.insert(self._secondary_start_idx, repository)
100-
101-
for name, idx in self._lookup.items():
102-
if idx < self._secondary_start_idx:
103-
continue
104-
105-
self._lookup[name] += 1
106-
107-
self._lookup[repository_name] = self._secondary_start_idx
108-
self._secondary_start_idx += 1
109-
94+
priority = Priority.SECONDARY
95+
self._repositories[repository_name] = (repository, priority)
11096
return self
11197

112-
def remove_repository(self, repository_name: str) -> Pool:
113-
if repository_name is not None:
114-
repository_name = repository_name.lower()
115-
116-
idx = self._lookup.get(repository_name)
117-
if idx is not None:
118-
del self._repositories[idx]
119-
del self._lookup[repository_name]
120-
121-
if idx == 0:
122-
self._default = False
123-
124-
for name in self._lookup:
125-
if self._lookup[name] > idx:
126-
self._lookup[name] -= 1
127-
128-
if (
129-
self._secondary_start_idx is not None
130-
and self._secondary_start_idx > idx
131-
):
132-
self._secondary_start_idx -= 1
133-
98+
def remove_repository(self, name: str) -> Pool:
99+
if not self.has_repository(name):
100+
raise IndexError(f"Pool can not remove unknown repository '{name}'.")
101+
del self._repositories[name.lower()]
134102
return self
135103

136104
def package(
137105
self,
138106
name: str,
139107
version: Version,
140108
extras: list[str] | None = None,
141-
repository: str | None = None,
109+
repository_name: str | None = None,
142110
) -> Package:
143-
if repository is not None:
144-
repository = repository.lower()
145-
146-
if (
147-
repository is not None
148-
and repository not in self._lookup
149-
and not self._ignore_repository_names
150-
):
151-
raise ValueError(f'Repository "{repository}" does not exist.')
152-
153-
if repository is not None and not self._ignore_repository_names:
154-
return self.repository(repository).package(name, version, extras=extras)
111+
if repository_name and not self._ignore_repository_names:
112+
return self.repository(repository_name).package(
113+
name, version, extras=extras
114+
)
155115

156-
for repo in self._repositories:
116+
for repo in self.repositories:
157117
try:
158-
package = repo.package(name, version, extras=extras)
118+
return repo.package(name, version, extras=extras)
159119
except PackageNotFound:
160120
continue
161-
162-
return package
163-
164121
raise PackageNotFound(f"Package {name} ({version}) not found.")
165122

166123
def find_packages(self, dependency: Dependency) -> list[Package]:
167-
repository = dependency.source_name
168-
if repository is not None:
169-
repository = repository.lower()
170-
171-
if (
172-
repository is not None
173-
and repository not in self._lookup
174-
and not self._ignore_repository_names
175-
):
176-
raise ValueError(f'Repository "{repository}" does not exist.')
177-
178-
if repository is not None and not self._ignore_repository_names:
179-
return self.repository(repository).find_packages(dependency)
180-
181-
packages = []
182-
for repo in self._repositories:
183-
packages += repo.find_packages(dependency)
124+
repository_name = dependency.source_name
125+
if repository_name and not self._ignore_repository_names:
126+
return self.repository(repository_name).find_packages(dependency)
184127

128+
packages: list[Package] = []
129+
for repo in self.repositories:
130+
packages += repo.find_packages(dependency)
185131
return packages
186132

187133
def search(self, query: str) -> list[Package]:
188-
from poetry.repositories.legacy_repository import LegacyRepository
189-
190-
results = []
191-
for repository in self._repositories:
192-
if isinstance(repository, LegacyRepository):
193-
continue
194-
134+
results: list[Package] = []
135+
for repository in self.repositories:
195136
results += repository.search(query)
196-
197137
return results

tests/console/commands/test_add.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ def test_add_constraint_with_source(
858858
def test_add_constraint_with_source_that_does_not_exist(
859859
app: PoetryTestApplication, tester: CommandTester
860860
):
861-
with pytest.raises(ValueError) as e:
861+
with pytest.raises(IndexError) as e:
862862
tester.execute("foo --source i-dont-exist")
863863

864864
assert str(e.value) == 'Repository "i-dont-exist" does not exist.'
@@ -1848,7 +1848,7 @@ def test_add_constraint_with_source_old_installer(
18481848
def test_add_constraint_with_source_that_does_not_exist_old_installer(
18491849
app: PoetryTestApplication, old_tester: CommandTester
18501850
):
1851-
with pytest.raises(ValueError) as e:
1851+
with pytest.raises(IndexError) as e:
18521852
old_tester.execute("foo --source i-dont-exist")
18531853

18541854
assert str(e.value) == 'Repository "i-dont-exist" does not exist.'

tests/repositories/test_legacy_repository.py

+7
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ def _download(self, url: str, dest: Path) -> None:
6363
shutil.copyfile(str(filepath), dest)
6464

6565

66+
def test_packages_property_returns_empty_list() -> None:
67+
repo = MockRepository()
68+
repo._packages = [repo.package("jupyter", Version.parse("1.0.0"))]
69+
70+
assert repo.packages == []
71+
72+
6673
def test_page_relative_links_path_are_correct() -> None:
6774
repo = MockRepository()
6875

tests/repositories/test_pool.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def test_pool_with_initial_repositories() -> None:
3232
def test_repository_no_repository() -> None:
3333
pool = Pool()
3434

35-
with pytest.raises(ValueError):
35+
with pytest.raises(IndexError):
3636
pool.repository("foo")
3737

3838

@@ -178,7 +178,9 @@ def test_pool_get_package_in_specified_repository() -> None:
178178
repo2 = Repository("repo2", [package])
179179
pool = Pool([repo1, repo2])
180180

181-
returned_package = pool.package("foo", Version.parse("1.0.0"), repository="repo2")
181+
returned_package = pool.package(
182+
"foo", Version.parse("1.0.0"), repository_name="repo2"
183+
)
182184

183185
assert returned_package == package
184186

@@ -198,7 +200,7 @@ def test_pool_no_package_from_specified_repository_raises_package_not_found() ->
198200
pool = Pool([repo1, repo2])
199201

200202
with pytest.raises(PackageNotFound):
201-
pool.package("foo", Version.parse("1.0.0"), repository="repo1")
203+
pool.package("foo", Version.parse("1.0.0"), repository_name="repo1")
202204

203205

204206
def test_pool_find_packages_in_any_repository() -> None:

tests/test_factory.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ def test_poetry_with_two_default_sources(with_simple_keyring: None):
299299
with pytest.raises(ValueError) as e:
300300
Factory().create_poetry(fixtures_dir / "with_two_default_sources")
301301

302-
assert str(e.value) == "Only one repository can be the default"
302+
assert str(e.value) == "Only one repository can be the default."
303303

304304

305305
def test_validate():

0 commit comments

Comments
 (0)