From 3a05279398aad35437e47ddbf9d26cec8a047dc9 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sat, 16 Nov 2024 19:04:48 +0100 Subject: [PATCH] feat: add confirmation step --- src/poetry/config/config_source.py | 24 +++++++++--- src/poetry/console/commands/config.py | 21 ++++++++-- src/poetry/console/commands/init.py | 2 +- tests/console/commands/test_config.py | 56 +++++++++++++++++++-------- 4 files changed, 76 insertions(+), 27 deletions(-) diff --git a/src/poetry/config/config_source.py b/src/poetry/config/config_source.py index 61f7b37b648..e06c934e70a 100644 --- a/src/poetry/config/config_source.py +++ b/src/poetry/config/config_source.py @@ -39,25 +39,22 @@ class ConfigSourceMigration: new_key: str | None value_migration: dict[Any, Any] = dataclasses.field(default_factory=dict) - def apply(self, config_source: ConfigSource, io: IO | None = None) -> None: + def dry_run(self, config_source: ConfigSource, io: IO | None = None) -> bool: io = io or NullIO() try: old_value = config_source.get_property(self.old_key) except PropertyNotFoundError: - return + return False new_value = ( self.value_migration[old_value] if self.value_migration else old_value ) - config_source.remove_property(self.old_key) - msg = f"{self.old_key} = {json.dumps(old_value)}" if self.new_key is not None and new_value is not UNSET: msg += f" -> {self.new_key} = {json.dumps(new_value)}" - config_source.add_property(self.new_key, new_value) elif self.new_key is None: msg += " -> Removed from config" elif self.new_key and new_value is UNSET: @@ -65,6 +62,23 @@ def apply(self, config_source: ConfigSource, io: IO | None = None) -> None: io.write_line(msg) + return True + + def apply(self, config_source: ConfigSource) -> None: + try: + old_value = config_source.get_property(self.old_key) + except PropertyNotFoundError: + return + + new_value = ( + self.value_migration[old_value] if self.value_migration else old_value + ) + + config_source.remove_property(self.old_key) + + if self.new_key is not None and new_value is not UNSET: + config_source.add_property(self.new_key, new_value) + def drop_empty_config_category( keys: list[str], config: dict[Any, Any] diff --git a/src/poetry/console/commands/config.py b/src/poetry/console/commands/config.py index cd9eefa2d1f..85cbb7f0eb3 100644 --- a/src/poetry/console/commands/config.py +++ b/src/poetry/console/commands/config.py @@ -356,9 +356,22 @@ def _migrate(self) -> None: config_source = FileConfigSource(config_file) - self.io.write_line("Starting config migration ...") + self.io.write_line("Checking for required migrations ...") - for migration in CONFIG_MIGRATIONS: - migration.apply(config_source, io=self.io) + required_migrations = [ + migration + for migration in CONFIG_MIGRATIONS + if migration.dry_run(config_source, io=self.io) + ] - self.io.write_line("Config migration successfully done.") + if not required_migrations: + self.io.write_line("Already up to date.") + return + + if not self.io.is_interactive() or self.confirm( + "Proceed with migration?: ", False + ): + for migration in required_migrations: + migration.apply(config_source) + + self.io.write_line("Config migration successfully done.") diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index b1244965fbf..2251ab65fde 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -104,7 +104,7 @@ def _init_pyproject( if pyproject.file.exists(): if pyproject.is_poetry_project(): self.line_error( - "A pyproject.toml file with a project and/or" + "Ayproject.toml file with a project and/or" " a poetry section already exists." ) return 1 diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 506351eebf9..0ae2dd559d8 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -563,32 +563,54 @@ def test_config_solver_lazy_wheel( assert not repo._lazy_wheel +current_config = """\ +[experimental] +system-git-client = true + +[virtualenvs] +prefer-active-python = false +""" + +config_migrated = """\ +system-git-client = true + +[virtualenvs] +use-poetry-python = true +""" + + +@pytest.mark.parametrize( + ["proceed", "expected_config"], + [ + ("yes", config_migrated), + ("no", current_config), + ], +) def test_config_migrate( - tester: CommandTester, mocker: MockerFixture, tmp_path: Path + proceed: str, + expected_config: str, + tester: CommandTester, + mocker: MockerFixture, + tmp_path: Path, ) -> None: config_dir = tmp_path / "config" mocker.patch("poetry.locations.CONFIG_DIR", config_dir) config_file = Path(config_dir / "config.toml") - config_data = textwrap.dedent("""\ - [experimental] - system-git-client = true - - [virtualenvs] - prefer-active-python = false - """) with config_file.open("w") as fh: - fh.write(config_data) - - tester.execute("--migrate") + fh.write(current_config) - expected_config = textwrap.dedent("""\ - system-git-client = true + tester.execute("--migrate", inputs=proceed) - [virtualenvs] - use-poetry-python = true + expected_output = textwrap.dedent("""\ + Checking for required migrations ... + experimental.system-git-client = true -> system-git-client = true + virtualenvs.prefer-active-python = false -> virtualenvs.use-poetry-python = true """) + output = tester.io.fetch_output() + assert output.startswith(expected_output) + with config_file.open("r") as fh: assert fh.read() == expected_config @@ -606,7 +628,7 @@ def test_config_migrate_local_config(tester: CommandTester, poetry: Poetry) -> N with local_config.open("w") as fh: fh.write(config_data) - tester.execute("--migrate --local") + tester.execute("--migrate --local", inputs="yes") expected_config = textwrap.dedent("""\ system-git-client = true @@ -623,4 +645,4 @@ def test_config_migrate_local_config_should_raise_if_not_found( tester: CommandTester, ) -> None: with pytest.raises(RuntimeError, match="No local config file found"): - tester.execute("--migrate --local") + tester.execute("--migrate --local", inputs="yes")