diff --git a/docs/cli.md b/docs/cli.md index fb94f416ce1..404ff69f452 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -272,6 +272,13 @@ 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" >}}). + + ## update In order to get the latest versions of the dependencies and to update the `poetry.lock` file, diff --git a/src/poetry/console/application.py b/src/poetry/console/application.py index a10b41a309e..9a487df82a6 100644 --- a/src/poetry/console/application.py +++ b/src/poetry/console/application.py @@ -64,6 +64,7 @@ def _load() -> Command: "search", "shell", "show", + "sync", "update", "version", # Cache commands diff --git a/src/poetry/console/commands/sync.py b/src/poetry/console/commands/sync.py new file mode 100644 index 00000000000..29d6c1be873 --- /dev/null +++ b/src/poetry/console/commands/sync.py @@ -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 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. + +poetry sync + +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 +--no-root option like below: + + poetry sync --no-root + +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. +""" diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py index ae163df3ec2..15125be4a23 100644 --- a/tests/console/commands/test_install.py +++ b/tests/console/commands/test_install.py @@ -63,6 +63,11 @@ """ +@pytest.fixture +def command() -> str: + return "install" + + @pytest.fixture def poetry(project_factory: ProjectFactory) -> Poetry: return project_factory(name="export", pyproject_content=PYPROJECT_CONTENT) @@ -70,9 +75,9 @@ def poetry(project_factory: ProjectFactory) -> Poetry: @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( @@ -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, @@ -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: @@ -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, @@ -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: @@ -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, @@ -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: @@ -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, @@ -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: @@ -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 = """\ @@ -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 diff --git a/tests/console/commands/test_sync.py b/tests/console/commands/test_sync.py new file mode 100644 index 00000000000..ed1610cc5b7 --- /dev/null +++ b/tests/console/commands/test_sync.py @@ -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 +def command() -> str: + return "sync" + + +@pytest.mark.skip("Only relevant for `poetry install`") +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")