Skip to content
Merged
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
78 changes: 59 additions & 19 deletions src/poetry/console/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import re

from contextlib import suppress
from functools import cached_property
from importlib import import_module
from pathlib import Path
from typing import TYPE_CHECKING
Expand All @@ -21,10 +20,12 @@
from poetry.console.command_loader import CommandLoader
from poetry.console.commands.command import Command
from poetry.utils.helpers import directory
from poetry.utils.helpers import ensure_path


if TYPE_CHECKING:
from collections.abc import Callable
from typing import Any

from cleo.events.event import Event
from cleo.io.inputs.argv_input import ArgvInput
Expand Down Expand Up @@ -103,6 +104,8 @@ def __init__(self) -> None:
self._disable_plugins = False
self._disable_cache = False
self._plugins_loaded = False
self._working_directory = Path.cwd()
self._project_directory: Path | None = None

dispatcher = EventDispatcher()
dispatcher.add_listener(COMMAND, self.register_command_loggers)
Expand Down Expand Up @@ -156,20 +159,9 @@ def _default_definition(self) -> Definition:

return definition

@cached_property
def _project_directory(self) -> Path:
if self._io and self._io.input.option("project"):
with directory(self._working_directory):
return Path(self._io.input.option("project")).absolute()

return self._working_directory

@cached_property
def _working_directory(self) -> Path:
if self._io and self._io.input.option("directory"):
return Path(self._io.input.option("directory")).absolute()

return Path.cwd()
@property
def project_directory(self) -> Path:
return self._project_directory or self._working_directory

@property
def poetry(self) -> Poetry:
Expand All @@ -179,7 +171,7 @@ def poetry(self) -> Poetry:
return self._poetry

self._poetry = Factory().create_poetry(
cwd=self._project_directory,
cwd=self.project_directory,
io=self._io,
disable_plugins=self._disable_plugins,
disable_cache=self._disable_cache,
Expand Down Expand Up @@ -227,8 +219,11 @@ def create_io(
return io

def _run(self, io: IO) -> int:
self._disable_plugins = io.input.parameter_option("--no-plugins")
self._disable_cache = io.input.has_parameter_option("--no-cache")
# we do this here and not inside the _configure_io implementation in order
# to ensure the users are not exposed to a stack trace for providing invalid values to
# the options --directory or --project, configuring the options here allow cleo to trap and
# display the error cleanly unless the user uses verbose or debug
self._configure_custom_application_options(io)

self._load_plugins(io)

Expand All @@ -237,6 +232,51 @@ def _run(self, io: IO) -> int:

return exit_code

def _option_get_value(self, io: IO, name: str, default: Any) -> Any:
option = self.definition.option(name)

if option is None:
return default

values = [f"--{option.name}"]

if option.shortcut:
values.append(f"-{option.shortcut}")

if not io.input.has_parameter_option(values):
return default

if option.is_flag():
return True

return io.input.parameter_option(values=values, default=default)

def _configure_custom_application_options(self, io: IO) -> None:
self._disable_plugins = self._option_get_value(
io, "no-plugins", self._disable_plugins
)
self._disable_cache = self._option_get_value(
io, "no-cache", self._disable_cache
)

# we use ensure_path for the directories to make sure these are valid paths
# this will raise an exception if the path is invalid
self._working_directory = ensure_path(
self._option_get_value(io, "directory", Path.cwd()), is_directory=True
)

self._project_directory = self._option_get_value(io, "project", None)
if self._project_directory is not None:
self._project_directory = Path(self._project_directory)
self._project_directory = ensure_path(
self._project_directory
if self._project_directory.is_absolute()
else self._working_directory.joinpath(self._project_directory).resolve(
strict=False
),
is_directory=True,
)

def _configure_io(self, io: IO) -> None:
# We need to check if the command being run
# is the "run" command.
Expand Down Expand Up @@ -394,7 +434,7 @@ def _load_plugins(self, io: IO) -> None:
from poetry.plugins.application_plugin import ApplicationPlugin
from poetry.plugins.plugin_manager import PluginManager

PluginManager.add_project_plugin_path(self._project_directory)
PluginManager.add_project_plugin_path(self.project_directory)
manager = PluginManager(ApplicationPlugin.group)
manager.load_plugins()
manager.activate(self)
Expand Down
12 changes: 12 additions & 0 deletions src/poetry/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,18 @@ def paths_csv(paths: list[Path]) -> str:
return ", ".join(f'"{c!s}"' for c in paths)


def ensure_path(path: str | Path, is_directory: bool = False) -> Path:
if isinstance(path, str):
path = Path(path)

if path.exists() and path.is_dir() == is_directory:
return path

raise ValueError(
f"Specified path '{path}' is not a valid {'directory' if is_directory else 'file'}."
)


def is_dir_writable(path: Path, create: bool = False) -> bool:
try:
if not path.exists():
Expand Down
108 changes: 0 additions & 108 deletions tests/console/commands/test_version.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
from __future__ import annotations

import os
import textwrap

from pathlib import Path
from typing import TYPE_CHECKING

import pytest

from cleo.testers.application_tester import ApplicationTester

from poetry.console.application import Application
from poetry.console.commands.version import VersionCommand


if TYPE_CHECKING:
from cleo.testers.command_tester import CommandTester
from pytest_mock import MockerFixture

from poetry.poetry import Poetry
from tests.types import CommandTesterFactory
Expand Down Expand Up @@ -140,103 +132,3 @@ def test_dry_run(tester: CommandTester) -> None:
new_pyproject = tester.command.poetry.file.path.read_text(encoding="utf-8")
assert tester.io.fetch_output() == "Bumping version from 1.2.3 to 2.0.0\n"
assert old_pyproject == new_pyproject


def test_version_with_project_parameter(
fixture_dir: FixtureDirGetter, mocker: MockerFixture
) -> None:
app = Application()
tester = ApplicationTester(app)

orig_version_command = VersionCommand.handle

def mock_handle(command: VersionCommand) -> int:
exit_code = orig_version_command(command)

command.io.write_line(f"ProjectPath: {command.poetry.pyproject_path.parent}")
command.io.write_line(f"WorkingDirectory: {os.getcwd()}")

return exit_code

mocker.patch("poetry.console.commands.version.VersionCommand.handle", mock_handle)

source_dir = fixture_dir("scripts")
tester.execute(f"--project {source_dir} version")

output = tester.io.fetch_output()
expected = textwrap.dedent(f"""\
scripts 0.1.0
ProjectPath: {source_dir}
WorkingDirectory: {os.getcwd()}
""")

assert source_dir != Path(os.getcwd())
assert output == expected


def test_version_with_directory_parameter(
fixture_dir: FixtureDirGetter, mocker: MockerFixture
) -> None:
app = Application()
tester = ApplicationTester(app)

orig_version_command = VersionCommand.handle

def mock_handle(command: VersionCommand) -> int:
exit_code = orig_version_command(command)

command.io.write_line(f"ProjectPath: {command.poetry.pyproject_path.parent}")
command.io.write_line(f"WorkingDirectory: {os.getcwd()}")

return exit_code

mocker.patch("poetry.console.commands.version.VersionCommand.handle", mock_handle)

source_dir = fixture_dir("scripts")
tester.execute(f"--directory {source_dir} version")

output = tester.io.fetch_output()
expected = textwrap.dedent(f"""\
scripts 0.1.0
ProjectPath: {source_dir}
WorkingDirectory: {source_dir}
""")

assert source_dir != Path(os.getcwd())
assert output == expected


def test_version_with_directory_and_project_parameter(
fixture_dir: FixtureDirGetter, mocker: MockerFixture
) -> None:
app = Application()
tester = ApplicationTester(app)

orig_version_command = VersionCommand.handle

def mock_handle(command: VersionCommand) -> int:
exit_code = orig_version_command(command)

command.io.write_line(f"ProjectPath: {command.poetry.pyproject_path.parent}")
command.io.write_line(f"WorkingDirectory: {os.getcwd()}")

return exit_code

mocker.patch("poetry.console.commands.version.VersionCommand.handle", mock_handle)

source_dir = fixture_dir("scripts")
working_directory = source_dir.parent
project_path = "./scripts"

tester.execute(f"--directory {working_directory} --project {project_path} version")

output = tester.io.fetch_output()

expected = textwrap.dedent(f"""\
scripts 0.1.0
ProjectPath: {source_dir}
WorkingDirectory: {working_directory}
""")

assert source_dir != working_directory
assert output == expected
Loading
Loading