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

introduce sync command #9801

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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" >}}).
Comment on lines +277 to +279
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid the direct reference to poetry install here. Explanation and an options list like for other commands, would be more open-ended and wouldn't be considered "breaking" if we decide to split install and sync even more in the future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I thought that too. I have just been too lazy so far, but I will make up for it.


{{% 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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are introducing it as a command, maybe we could remove the --sync option from install? I don't see why we would keep a duplicate (especially with the major version bump).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to avoid this breaking change because it would break a lot of CI scripts. (Most of our other breaking changes are not really relevant for most users or at least not relevant for CI.) But we can deprecate it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, let's deprecate it, but with a set date (June 2025 sounds reasonable). I'd like us to avoid a long-living deprecation we are stuck with forever.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, there is also poetry self install --sync.

Option a) Do not deprecate --sync for poetry self install (only for poetry install).
Option b) Introduce poetry self sync for the symmetry.

]

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nitpick to satisfy the mind goblins: I hate that we have to do that like this, but I don't see any better way.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dito.



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")
Loading