Skip to content

Commit

Permalink
Make subtasks of sequence inherit cwd option by default #160
Browse files Browse the repository at this point in the history
Also:
- apply correction if cwd value passed to app is a file path
- begin refactor of how configuration is managed across tasks
- improve error handling
  • Loading branch information
nat-n committed Nov 4, 2023
1 parent c46c9c2 commit 51a6fd7
Show file tree
Hide file tree
Showing 15 changed files with 149 additions and 30 deletions.
11 changes: 8 additions & 3 deletions poethepoet/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class PoeThePoet:

def __init__(
self,
cwd: Optional[Path] = None,
cwd: Optional[Union[Path, str]] = None,
config: Optional[Union[Mapping[str, Any], "PoeConfig"]] = None,
output: IO = sys.stdout,
poetry_env_path: Optional[str] = None,
Expand All @@ -68,11 +68,16 @@ def __init__(
from .config import PoeConfig
from .ui import PoeUi

self.cwd = cwd or Path().resolve()
self.cwd = Path(cwd) if cwd else Path().resolve()

if self.cwd and self.cwd.is_file():
config_name = self.cwd.name
self.cwd = self.cwd.parent

self.config = (
config
if isinstance(config, PoeConfig)
else PoeConfig(cwd=cwd, table=config, config_name=config_name)
else PoeConfig(cwd=self.cwd, table=config, config_name=config_name)
)
self.ui = PoeUi(output=output, program_name=program_name)
self._poetry_env_path = poetry_env_path
Expand Down
4 changes: 2 additions & 2 deletions poethepoet/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,8 @@ def find_config_file(self, target_dir: Optional[str] = None) -> Path:
"""
Resolve a path to a self._config_name using one of two strategies:
1. If target_dir is provided then only look there, (accept path to config file
or to a directory dir).
2. Otherwise look for the self._config_name is the current working directory,
or to a directory).
2. Otherwise look for the self._config_name in the current working directory,
following by all parent directories in ascending order.
Both strategies result in an Exception on failure.
Expand Down
19 changes: 6 additions & 13 deletions poethepoet/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,29 +98,22 @@ def get_task_output(self, invocation: Tuple[str, ...]):
"""
return re.sub(r"\s+", " ", self.captured_stdout[invocation].strip("\r\n"))

def get_working_dir(self, env: "EnvVarsManager", task_options: Dict[str, Any]):
cwd_option = env.fill_template(task_options.get("cwd", "."))
working_dir = Path(cwd_option)

if not working_dir.is_absolute():
working_dir = self.project_dir / working_dir

return working_dir

def get_executor(
self,
invocation: Tuple[str, ...],
env: "EnvVarsManager",
task_options: Dict[str, Any],
working_dir: Path,
executor_config: Optional[Mapping[str, str]] = None,
capture_stdout: bool = False,
) -> "PoeExecutor":
from .executor import PoeExecutor

return PoeExecutor.get(
invocation=invocation,
context=self,
env=env,
working_dir=self.get_working_dir(env, task_options),
working_dir=working_dir,
dry=self.dry,
executor_config=task_options.get("executor"),
capture_stdout=task_options.get("capture_stdout", False),
executor_config=executor_config,
capture_stdout=capture_stdout,
)
9 changes: 8 additions & 1 deletion poethepoet/executor/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,14 @@ def _execute_cmd(

return self._exec_via_subproc(cmd, input=input, env=env, shell=shell)
except FileNotFoundError as error:
return self._handle_file_not_found(cmd, error)
if error.filename == cmd[0]:
return self._handle_file_not_found(cmd, error)
if error.filename == self.working_dir:
raise PoeException(
"The specified working directory does not exists "
f"'{self.working_dir}'"
)
raise

def _handle_file_not_found(
self, cmd: Sequence[str], error: FileNotFoundError
Expand Down
45 changes: 45 additions & 0 deletions poethepoet/task/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Dict,
Iterator,
List,
NamedTuple,
Optional,
Sequence,
Tuple,
Expand Down Expand Up @@ -47,10 +48,23 @@ def __init__(cls, *args):
TaskContent = Union[str, List[Union[str, Dict[str, Any]]]]


class TaskInheritance(NamedTuple):
"""
Collection of inheritanced config from a parent task to a child task
"""

cwd: str

@classmethod
def from_task(cls, parent_task: "PoeTask"):
return cls(cwd=str(parent_task.options.get("cwd", parent_task.inheritance.cwd)))


class PoeTask(metaclass=MetaPoeTask):
name: str
content: TaskContent
options: Dict[str, Any]
inheritance: TaskInheritance
named_args: Optional[Dict[str, str]] = None

__options__: Dict[str, Union[Type, Tuple[Type, ...]]] = {}
Expand Down Expand Up @@ -81,6 +95,7 @@ def __init__(
config: "PoeConfig",
invocation: Tuple[str, ...],
capture_stdout: bool = False,
inheritance: Optional[TaskInheritance] = None,
):
self.name = name
self.content = content
Expand All @@ -89,6 +104,7 @@ def __init__(
self._config = config
self._is_windows = sys.platform == "win32"
self.invocation = invocation
self.inheritance = inheritance or TaskInheritance(cwd=str(config.cwd))

@classmethod
def from_config(
Expand All @@ -98,6 +114,7 @@ def from_config(
ui: "PoeUi",
invocation: Tuple[str, ...],
capture_stdout: Optional[bool] = None,
inheritance: Optional[TaskInheritance] = None,
) -> "PoeTask":
task_def = config.tasks.get(task_name)
if not task_def:
Expand All @@ -109,6 +126,7 @@ def from_config(
ui,
invocation=invocation,
capture_stdout=capture_stdout,
inheritance=inheritance,
)

@classmethod
Expand All @@ -121,6 +139,7 @@ def from_def(
invocation: Tuple[str, ...],
array_item: Union[bool, str] = False,
capture_stdout: Optional[bool] = None,
inheritance: Optional[TaskInheritance] = None,
) -> "PoeTask":
task_type = cls.resolve_task_type(task_def, config, array_item)
if task_type is None:
Expand Down Expand Up @@ -148,6 +167,7 @@ def from_def(
ui=ui,
config=config,
invocation=invocation,
inheritance=inheritance,
)

@classmethod
Expand Down Expand Up @@ -268,6 +288,31 @@ def _handle_run(
"""
raise NotImplementedError

def _get_executor(
self,
context: "RunContext",
env: "EnvVarsManager",
):
return context.get_executor(
self.invocation,
env,
working_dir=self.get_working_dir(env),
executor_config=self.options.get("executor"),
capture_stdout=self.options.get("capture_stdout", False),
)

def get_working_dir(
self,
env: "EnvVarsManager",
) -> Path:
cwd_option = env.fill_template(self.options.get("cwd", self.inheritance.cwd))
working_dir = Path(cwd_option)

if not working_dir.is_absolute():
working_dir = self._config.project_dir / working_dir

return working_dir

def iter_upstream_tasks(
self, context: "RunContext"
) -> Iterator[Tuple[str, "PoeTask"]]:
Expand Down
4 changes: 2 additions & 2 deletions poethepoet/task/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def _handle_run(

self._print_action(shlex.join(cmd), context.dry)

return context.get_executor(self.invocation, env, self.options).execute(
return self._get_executor(context, env).execute(
cmd, use_exec=self.options.get("use_exec", False)
)

Expand All @@ -68,7 +68,7 @@ def _resolve_args(self, context: "RunContext", env: "EnvVarsManager"):
f"Invalid cmd task {self.name!r} includes multiple command lines"
)

working_dir = context.get_working_dir(env, self.options)
working_dir = self.get_working_dir(env)

result = []
for cmd_token, has_glob in resolve_command_tokens(
Expand Down
2 changes: 1 addition & 1 deletion poethepoet/task/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def _handle_run(
cmd = ("python", "-c", "".join(script))

self._print_action(self.content.strip(), context.dry)
return context.get_executor(self.invocation, env, self.options).execute(
return self._get_executor(context, env).execute(
cmd, use_exec=self.options.get("use_exec", False)
)

Expand Down
10 changes: 8 additions & 2 deletions poethepoet/task/ref.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Tuple, Type, Union

from .base import PoeTask
from .base import PoeTask, TaskInheritance

if TYPE_CHECKING:
from ..config import PoeConfig
Expand Down Expand Up @@ -31,7 +31,13 @@ def _handle_run(

invocation = tuple(shlex.split(env.fill_template(self.content.strip())))
extra_args = [*invocation[1:], *extra_args]
task = self.from_config(invocation[0], self._config, self._ui, invocation)
task = self.from_config(
invocation[0],
self._config,
self._ui,
invocation,
inheritance=TaskInheritance.from_task(self),
)

if task.has_deps():
return self._run_task_graph(task, context, extra_args, env)
Expand Down
2 changes: 1 addition & 1 deletion poethepoet/task/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _handle_run(
cmd = ("python", "-c", "".join(script))

self._print_action(shlex.join(argv), context.dry)
return context.get_executor(self.invocation, env, self.options).execute(
return self._get_executor(context, env).execute(
cmd, use_exec=self.options.get("use_exec", False)
)

Expand Down
8 changes: 6 additions & 2 deletions poethepoet/task/sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
)

from ..exceptions import ExecutionError, PoeException
from .base import PoeTask, TaskContent
from .base import PoeTask, TaskContent, TaskInheritance

if TYPE_CHECKING:
from ..config import PoeConfig
Expand Down Expand Up @@ -43,9 +43,12 @@ def __init__(
config: "PoeConfig",
invocation: Tuple[str, ...],
capture_stdout: bool = False,
inheritance: Optional[TaskInheritance] = None,
):
assert capture_stdout is False
super().__init__(name, content, options, ui, config, invocation)
super().__init__(
name, content, options, ui, config, invocation, False, inheritance
)

self.subtasks = [
self.from_def(
Expand All @@ -55,6 +58,7 @@ def __init__(
invocation=(task_name,),
ui=ui,
array_item=self.options.get("default_item_type", True),
inheritance=TaskInheritance.from_task(self),
)
for index, item in enumerate(self.content)
for task_name in (
Expand Down
2 changes: 1 addition & 1 deletion poethepoet/task/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _handle_run(

self._print_action(content, context.dry)

return context.get_executor(self.invocation, env, self.options).execute(
return self._get_executor(context, env).execute(
interpreter_cmd, input=content.encode()
)

Expand Down
10 changes: 8 additions & 2 deletions poethepoet/task/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
)

from ..exceptions import ExecutionError, PoeException
from .base import PoeTask, TaskContent
from .base import PoeTask, TaskContent, TaskInheritance

if TYPE_CHECKING:
from ..config import PoeConfig
Expand Down Expand Up @@ -49,8 +49,12 @@ def __init__(
config: "PoeConfig",
invocation: Tuple[str, ...],
capture_stdout: bool = False,
inheritance: Optional[TaskInheritance] = None,
):
super().__init__(name, content, options, ui, config, invocation)
assert capture_stdout is False
super().__init__(
name, content, options, ui, config, invocation, False, inheritance
)

control_task_name = f"{name}[control]"
control_invocation: Tuple[str, ...] = (control_task_name,)
Expand All @@ -65,6 +69,7 @@ def __init__(
invocation=control_invocation,
ui=ui,
capture_stdout=True,
inheritance=TaskInheritance.from_task(self),
)

self.switch_tasks = {}
Expand All @@ -83,6 +88,7 @@ def __init__(
config=config,
invocation=task_invocation,
ui=ui,
inheritance=TaskInheritance.from_task(self),
)

def _handle_run(
Expand Down
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ _clean_docs.script = "shutil:rmtree('docs/_build', ignore_errors=1)"
help = "Execute poe from this repo (useful for testing)"
script = "poethepoet:main"

[tool.poe.tasks.y]
cmd = "pwd"

[tool.poe.tasks.x]
sequence = ["y", {cmd = "pwd"}]
cwd = "tests"


[tool.rstcheck]
ignore_messages = [
Expand Down
17 changes: 17 additions & 0 deletions tests/fixtures/sequences_project/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,20 @@ env = {thing = "Done."}
type = "integer"


[tool.poe.tasks.cwd_once]
expr = "os.getcwd()"
imports = ["os"]

[tool.poe.tasks.cwd_elsewhere]
expr = "os.getcwd()"
imports = ["os"]
cwd = "${POE_ROOT}"

[tool.poe.tasks.all_cwd]
sequence = [
"cwd_once",
"cwd_elsewhere",
{script = "os:getcwd()", print_result = true},
{script = "os:getcwd()", cwd = ".", print_result = true}
]
cwd = "my_package"
Loading

0 comments on commit 51a6fd7

Please sign in to comment.