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

feat [WIP]: add strategy=lowest #9631

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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 pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "poetry"
version = "1.9.0.dev0"
version = "1.9.0.dev1"
description = "Python dependency management and packaging made easy."
authors = ["Sébastien Eustace <sebastien@eustace.io>"]
maintainers = [
Expand Down
18 changes: 18 additions & 0 deletions src/poetry/console/commands/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from cleo.helpers import option

from poetry.console.commands.installer_command import InstallerCommand
from poetry.installation import Strategy


if TYPE_CHECKING:
Expand All @@ -27,6 +28,14 @@ class LockCommand(InstallerCommand):
" version of <comment>pyproject.toml</>. (<warning>Deprecated</>) Use"
" <comment>poetry check --lock</> instead.",
),
option(
"strategy",
None,
"Lock dependencies using a dependency resolution strategy.",
value_required=True,
default="latest",
flag=False,
),
]

help = """
Expand Down Expand Up @@ -57,6 +66,15 @@ def handle(self) -> int:
)
return 1

if strategy := self.option("strategy"):
if strategy not in [s.value for s in Strategy]:
self.line_error(
f"<error> Invalid strategy '{strategy}'. Valid strategies are: "
f"{', '.join([s.value for s in Strategy])}"
"</error>"
)
return 1
self.installer.strategy = strategy
self.installer.lock(update=not self.option("no-update"))

return self.installer.run()
3 changes: 2 additions & 1 deletion src/poetry/installation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from poetry.installation.installer import Installer
from poetry.installation.strategy import Strategy


__all__ = ["Installer"]
__all__ = ["Installer", "Strategy"]
9 changes: 7 additions & 2 deletions src/poetry/installation/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from poetry.installation.executor import Executor
from poetry.installation.operations import Uninstall
from poetry.installation.operations import Update
from poetry.installation.strategy import Strategy
from poetry.repositories import Repository
from poetry.repositories import RepositoryPool
from poetry.repositories.installed_repository import InstalledRepository
Expand All @@ -20,7 +21,6 @@

from cleo.io.io import IO
from packaging.utils import NormalizedName
from poetry.core.packages.path_dependency import PathDependency
from poetry.core.packages.project_package import ProjectPackage

from poetry.config.config import Config
Expand All @@ -41,13 +41,15 @@ def __init__(
installed: Repository | None = None,
executor: Executor | None = None,
disable_cache: bool = False,
strategy: Strategy = Strategy.LATEST,
) -> None:
self._io = io
self._env = env
self._package = package
self._locker = locker
self._pool = pool
self._config = config
self.strategy = strategy

self._dry_run = False
self._requires_synchronization = False
Expand Down Expand Up @@ -185,6 +187,7 @@ def _do_refresh(self) -> int:
locked_repository.packages,
locked_repository.packages,
self._io,
Strategy.is_using_lowest(self.strategy),
)

# Always re-solve directory dependencies, otherwise we can't determine
Expand Down Expand Up @@ -231,6 +234,7 @@ def _do_install(self) -> int:
self._installed_repository.packages,
locked_repository.packages,
self._io,
Strategy.is_using_lowest(self.strategy),
)

with solver.provider.use_source_root(
Expand Down Expand Up @@ -287,6 +291,7 @@ def _do_install(self) -> int:
self._installed_repository.packages,
locked_repository.packages,
NullIO(),
Strategy.is_using_lowest(self.strategy),
)
# Everything is resolved at this point, so we no longer need
# to load deferred dependencies (i.e. VCS, URL and path dependencies)
Expand All @@ -304,7 +309,7 @@ def _do_install(self) -> int:
for op in ops:
dep = op.package.to_dependency()
if dep.is_file() or dep.is_directory():
dep = cast("PathDependency", dep)
dep = cast("PathDependency", dep) # noqa: F821
dep.validate(raise_error=not op.skipped)

# Execute operations
Expand Down
15 changes: 15 additions & 0 deletions src/poetry/installation/strategy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import annotations

from enum import Enum


class Strategy(Enum):
LATEST = "latest"
LOWEST = "lowest"

@classmethod
def is_using_lowest(cls, other: Strategy | str) -> bool:
if isinstance(other, cls):
return other == cls.LOWEST
else:
return other == "lowest"
5 changes: 4 additions & 1 deletion src/poetry/puzzle/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ def __init__(
*,
installed: list[Package] | None = None,
locked: list[Package] | None = None,
use_lowest: bool = False,
) -> None:
self._package = package
self._pool = pool
Expand All @@ -133,16 +134,18 @@ def __init__(
self._direct_origin_packages: dict[str, Package] = {}
self._locked: dict[NormalizedName, list[DependencyPackage]] = defaultdict(list)
self._use_latest: Collection[NormalizedName] = []
self._use_lowest = use_lowest

self._explicit_sources: dict[str, str] = {}
for package in locked or []:
self._locked[package.name].append(
DependencyPackage(package.to_dependency(), package)
)
for dependency_packages in self._locked.values():
# prioritize resolving by lowest versions if use_lowest is True
dependency_packages.sort(
key=lambda p: p.package.version,
reverse=True,
reverse=not use_lowest,
)

@property
Expand Down
9 changes: 8 additions & 1 deletion src/poetry/puzzle/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,22 @@ def __init__(
installed: list[Package],
locked: list[Package],
io: IO,
use_lowest: bool = False,
) -> None:
self._package = package
self._pool = pool
self._installed_packages = installed
self._locked_packages = locked
self._io = io
self._lowest = use_lowest

self._provider = Provider(
self._package, self._pool, self._io, installed=installed, locked=locked
self._package,
self._pool,
self._io,
installed=installed,
locked=locked,
use_lowest=self._lowest,
)
self._overrides: list[dict[Package, dict[str, Dependency]]] = []

Expand Down
15 changes: 15 additions & 0 deletions tests/installation/test_strategy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import annotations

import pytest

from poetry.installation import Strategy


@pytest.mark.parametrize("strategy", ["lowest", Strategy.LOWEST])
def test_is_using_lowest_true(strategy):
assert Strategy.is_using_lowest(strategy)


@pytest.mark.parametrize("strategy", ["latest", Strategy.LATEST])
def test_is_using_lowest_false(strategy):
assert not Strategy.is_using_lowest(strategy)
Loading