mirror of
https://github.com/copier-org/copier.git
synced 2025-05-05 15:32:54 +00:00
feat: raise new TaskError
exception on task errors
Raising `TaskError` instead of `subprocess.CalledProcessError` is backwards compatible, as `TaskError` is a subclass of `subprocess.CalledProcessError`.
This commit is contained in:
parent
94c5bbdd12
commit
a812e14033
@ -71,7 +71,7 @@ def _handle_exceptions(method: Callable[[], None]) -> int:
|
||||
except KeyboardInterrupt:
|
||||
raise UserMessageError("Execution stopped by user")
|
||||
except UserMessageError as error:
|
||||
print(colors.red | "\n".join(error.args), file=sys.stderr)
|
||||
print(colors.red | error.message, file=sys.stderr)
|
||||
return 1
|
||||
except UnsafeTemplateError as error:
|
||||
print(colors.red | "\n".join(error.args), file=sys.stderr)
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from subprocess import CompletedProcess
|
||||
from typing import TYPE_CHECKING, Sequence
|
||||
|
||||
from .tools import printf_exception
|
||||
@ -12,6 +15,11 @@ if TYPE_CHECKING: # always false
|
||||
from .template import Template
|
||||
from .user_data import AnswersMap, Question
|
||||
|
||||
if sys.version_info < (3, 11):
|
||||
from typing_extensions import Self
|
||||
else:
|
||||
from typing import Self
|
||||
|
||||
|
||||
# Errors
|
||||
class CopierError(Exception):
|
||||
@ -21,6 +29,12 @@ class CopierError(Exception):
|
||||
class UserMessageError(CopierError):
|
||||
"""Exit the program giving a message to the user."""
|
||||
|
||||
def __init__(self, message: str):
|
||||
self.message = message
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.message
|
||||
|
||||
|
||||
class UnsupportedVersionError(UserMessageError):
|
||||
"""Copier version does not support template version."""
|
||||
@ -122,6 +136,35 @@ class MultipleYieldTagsError(CopierError):
|
||||
"""Multiple yield tags are used in one path name, but it is not allowed."""
|
||||
|
||||
|
||||
class TaskError(subprocess.CalledProcessError, UserMessageError):
|
||||
"""Exception raised when a task fails."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
command: str | Sequence[str],
|
||||
returncode: int,
|
||||
stdout: str | bytes | None,
|
||||
stderr: str | bytes | None,
|
||||
):
|
||||
subprocess.CalledProcessError.__init__(
|
||||
self, returncode=returncode, cmd=command, output=stdout, stderr=stderr
|
||||
)
|
||||
message = f"Task {command!r} returned non-zero exit status {returncode}."
|
||||
UserMessageError.__init__(self, message)
|
||||
|
||||
@classmethod
|
||||
def from_process(
|
||||
cls, process: CompletedProcess[str] | CompletedProcess[bytes]
|
||||
) -> Self:
|
||||
"""Create a TaskError from a CompletedProcess."""
|
||||
return cls(
|
||||
command=process.args,
|
||||
returncode=process.returncode,
|
||||
stdout=process.stdout,
|
||||
stderr=process.stderr,
|
||||
)
|
||||
|
||||
|
||||
# Warnings
|
||||
class CopierWarning(Warning):
|
||||
"""Base class for all other Copier warnings."""
|
||||
|
@ -42,6 +42,7 @@ from .errors import (
|
||||
CopierAnswersInterrupt,
|
||||
ExtensionNotFoundError,
|
||||
InteractiveSessionError,
|
||||
TaskError,
|
||||
UnsafeTemplateError,
|
||||
UserMessageError,
|
||||
YieldTagInFileError,
|
||||
@ -388,7 +389,9 @@ class Worker:
|
||||
|
||||
extra_env = {k[1:].upper(): str(v) for k, v in extra_context.items()}
|
||||
with local.cwd(working_directory), local.env(**extra_env):
|
||||
subprocess.run(task_cmd, shell=use_shell, check=True, env=local.env)
|
||||
process = subprocess.run(task_cmd, shell=use_shell, env=local.env)
|
||||
if process.returncode:
|
||||
raise TaskError.from_process(process)
|
||||
|
||||
def _render_context(self) -> AnyByStrMutableMapping:
|
||||
"""Produce render context for Jinja."""
|
||||
|
2
poetry.lock
generated
2
poetry.lock
generated
@ -1639,4 +1639,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9"
|
||||
content-hash = "8778772793661c235a48d4ab2de8839426451f751ca66a79387790f5067c2794"
|
||||
content-hash = "b871a6f90070af08db2af9a9303eb7dde034eef4363d26c67ca8f08be2be29b6"
|
||||
|
@ -42,6 +42,7 @@ pyyaml = ">=5.3.1"
|
||||
questionary = ">=1.8.1"
|
||||
eval-type-backport = { version = ">=0.1.3,<0.3.0", python = "<3.10" }
|
||||
platformdirs = ">=4.3.6"
|
||||
typing-extensions = { version = ">=4.0.0,<5.0.0", python = "<3.11" }
|
||||
|
||||
[tool.poetry.group.dev]
|
||||
optional = true
|
||||
@ -59,7 +60,6 @@ types-backports = ">=0.1.3"
|
||||
types-colorama = ">=0.4"
|
||||
types-pygments = ">=2.17"
|
||||
types-pyyaml = ">=6.0.4"
|
||||
typing-extensions = { version = ">=3.10.0.0,<5.0.0", python = "<3.10" }
|
||||
|
||||
[tool.poetry.group.docs]
|
||||
optional = true
|
||||
|
@ -1,5 +1,4 @@
|
||||
from pathlib import Path
|
||||
from subprocess import CalledProcessError
|
||||
|
||||
import pytest
|
||||
from plumbum import local
|
||||
@ -10,7 +9,7 @@ import copier
|
||||
def test_cleanup(tmp_path: Path) -> None:
|
||||
"""Copier creates dst_path, fails to copy and removes it."""
|
||||
dst = tmp_path / "new_folder"
|
||||
with pytest.raises(CalledProcessError):
|
||||
with pytest.raises(copier.errors.TaskError):
|
||||
copier.run_copy("./tests/demo_cleanup", dst, quiet=True, unsafe=True)
|
||||
assert not dst.exists()
|
||||
|
||||
@ -18,7 +17,7 @@ def test_cleanup(tmp_path: Path) -> None:
|
||||
def test_do_not_cleanup(tmp_path: Path) -> None:
|
||||
"""Copier creates dst_path, fails to copy and keeps it."""
|
||||
dst = tmp_path / "new_folder"
|
||||
with pytest.raises(CalledProcessError):
|
||||
with pytest.raises(copier.errors.TaskError):
|
||||
copier.run_copy(
|
||||
"./tests/demo_cleanup", dst, quiet=True, unsafe=True, cleanup_on_error=False
|
||||
)
|
||||
@ -29,7 +28,7 @@ def test_no_cleanup_when_folder_existed(tmp_path: Path) -> None:
|
||||
"""Copier will not delete a folder if it didn't create it."""
|
||||
preexisting_file = tmp_path / "something"
|
||||
preexisting_file.touch()
|
||||
with pytest.raises(CalledProcessError):
|
||||
with pytest.raises(copier.errors.TaskError):
|
||||
copier.run_copy(
|
||||
"./tests/demo_cleanup",
|
||||
tmp_path,
|
||||
|
Loading…
x
Reference in New Issue
Block a user