Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor repositories pool #6669

Merged
merged 6 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/poetry/puzzle/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ def complete_package(
package.pretty_name,
package.version,
extras=list(dependency.extras),
repository=dependency.source_name,
repository_name=dependency.source_name,
),
)
except PackageNotFound as e:
Expand Down
37 changes: 37 additions & 0 deletions src/poetry/repositories/abstract_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import annotations

from abc import ABC
from abc import abstractmethod
from typing import TYPE_CHECKING


if TYPE_CHECKING:
from poetry.core.constraints.version import Version
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package


class AbstractRepository(ABC):
def __init__(self, name: str) -> None:
self._name = name

@property
def name(self) -> str:
return self._name

@abstractmethod
def find_packages(self, dependency: Dependency) -> list[Package]:
...

@abstractmethod
def search(self, query: str) -> list[Package]:
...

@abstractmethod
def package(
self,
name: str,
version: Version,
extras: list[str] | None = None,
) -> Package:
...
2 changes: 1 addition & 1 deletion src/poetry/repositories/cached.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def __init__(
def _get_release_info(
self, name: NormalizedName, version: Version
) -> dict[str, Any]:
raise NotImplementedError()
...

def get_release_info(self, name: NormalizedName, version: Version) -> PackageInfo:
"""
Expand Down
3 changes: 1 addition & 2 deletions src/poetry/repositories/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import urllib
import urllib.parse

from abc import ABC
from collections import defaultdict
from pathlib import Path
from typing import TYPE_CHECKING
Expand Down Expand Up @@ -35,7 +34,7 @@
from poetry.utils.authenticator import RepositoryCertificateConfig


class HTTPRepository(CachedRepository, ABC):
class HTTPRepository(CachedRepository):
def __init__(
self,
name: str,
Expand Down
12 changes: 12 additions & 0 deletions src/poetry/repositories/legacy_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ def __init__(

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

@property
def packages(self) -> list[Package]:
# LegacyRepository._packages is not populated and other implementations
# implicitly rely on this (e.g. Pool.search via
# LegacyRepository.search). To avoid special-casing Pool or changing
# behavior, we stub and return an empty list.
#
# TODO: Rethinking search behaviour and design.
# Ref: https://github.com/python-poetry/poetry/issues/2446 and
# https://github.com/python-poetry/poetry/pull/6669#discussion_r990874908.
return []
b-kamphorst marked this conversation as resolved.
Show resolved Hide resolved

def package(
self, name: str, version: Version, extras: list[str] | None = None
) -> Package:
Expand Down
198 changes: 73 additions & 125 deletions src/poetry/repositories/pool.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,80 @@
from __future__ import annotations

import enum

from collections import OrderedDict
from dataclasses import dataclass
from enum import IntEnum
from typing import TYPE_CHECKING

from poetry.repositories.abstract_repository import AbstractRepository
from poetry.repositories.exceptions import PackageNotFound
from poetry.repositories.repository import Repository


if TYPE_CHECKING:
from poetry.core.constraints.version import Version
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package

from poetry.repositories.repository import Repository


class Priority(IntEnum):
# The order of the members below dictates the actual priority. The first member has
# top priority.
DEFAULT = enum.auto()
PRIMARY = enum.auto()
SECONDARY = enum.auto()


@dataclass(frozen=True)
class PrioritizedRepository:
repository: Repository
priority: Priority

class Pool(Repository):

class Pool(AbstractRepository):
neersighted marked this conversation as resolved.
Show resolved Hide resolved
def __init__(
self,
repositories: list[Repository] | None = None,
ignore_repository_names: bool = False,
) -> None:
super().__init__("poetry-pool")
self._repositories: OrderedDict[str, PrioritizedRepository] = OrderedDict()
self._ignore_repository_names = ignore_repository_names

if repositories is None:
repositories = []

self._lookup: dict[str, int] = {}
self._repositories: list[Repository] = []
self._default = False
self._has_primary_repositories = False
self._secondary_start_idx: int | None = None

for repository in repositories:
self.add_repository(repository)

self._ignore_repository_names = ignore_repository_names

@property
def repositories(self) -> list[Repository]:
return self._repositories
unsorted_repositories = self._repositories.values()
sorted_repositories = sorted(
unsorted_repositories, key=lambda prio_repo: prio_repo.priority
)
return [prio_repo.repository for prio_repo in sorted_repositories]

def has_default(self) -> bool:
return self._default
return self._contains_priority(Priority.DEFAULT)

def has_primary_repositories(self) -> bool:
return self._has_primary_repositories
return self._contains_priority(Priority.PRIMARY)

def _contains_priority(self, priority: Priority) -> bool:
return any(
prio_repo.priority is priority for prio_repo in self._repositories.values()
)

def has_repository(self, name: str) -> bool:
return name.lower() in self._lookup
return name.lower() in self._repositories
neersighted marked this conversation as resolved.
Show resolved Hide resolved

def repository(self, name: str) -> Repository:
name = name.lower()

lookup = self._lookup.get(name)
if lookup is not None:
return self._repositories[lookup]

raise ValueError(f'Repository "{name}" does not exist.')
if self.has_repository(name):
return self._repositories[name].repository
raise IndexError(f'Repository "{name}" does not exist.')

def add_repository(
self, repository: Repository, default: bool = False, secondary: bool = False
Expand All @@ -63,133 +83,61 @@ def add_repository(
Adds a repository to the pool.
"""
repository_name = repository.name.lower()
if repository_name in self._lookup:
raise ValueError(f"{repository_name} already added")

if default:
if self.has_default():
raise ValueError("Only one repository can be the default")

self._default = True
self._repositories.insert(0, repository)
for name in self._lookup:
self._lookup[name] += 1
if self.has_repository(repository_name):
raise ValueError(
f"A repository with name {repository_name} was already added."
)

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

self._lookup[repository_name] = 0
priority = Priority.PRIMARY
if default:
priority = Priority.DEFAULT
elif secondary:
if self._secondary_start_idx is None:
self._secondary_start_idx = len(self._repositories)

self._repositories.append(repository)
self._lookup[repository_name] = len(self._repositories) - 1
else:
self._has_primary_repositories = True
if self._secondary_start_idx is None:
self._repositories.append(repository)
self._lookup[repository_name] = len(self._repositories) - 1
else:
self._repositories.insert(self._secondary_start_idx, repository)

for name, idx in self._lookup.items():
if idx < self._secondary_start_idx:
continue

self._lookup[name] += 1

self._lookup[repository_name] = self._secondary_start_idx
self._secondary_start_idx += 1

priority = Priority.SECONDARY
self._repositories[repository_name] = PrioritizedRepository(
repository, priority
)
return self

def remove_repository(self, repository_name: str) -> Pool:
if repository_name is not None:
repository_name = repository_name.lower()

idx = self._lookup.get(repository_name)
if idx is not None:
del self._repositories[idx]
del self._lookup[repository_name]

if idx == 0:
self._default = False

for name in self._lookup:
if self._lookup[name] > idx:
self._lookup[name] -= 1

if (
self._secondary_start_idx is not None
and self._secondary_start_idx > idx
):
self._secondary_start_idx -= 1

def remove_repository(self, name: str) -> Pool:
if not self.has_repository(name):
raise IndexError(f"Pool can not remove unknown repository '{name}'.")
del self._repositories[name.lower()]
return self

def has_package(self, package: Package) -> bool:
raise NotImplementedError()

def package(
self,
name: str,
version: Version,
extras: list[str] | None = None,
repository: str | None = None,
repository_name: str | None = None,
) -> Package:
if repository is not None:
repository = repository.lower()

if (
repository is not None
and repository not in self._lookup
and not self._ignore_repository_names
):
raise ValueError(f'Repository "{repository}" does not exist.')
if repository_name and not self._ignore_repository_names:
return self.repository(repository_name).package(
name, version, extras=extras
)

if repository is not None and not self._ignore_repository_names:
return self.repository(repository).package(name, version, extras=extras)

for repo in self._repositories:
for repo in self.repositories:
try:
package = repo.package(name, version, extras=extras)
return repo.package(name, version, extras=extras)
except PackageNotFound:
continue

return package

raise PackageNotFound(f"Package {name} ({version}) not found.")

def find_packages(self, dependency: Dependency) -> list[Package]:
repository = dependency.source_name
if repository is not None:
repository = repository.lower()

if (
repository is not None
and repository not in self._lookup
and not self._ignore_repository_names
):
raise ValueError(f'Repository "{repository}" does not exist.')

if repository is not None and not self._ignore_repository_names:
return self.repository(repository).find_packages(dependency)

packages = []
for repo in self._repositories:
packages += repo.find_packages(dependency)
repository_name = dependency.source_name
if repository_name and not self._ignore_repository_names:
return self.repository(repository_name).find_packages(dependency)

packages: list[Package] = []
for repo in self.repositories:
packages += repo.find_packages(dependency)
return packages

def search(self, query: str) -> list[Package]:
from poetry.repositories.legacy_repository import LegacyRepository

results = []
for repository in self._repositories:
if isinstance(repository, LegacyRepository):
continue

results: list[Package] = []
for repository in self.repositories:
results += repository.search(query)

return results
9 changes: 3 additions & 6 deletions src/poetry/repositories/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from poetry.core.constraints.version import Version
from poetry.core.constraints.version import VersionRange

from poetry.repositories.abstract_repository import AbstractRepository
from poetry.repositories.exceptions import PackageNotFound


Expand All @@ -19,18 +20,14 @@
from poetry.core.packages.utils.link import Link


class Repository:
class Repository(AbstractRepository):
def __init__(self, name: str, packages: list[Package] | None = None) -> None:
self._name = name
super().__init__(name)
self._packages: list[Package] = []

for package in packages or []:
self.add_package(package)

@property
def name(self) -> str:
return self._name

@property
def packages(self) -> list[Package]:
return self._packages
Expand Down
Loading