From 4fb9493e94deab47f7f8e5504a739e10b1453dd6 Mon Sep 17 00:00:00 2001 From: Ben Ellis Date: Fri, 9 Jun 2023 22:49:53 +0100 Subject: [PATCH 1/2] feat(#8083): Added support for poetry config being stored in a project .poetry directory and added the 'self init' command. --- docs/cli.md | 5 ++ docs/configuration.md | 2 + src/poetry/console/application.py | 1 + src/poetry/console/commands/self/init.py | 57 ++++++++++++++++ src/poetry/locations.py | 1 + .../console/commands/self/test_add_plugins.py | 66 +++++++++++++++++++ tests/console/commands/self/test_init.py | 64 ++++++++++++++++++ 7 files changed, 196 insertions(+) create mode 100644 src/poetry/console/commands/self/init.py create mode 100644 tests/console/commands/self/test_init.py diff --git a/docs/cli.md b/docs/cli.md index b5913b69040..83c4775019e 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -987,6 +987,11 @@ poetry self remove poetry-plugin-export * `--dry-run`: Outputs the operations but will not execute anything (implicitly enables --verbose). +### self init + +The `self init` command creates a local `.poetry` configuration directory for poetry. Poetry will +use configuration and it's dependencies from this directory instead of the system default. + ### self install The `self install` command ensures all additional packages specified are installed in the current diff --git a/docs/configuration.md b/docs/configuration.md index d9c074262c3..5b05d2cb238 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -125,6 +125,8 @@ Poetry uses the following default directories: - Windows: `%APPDATA%\pypoetry` - MacOS: `~/Library/Application Support/pypoetry` +If there is a `.poetry` directory in the project directory, this will override the default configuration directory. + You can override the Config directory by setting the `POETRY_CONFIG_DIR` environment variable. ### Data Directory diff --git a/src/poetry/console/application.py b/src/poetry/console/application.py index 8d325efde1f..b34f91f66ba 100644 --- a/src/poetry/console/application.py +++ b/src/poetry/console/application.py @@ -80,6 +80,7 @@ def _load() -> Command: "env use", # Self commands "self add", + "self init", "self install", "self lock", "self remove", diff --git a/src/poetry/console/commands/self/init.py b/src/poetry/console/commands/self/init.py new file mode 100644 index 00000000000..9840efb8e4e --- /dev/null +++ b/src/poetry/console/commands/self/init.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +from pathlib import Path + +from cleo.helpers import option + +from poetry.console.commands.self.self_command import SelfCommand + + +class SelfInitCommand(SelfCommand): + name = "self init" + description = """\ +Initializes a local .poetry directory to be used instead of the global poetry \ +configuration.\ +""" + options = [ + option( + "project-dir", + None, + ( + "The directory containing a pyproject.toml file to which the .poetry " + " directory will be added." + ), + flag=False, + default=None, + ), + ] + help = """\ +The self init command creates and initializes a .poetry directory that\ +contains poetry configuration specific for the project directory instead of using the\ +global poetry configuration. +""" + + loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] + + def __init__(self) -> None: + self._system_pyproject = super().system_pyproject + super().__init__() + + def handle(self) -> int: + project_dir = self.option("project-dir") + if project_dir is None: + project_dir = Path.cwd() + self._system_pyproject = Path(project_dir) / ".poetry" / "pyproject.toml" + if self.system_pyproject.exists(): + self.line(f"Poetry settings already exist for project {project_dir}") + self.line_error("\nNo changes were applied.") + return 1 + + self.line(f"Initialising poetry settings for project {project_dir}") + self.system_pyproject.parent.mkdir(parents=True, exist_ok=True) + self.reset_poetry() + return 0 + + @property + def system_pyproject(self) -> Path: + return self._system_pyproject diff --git a/src/poetry/locations.py b/src/poetry/locations.py index 2d3b25da722..a011ad6ab3b 100644 --- a/src/poetry/locations.py +++ b/src/poetry/locations.py @@ -18,6 +18,7 @@ DEFAULT_CACHE_DIR = user_cache_path(_APP_NAME, appauthor=False) CONFIG_DIR = Path( os.getenv("POETRY_CONFIG_DIR") + or (Path.cwd() / ".poetry" if (Path.cwd() / ".poetry").exists() else None) or user_config_path(_APP_NAME, appauthor=False, roaming=True) ) diff --git a/tests/console/commands/self/test_add_plugins.py b/tests/console/commands/self/test_add_plugins.py index 07b0c905b1f..978ca5c2b5e 100644 --- a/tests/console/commands/self/test_add_plugins.py +++ b/tests/console/commands/self/test_add_plugins.py @@ -1,7 +1,15 @@ from __future__ import annotations +from pathlib import Path from typing import TYPE_CHECKING from typing import Any +from typing import cast + +from poetry.core.utils.helpers import temporary_directory + +import poetry.locations + +from poetry.console.commands.self.add import SelfAddCommand if TYPE_CHECKING: @@ -29,6 +37,11 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: return command_tester_factory("self add") +@pytest.fixture() +def init_command_tester(command_tester_factory: CommandTesterFactory) -> CommandTester: + return command_tester_factory("self init") + + def assert_plugin_add_result( tester: CommandTester, expected: str, @@ -64,6 +77,59 @@ def test_add_no_constraint( assert_plugin_add_result(tester, expected, "^0.1.0") +def test_add_after_init( + tester: CommandTester, + init_command_tester: CommandTester, + repo: TestRepository, +) -> None: + with temporary_directory() as tmp_dir: + repo.add_package(Package("poetry-plugin", "0.1.0")) + init_command_tester.execute(args=f"--project-dir={tmp_dir}") + current_config_dir = poetry.locations.CONFIG_DIR + try: + poetry.locations.CONFIG_DIR = Path(tmp_dir) / ".poetry" + cast(SelfAddCommand, tester._command).reset_poetry() + tester.execute("poetry-plugin") + + expected_add_output = """\ +Using version ^0.1.0 for poetry-plugin + +Updating dependencies +Resolving dependencies... + +Package operations: 1 install, 0 updates, 0 removals + + • Installing poetry-plugin (0.1.0) + +Writing lock file +""" + + expected_pyproject_toml = """\ +[tool.poetry] +name = "poetry-instance" +version = "1.6.0.dev0" +description = "" +authors = [] +license = "" + +[tool.poetry.dependencies] +python = "3.10.9" +poetry = "1.6.0.dev0" + +[tool.poetry.group.additional.dependencies] +poetry-plugin = "^0.1.0" + +""" + + assert_plugin_add_result(tester, expected_add_output, "^0.1.0") + assert ( + Path(tmp_dir) / ".poetry" / "pyproject.toml" + ).read_text() == expected_pyproject_toml + + finally: + poetry.locations.CONFIG_DIR = current_config_dir + + def test_add_with_constraint( tester: CommandTester, repo: TestRepository, diff --git a/tests/console/commands/self/test_init.py b/tests/console/commands/self/test_init.py new file mode 100644 index 00000000000..8a711847744 --- /dev/null +++ b/tests/console/commands/self/test_init.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import os + +from pathlib import Path +from typing import TYPE_CHECKING + +import pytest + +from poetry.core.utils.helpers import temporary_directory + + +if TYPE_CHECKING: + from cleo.testers.command_tester import CommandTester + + from tests.types import CommandTesterFactory + + +@pytest.fixture() +def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: + return command_tester_factory("self init") + + +def test_init_no_args( + tester: CommandTester, +) -> None: + with temporary_directory() as tmp_dir: + current_dir = os.getcwd() + try: + os.chdir(tmp_dir) + tester.execute() + assert tester.io.fetch_output() == f"""\ +Initialising poetry settings for project {tmp_dir} +""" + assert (Path(tmp_dir) / ".poetry" / "pyproject.toml").exists() + assert tester.status_code == 0 + finally: + os.chdir(current_dir) + + +def test_init_project_dir( + tester: CommandTester, +) -> None: + with temporary_directory() as tmp_dir: + tester.execute(args=f"--project-dir {tmp_dir}") + assert tester.io.fetch_output() == f"""\ +Initialising poetry settings for project {tmp_dir} +""" + assert (Path(tmp_dir) / ".poetry" / "pyproject.toml").exists() + assert tester.status_code == 0 + + +def test_init_project_dir_already_exists( + tester: CommandTester, +) -> None: + with temporary_directory() as tmp_dir: + pyproject_file = Path(tmp_dir) / ".poetry" / "pyproject.toml" + pyproject_file.parent.mkdir() + pyproject_file.write_text("hello world") + tester.execute(args=f"--project-dir {tmp_dir}") + assert tester.io.fetch_output() == f"""\ +Poetry settings already exist for project {tmp_dir} +""" + assert tester.status_code == 1 From 30ec386503c529cadc74e0d3f33d1d1229663195 Mon Sep 17 00:00:00 2001 From: Ben Ellis Date: Fri, 9 Jun 2023 23:52:27 +0100 Subject: [PATCH 2/2] chore: fix for ubuntu CI failures. --- .../console/commands/self/test_add_plugins.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/tests/console/commands/self/test_add_plugins.py b/tests/console/commands/self/test_add_plugins.py index 978ca5c2b5e..5ea1a4ae26d 100644 --- a/tests/console/commands/self/test_add_plugins.py +++ b/tests/console/commands/self/test_add_plugins.py @@ -104,27 +104,16 @@ def test_add_after_init( Writing lock file """ - expected_pyproject_toml = """\ -[tool.poetry] -name = "poetry-instance" -version = "1.6.0.dev0" -description = "" -authors = [] -license = "" - -[tool.poetry.dependencies] -python = "3.10.9" -poetry = "1.6.0.dev0" - + expected_in_pyproject_toml = """\ [tool.poetry.group.additional.dependencies] poetry-plugin = "^0.1.0" - """ assert_plugin_add_result(tester, expected_add_output, "^0.1.0") assert ( - Path(tmp_dir) / ".poetry" / "pyproject.toml" - ).read_text() == expected_pyproject_toml + expected_in_pyproject_toml + in (Path(tmp_dir) / ".poetry" / "pyproject.toml").read_text() + ) finally: poetry.locations.CONFIG_DIR = current_config_dir