Commit 375c2242 by Arun Babu Neelicattu

refactor group command logic and simplify installer

This change removed code duplication for group option handling in
commands and group activation in installer. The installer now only
allows for `only_groups` and removes `with_groups`/`without_groups`.

Additionally, this change enables code reuse for group flags in the
update command and centralises deprecated option handling logic for
these options.

Further, this fixes regression when using `update --no-dev`.
parent c52a2130
from __future__ import annotations
from typing import TYPE_CHECKING
from cleo.helpers import option
from poetry.console.commands.env_command import EnvCommand
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
from poetry.packages.project_package import ProjectPackage
class GroupCommand(EnvCommand):
@staticmethod
def _group_dependency_options() -> list[Option]:
return [
option(
"without",
None,
"The dependency groups to ignore for installation.",
flag=False,
multiple=True,
),
option(
"with",
None,
"The optional dependency groups to include for installation.",
flag=False,
multiple=True,
),
option("default", None, "Only install the default dependencies."),
option(
"only",
None,
"The only dependency groups to install.",
flag=False,
multiple=True,
),
]
@property
def non_optional_groups(self) -> set[str]:
# TODO: this should move into poetry-core
return {
group.name
for group in self.poetry.package._dependency_groups.values()
if not group.is_optional()
}
@property
def activated_groups(self) -> set[str]:
groups = {}
for key in {"with", "without", "only"}:
groups[key] = {
group.strip()
for groups in self.option(key)
for group in groups.split(",")
}
if self.option("default"):
groups["only"].add("default")
for opt, new, group in [
("no-dev", "only", "default"),
("dev", "without", "default"),
("dev-only", "without", "default"),
]:
if self.io.input.has_option(opt) and self.option(opt):
self.line_error(
f"<warning>The `<fg=yellow;options=bold>--{opt}</>` option is"
f" deprecated, use the `<fg=yellow;options=bold>--{new} {group}</>`"
" notation instead.</warning>"
)
groups[new].add(group)
return groups["only"] or self.non_optional_groups.union(
groups["with"]
).difference(groups["without"])
def project_with_activated_groups_only(self) -> ProjectPackage:
return self.poetry.package.with_dependency_groups(
list(self.activated_groups), only=True
)
......@@ -11,28 +11,7 @@ class InstallCommand(InstallerCommand):
description = "Installs the project dependencies."
options = [
option(
"without",
None,
"The dependency groups to ignore for installation.",
flag=False,
multiple=True,
),
option(
"with",
None,
"The optional dependency groups to include for installation.",
flag=False,
multiple=True,
),
option("default", None, "Only install the default dependencies."),
option(
"only",
None,
"The only dependency groups to install.",
flag=False,
multiple=True,
),
*InstallerCommand._group_dependency_options(),
option(
"no-dev",
None,
......@@ -109,49 +88,6 @@ dependencies and not including the current project, run the command with the
self._installer.extras(extras)
excluded_groups = []
included_groups = []
only_groups = []
if self.option("no-dev"):
self.line_error(
"<warning>The `<fg=yellow;options=bold>--no-dev</>` option is"
" deprecated, use the `<fg=yellow;options=bold>--without dev</>`"
" notation instead.</warning>"
)
excluded_groups.append("dev")
elif self.option("dev-only"):
self.line_error(
"<warning>The `<fg=yellow;options=bold>--dev-only</>` option is"
" deprecated, use the `<fg=yellow;options=bold>--only dev</>` notation"
" instead.</warning>"
)
only_groups.append("dev")
excluded_groups.extend(
[
group.strip()
for groups in self.option("without")
for group in groups.split(",")
]
)
included_groups.extend(
[
group.strip()
for groups in self.option("with")
for group in groups.split(",")
]
)
only_groups.extend(
[
group.strip()
for groups in self.option("only")
for group in groups.split(",")
]
)
if self.option("default"):
only_groups.append("default")
with_synchronization = self.option("sync")
if self.option("remove-untracked"):
self.line_error(
......@@ -162,9 +98,7 @@ dependencies and not including the current project, run the command with the
with_synchronization = True
self._installer.only_groups(only_groups)
self._installer.without_groups(excluded_groups)
self._installer.with_groups(included_groups)
self._installer.only_groups(self.activated_groups)
self._installer.dry_run(self.option("dry-run"))
self._installer.requires_synchronization(with_synchronization)
self._installer.verbose(self._io.is_verbose())
......
......@@ -2,14 +2,14 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.console.commands.env_command import EnvCommand
from poetry.console.commands.group_command import GroupCommand
if TYPE_CHECKING:
from poetry.installation.installer import Installer
class InstallerCommand(EnvCommand):
class InstallerCommand(GroupCommand):
def __init__(self) -> None:
# Set in poetry.console.application.Application.configure_installer
self._installer: Installer = None # type: ignore[assignment]
......
......@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
from cleo.helpers import argument
from cleo.helpers import option
from poetry.console.commands.env_command import EnvCommand
from poetry.console.commands.group_command import GroupCommand
if TYPE_CHECKING:
......@@ -18,39 +18,14 @@ if TYPE_CHECKING:
from poetry.repositories.installed_repository import InstalledRepository
class ShowCommand(EnvCommand):
class ShowCommand(GroupCommand):
name = "show"
description = "Shows information about packages."
arguments = [argument("package", "The package to inspect", optional=True)]
options = [
option(
"without",
None,
"Do not show the information of the specified groups' dependencies.",
flag=False,
multiple=True,
),
option(
"with",
None,
"Show the information of the specified optional groups' dependencies as"
" well.",
flag=False,
multiple=True,
),
option(
"default", None, "Only show the information of the default dependencies."
),
option(
"only",
None,
"Only show the information of dependencies belonging to the specified"
" groups.",
flag=False,
multiple=True,
),
*GroupCommand._group_dependency_options(),
option(
"no-dev",
None,
......@@ -93,42 +68,6 @@ lists all packages available."""
if self.option("outdated"):
self._io.input.set_option("latest", True)
excluded_groups = []
included_groups = []
only_groups = []
if self.option("no-dev"):
self.line_error(
"<warning>The `<fg=yellow;options=bold>--no-dev</>` option is"
" deprecated, use the `<fg=yellow;options=bold>--without dev</>`"
" notation instead.</warning>"
)
excluded_groups.append("dev")
excluded_groups.extend(
[
group.strip()
for groups in self.option("without")
for group in groups.split(",")
]
)
included_groups.extend(
[
group.strip()
for groups in self.option("with")
for group in groups.split(",")
]
)
only_groups.extend(
[
group.strip()
for groups in self.option("only")
for group in groups.split(",")
]
)
if self.option("default"):
only_groups.append("default")
if not self.poetry.locker.is_locked():
self.line_error(
"<error>Error: poetry.lock not found. Run `poetry lock` to create"
......@@ -137,13 +76,7 @@ lists all packages available."""
return 1
locked_repo = self.poetry.locker.locked_repository(True)
if only_groups:
root = self.poetry.package.with_dependency_groups(only_groups, only=True)
else:
root = self.poetry.package.with_dependency_groups(
included_groups
).without_dependency_groups(excluded_groups)
root = self.project_with_activated_groups_only()
# Show tree view if requested
if self.option("tree") and not package:
......
......@@ -17,7 +17,13 @@ class UpdateCommand(InstallerCommand):
argument("packages", "The packages to update", optional=True, multiple=True)
]
options = [
option("no-dev", None, "Do not update the development dependencies."),
*InstallerCommand._group_dependency_options(),
option(
"no-dev",
None,
"Do not update the development dependencies."
" (<warning>Deprecated</warning>)",
),
option(
"dry-run",
None,
......@@ -39,9 +45,7 @@ class UpdateCommand(InstallerCommand):
if packages:
self._installer.whitelist({name: "*" for name in packages})
if self.option("no-dev"):
self._installer.with_groups(["dev"])
self._installer.only_groups(self.activated_groups)
self._installer.dry_run(self.option("dry-run"))
self._installer.execute_operations(not self.option("lock"))
......
......@@ -54,9 +54,7 @@ class Installer:
self._update = False
self._verbose = False
self._write_lock = True
self._without_groups = None
self._with_groups = None
self._only_groups = None
self._groups: Iterable[str] | None = None
self._execute_operations = True
self._lock = False
......@@ -138,18 +136,8 @@ class Installer:
def is_verbose(self) -> bool:
return self._verbose
def without_groups(self, groups: list[str]) -> Installer:
self._without_groups = groups
return self
def with_groups(self, groups: list[str]) -> Installer:
self._with_groups = groups
return self
def only_groups(self, groups: list[str]) -> Installer:
self._only_groups = groups
def only_groups(self, groups: Iterable[str]) -> Installer:
self._groups = groups
return self
......@@ -281,18 +269,8 @@ class Installer:
# If we are only in lock mode, no need to go any further
return 0
if self._without_groups or self._with_groups or self._only_groups:
if self._with_groups:
# Default dependencies and opted-in optional dependencies
root = self._package.with_dependency_groups(self._with_groups)
elif self._without_groups:
# Default dependencies without selected groups
root = self._package.without_dependency_groups(self._without_groups)
else:
# Only selected groups
root = self._package.with_dependency_groups(
self._only_groups, only=True
)
if self._groups is not None:
root = self._package.with_dependency_groups(list(self._groups), only=True)
else:
root = self._package.without_optional_dependency_groups()
......
......@@ -9,11 +9,51 @@ if TYPE_CHECKING:
from cleo.testers.command_tester import CommandTester
from pytest_mock import MockerFixture
from poetry.poetry import Poetry
from tests.types import CommandTesterFactory
from tests.types import ProjectFactory
PYPROJECT_CONTENT = """\
[tool.poetry]
name = "simple-project"
version = "1.2.3"
description = "Some description."
authors = [
"Python Poetry <tests@python-poetry.org>"
]
license = "MIT"
readme = "README.rst"
[tool.poetry.dependencies]
python = "~2.7 || ^3.4"
[tool.poetry.group.foo.dependencies]
foo = "^1.0"
[tool.poetry.group.bar.dependencies]
bar = "^1.1"
[tool.poetry.group.baz.dependencies]
baz = "^1.2"
[tool.poetry.group.bim.dependencies]
bim = "^1.3"
[tool.poetry.group.bam.dependencies]
bam = "^1.4"
"""
@pytest.fixture
def poetry(project_factory: ProjectFactory) -> Poetry:
return project_factory(name="export", pyproject_content=PYPROJECT_CONTENT)
@pytest.fixture
def tester(command_tester_factory: CommandTesterFactory) -> CommandTester:
def tester(
command_tester_factory: CommandTesterFactory, poetry: Poetry
) -> CommandTester:
return command_tester_factory("install")
......@@ -27,9 +67,11 @@ def test_group_options_are_passed_to_the_installer(
tester.execute("--with foo,bar --without baz --without bim --only bam")
assert tester.command.installer._with_groups == ["foo", "bar"]
assert tester.command.installer._without_groups == ["baz", "bim"]
assert tester.command.installer._only_groups == ["bam"]
package_groups = set(tester.command.poetry.package._dependency_groups.keys())
installer_groups = set(tester.command.installer._groups)
assert installer_groups <= package_groups
assert set(installer_groups) == {"bam"}
def test_sync_option_is_passed_to_the_installer(
......
......@@ -381,7 +381,7 @@ def test_run_install_no_group(
):
_configure_run_install_dev(locker, repo, package, installed)
installer.without_groups(["dev"])
installer.only_groups([])
installer.run()
assert installer.executor.installations_count == 0
......@@ -647,7 +647,7 @@ def test_run_install_with_optional_group_selected(
locker, repo, package, installed, with_optional_group=True
)
installer.with_groups(["dev"])
installer.only_groups(["default", "dev"])
installer.run()
assert installer.executor.installations_count == 0
......
......@@ -312,7 +312,7 @@ def test_run_install_no_group(
package.add_dependency(Factory.create_dependency("B", "~1.1"))
package.add_dependency(Factory.create_dependency("C", "~1.2", groups=["dev"]))
installer.without_groups(["dev"])
installer.only_groups([])
installer.run()
installs = installer.installer.installs
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment