Skip to content

Commit

Permalink
introduce sync command
Browse files Browse the repository at this point in the history
  • Loading branch information
radoering committed Dec 14, 2024
1 parent 21e30b1 commit d6b43ff
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 7 deletions.
15 changes: 15 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,21 @@ When `--only` is specified, `--with` and `--without` options are ignored.
{{% /note %}}


## sync

The `sync` command makes sure that the project's environment is in sync with the `poetry.lock` file.
It is equivalent to running `poetry install --sync` and provides the same options
(except for `--sync`) as [install]({{< relref "#install" >}}).

{{% note %}}
Normally, you should prefer `poetry sync` to `poetry install` to avoid untracked outdated packages.
However, if you have set `virtualenvs.create = false` to install dependencies into your system environment,
which is discouraged, or `virtualenvs.options.system-site-packages = true` to make
system site-packages available in your virtual environment, you should use `poetry install`
because `poetry sync` will normally not work well in these cases.
{{% /note %}}


## update

In order to get the latest versions of the dependencies and to update the `poetry.lock` file,
Expand Down
1 change: 1 addition & 0 deletions src/poetry/console/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def _load() -> Command:
"run",
"search",
"show",
"sync",
"update",
"version",
# Cache commands
Expand Down
36 changes: 36 additions & 0 deletions src/poetry/console/commands/sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import ClassVar

from poetry.console.commands.install import InstallCommand


if TYPE_CHECKING:
from cleo.io.inputs.option import Option


class SyncCommand(InstallCommand):
name = "sync"
description = "Update the project's environment according to the lockfile."

options: ClassVar[list[Option]] = [
opt for opt in InstallCommand.options if opt.name != "sync"
]

help = """\
The <info>sync</info> command makes sure that the project's environment is in sync with
the <comment>poetry.lock</> file.
It is equivalent to running <info>poetry install --sync</info>.
<info>poetry sync</info>
By default, the above command will also install the current project. To install only the
dependencies and not including the current project, run the command with the
<info>--no-root</info> option like below:
<info> poetry sync --no-root</info>
If you want to use Poetry only for dependency management but not for packaging,
you can set the "package-mode" to false in your pyproject.toml file.
"""
24 changes: 17 additions & 7 deletions tests/console/commands/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,21 @@
"""


@pytest.fixture
def command() -> str:
return "install"


@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, poetry: Poetry
command_tester_factory: CommandTesterFactory, command: str, poetry: Poetry
) -> CommandTester:
return command_tester_factory("install")
return command_tester_factory(command)


def _project_factory(
Expand Down Expand Up @@ -443,6 +448,7 @@ def test_install_logs_output_decorated(
@pytest.mark.parametrize("error", ["module", "readme", ""])
def test_install_warning_corrupt_root(
command_tester_factory: CommandTesterFactory,
command: str,
project_factory: ProjectFactory,
with_root: bool,
error: str,
Expand All @@ -461,7 +467,7 @@ def test_install_warning_corrupt_root(
if error != "module":
(poetry.pyproject_path.parent / f"{name}.py").touch()

tester = command_tester_factory("install", poetry=poetry)
tester = command_tester_factory(command, poetry=poetry)
tester.execute("" if with_root else "--no-root")

if error and with_root:
Expand All @@ -481,6 +487,7 @@ def test_install_warning_corrupt_root(
)
def test_install_path_dependency_does_not_exist(
command_tester_factory: CommandTesterFactory,
command: str,
project_factory: ProjectFactory,
fixture_dir: FixtureDirGetter,
project: str,
Expand All @@ -489,7 +496,7 @@ def test_install_path_dependency_does_not_exist(
poetry = _project_factory(project, project_factory, fixture_dir)
assert isinstance(poetry.locker, TestLocker)
poetry.locker.locked(True)
tester = command_tester_factory("install", poetry=poetry)
tester = command_tester_factory(command, poetry=poetry)
if options:
tester.execute(options)
else:
Expand All @@ -500,6 +507,7 @@ def test_install_path_dependency_does_not_exist(
@pytest.mark.parametrize("options", ["", "--extras notinstallable"])
def test_install_extra_path_dependency_does_not_exist(
command_tester_factory: CommandTesterFactory,
command: str,
project_factory: ProjectFactory,
fixture_dir: FixtureDirGetter,
options: str,
Expand All @@ -508,7 +516,7 @@ def test_install_extra_path_dependency_does_not_exist(
poetry = _project_factory(project, project_factory, fixture_dir)
assert isinstance(poetry.locker, TestLocker)
poetry.locker.locked(True)
tester = command_tester_factory("install", poetry=poetry)
tester = command_tester_factory(command, poetry=poetry)
if not options:
tester.execute(options)
else:
Expand All @@ -519,6 +527,7 @@ def test_install_extra_path_dependency_does_not_exist(
@pytest.mark.parametrize("options", ["", "--no-directory"])
def test_install_missing_directory_dependency_with_no_directory(
command_tester_factory: CommandTesterFactory,
command: str,
project_factory: ProjectFactory,
fixture_dir: FixtureDirGetter,
options: str,
Expand All @@ -528,7 +537,7 @@ def test_install_missing_directory_dependency_with_no_directory(
)
assert isinstance(poetry.locker, TestLocker)
poetry.locker.locked(True)
tester = command_tester_factory("install", poetry=poetry)
tester = command_tester_factory(command, poetry=poetry)
if options:
tester.execute(options)
else:
Expand All @@ -538,6 +547,7 @@ def test_install_missing_directory_dependency_with_no_directory(

def test_non_package_mode_does_not_try_to_install_root(
command_tester_factory: CommandTesterFactory,
command: str,
project_factory: ProjectFactory,
) -> None:
content = """\
Expand All @@ -546,7 +556,7 @@ def test_non_package_mode_does_not_try_to_install_root(
"""
poetry = project_factory(name="non-package-mode", pyproject_content=content)

tester = command_tester_factory("install", poetry=poetry)
tester = command_tester_factory(command, poetry=poetry)
tester.execute()

assert tester.status_code == 0
Expand Down
30 changes: 30 additions & 0 deletions tests/console/commands/test_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from cleo.exceptions import CleoNoSuchOptionError

# import all tests from the install command
# and run them for sync by overriding the command fixture
from tests.console.commands.test_install import * # noqa: F403


if TYPE_CHECKING:
from cleo.testers.command_tester import CommandTester


@pytest.fixture # type: ignore[no-redef]
def command() -> str:
return "sync"


@pytest.mark.skip("Only relevant for `poetry install`") # type: ignore[no-redef]
def test_sync_option_is_passed_to_the_installer() -> None:
"""The only test from the install command that does not work for sync."""


def test_sync_option_not_available(tester: CommandTester) -> None:
with pytest.raises(CleoNoSuchOptionError):
tester.execute("--sync")

0 comments on commit d6b43ff

Please sign in to comment.