refactor: move clan_cli.error to clan_lib.error
This commit is contained in:
@@ -18,7 +18,7 @@ from .serde import dataclass_to_dict, from_dict, sanitize_string
|
||||
|
||||
__all__ = ["dataclass_to_dict", "from_dict", "sanitize_string"]
|
||||
|
||||
from clan_cli.errors import ClanError
|
||||
from clan_lib.errors import ClanError
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
from clan_cli.cmd import RunOpts, run
|
||||
from clan_cli.errors import ClanError
|
||||
from clan_cli.nix import nix_shell
|
||||
|
||||
from clan_lib.errors import ClanError
|
||||
from clan_lib.flake.flake import Flake
|
||||
|
||||
from . import API
|
||||
|
||||
@@ -6,13 +6,13 @@ from typing import Any, TypedDict
|
||||
from uuid import uuid4
|
||||
|
||||
from clan_cli.dirs import TemplateType, clan_templates
|
||||
from clan_cli.errors import ClanError
|
||||
from clan_cli.git import commit_file
|
||||
from clan_cli.machines.hardware import HardwareConfig, show_machine_hardware_config
|
||||
from clan_cli.machines.machines import Machine
|
||||
|
||||
from clan_lib.api import API
|
||||
from clan_lib.api.modules import Frontmatter, extract_frontmatter
|
||||
from clan_lib.errors import ClanError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from clan_cli.errors import ClanError
|
||||
|
||||
from clan_lib.errors import ClanError
|
||||
from clan_lib.flake.flake import Flake
|
||||
|
||||
from . import API
|
||||
|
||||
@@ -4,10 +4,10 @@ from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
from clan_cli.cmd import RunOpts
|
||||
from clan_cli.errors import ClanError
|
||||
from clan_cli.machines.machines import Machine
|
||||
|
||||
from clan_lib.api import API
|
||||
from clan_lib.errors import ClanError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Dependencies:
|
||||
- pydantic: A library for data validation and settings management.
|
||||
- pydantic_core: Core functionality for Pydantic.
|
||||
|
||||
Note: This module assumes the presence of other modules and classes such as `ClanError` and `ErrorDetails` from the `clan_cli.errors` module.
|
||||
Note: This module assumes the presence of other modules and classes such as `ClanError` and `ErrorDetails` from the `clan_lib.errors` module.
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
@@ -45,7 +45,7 @@ from typing import (
|
||||
is_typeddict,
|
||||
)
|
||||
|
||||
from clan_cli.errors import ClanError
|
||||
from clan_lib.errors import ClanError
|
||||
|
||||
|
||||
def sanitize_string(s: str) -> str:
|
||||
|
||||
201
pkgs/clan-cli/clan_lib/errors/__init__.py
Normal file
201
pkgs/clan-cli/clan_lib/errors/__init__.py
Normal file
@@ -0,0 +1,201 @@
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
from dataclasses import dataclass
|
||||
from math import floor
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
|
||||
def get_term_filler(name: str) -> tuple[int, int]:
|
||||
width, _ = shutil.get_terminal_size()
|
||||
|
||||
filler = floor((width - len(name)) / 2)
|
||||
return (filler - 1, width)
|
||||
|
||||
|
||||
def text_heading(heading: str) -> str:
|
||||
filler, total = get_term_filler(heading)
|
||||
msg = f"{'=' * filler} {heading} {'=' * filler}"
|
||||
if len(msg) < total:
|
||||
msg += "="
|
||||
return msg
|
||||
|
||||
|
||||
def optional_text(heading: str, text: str | None) -> str:
|
||||
if text is None or text.strip() == "":
|
||||
return ""
|
||||
|
||||
with contextlib.suppress(json.JSONDecodeError):
|
||||
text = json.dumps(json.loads(text), indent=4)
|
||||
|
||||
return f"{text_heading(heading)}\n{text}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DictDiff:
|
||||
added: dict[str, str]
|
||||
removed: dict[str, str]
|
||||
changed: dict[str, dict[str, str]]
|
||||
|
||||
|
||||
def diff_dicts(dict1: dict[str, str], dict2: dict[str, str]) -> DictDiff:
|
||||
"""
|
||||
Compare two dictionaries and report additions, deletions, and changes.
|
||||
|
||||
:param dict1: The first dictionary (baseline).
|
||||
:param dict2: The second dictionary (to compare).
|
||||
:return: A dictionary with keys 'added', 'removed', and 'changed', each containing
|
||||
the respective differences. 'changed' is a nested dictionary with keys 'old' and 'new'.
|
||||
"""
|
||||
added = {k: dict2[k] for k in dict2 if k not in dict1}
|
||||
removed = {k: dict1[k] for k in dict1 if k not in dict2}
|
||||
changed = {
|
||||
k: {"old": dict1[k], "new": dict2[k]}
|
||||
for k in dict1
|
||||
if k in dict2 and dict1[k] != dict2[k]
|
||||
}
|
||||
|
||||
return DictDiff(added=added, removed=removed, changed=changed)
|
||||
|
||||
|
||||
def indent_command(command_list: list[str]) -> str:
|
||||
formatted_command = []
|
||||
i = 0
|
||||
while i < len(command_list):
|
||||
arg = command_list[i]
|
||||
formatted_command.append(shlex.quote(arg))
|
||||
|
||||
if i < len(command_list) - 1:
|
||||
# Check if the current argument is an option
|
||||
if arg.startswith("-"):
|
||||
# Indent after the next argument
|
||||
formatted_command.append(" ")
|
||||
i += 1
|
||||
formatted_command.append(shlex.quote(command_list[i]))
|
||||
|
||||
if i < len(command_list) - 1:
|
||||
# Add line continuation only if it's not the last argument
|
||||
formatted_command.append(" \\\n ")
|
||||
|
||||
i += 1
|
||||
|
||||
# Join the list into a single string
|
||||
final_command = "".join(formatted_command)
|
||||
|
||||
# Remove the trailing continuation if it exists
|
||||
if final_command.endswith(" \\ \n "):
|
||||
final_command = final_command.rsplit(" \\ \n ", 1)[0]
|
||||
return final_command
|
||||
|
||||
|
||||
DEBUG_COMMANDS = os.environ.get("CLAN_DEBUG_COMMANDS", False)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CmdOut:
|
||||
stdout: str
|
||||
stderr: str
|
||||
env: dict[str, str] | None
|
||||
cwd: Path
|
||||
command_list: list[str]
|
||||
returncode: int
|
||||
msg: str | None
|
||||
|
||||
@property
|
||||
def command(self) -> str:
|
||||
return indent_command(self.command_list)
|
||||
|
||||
def __str__(self) -> str:
|
||||
# Set a common indentation level, assuming a reasonable spacing
|
||||
label_width = max(len("Return Code"), len("Work Dir"), len("Error Msg"))
|
||||
error_msg = [
|
||||
f"""
|
||||
{optional_text("Command", self.command)}
|
||||
{optional_text("Stdout", self.stdout)}
|
||||
{optional_text("Stderr", self.stderr)}
|
||||
{"Return Code:":<{label_width}} {self.returncode}
|
||||
"""
|
||||
]
|
||||
if self.msg:
|
||||
error_msg += [f"{'Error Msg:':<{label_width}} {self.msg.capitalize()}"]
|
||||
|
||||
if DEBUG_COMMANDS:
|
||||
diffed_dict = (
|
||||
diff_dicts(cast(dict[str, str], os.environ), self.env)
|
||||
if self.env
|
||||
else None
|
||||
)
|
||||
diffed_dict_str = (
|
||||
json.dumps(diffed_dict.__dict__, indent=4) if diffed_dict else None
|
||||
)
|
||||
error_msg += [
|
||||
f"""
|
||||
{optional_text("Environment", diffed_dict_str)}
|
||||
{text_heading(heading="Metadata")}
|
||||
{"Work Dir:":<{label_width}} '{self.cwd}'
|
||||
"""
|
||||
]
|
||||
return "\n".join(error_msg)
|
||||
|
||||
|
||||
class ClanError(Exception):
|
||||
"""Base class for exceptions in this module."""
|
||||
|
||||
description: str | None
|
||||
location: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
msg: str | None = None,
|
||||
*,
|
||||
description: str | None = None,
|
||||
location: str | None = None,
|
||||
) -> None:
|
||||
self.description = description
|
||||
self.location = location or "Unknown location"
|
||||
self.msg = msg or ""
|
||||
exception_msg = ""
|
||||
if location:
|
||||
exception_msg += f"{location}: \n"
|
||||
exception_msg += self.msg
|
||||
|
||||
if self.description:
|
||||
exception_msg += f" - {self.description}"
|
||||
super().__init__(exception_msg)
|
||||
|
||||
|
||||
class ClanHttpError(ClanError):
|
||||
status_code: int
|
||||
msg: str
|
||||
|
||||
def __init__(self, status_code: int, msg: str) -> None:
|
||||
self.status_code = status_code
|
||||
self.msg = msg
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class ClanCmdError(ClanError):
|
||||
cmd: CmdOut
|
||||
|
||||
def __init__(self, cmd: CmdOut) -> None:
|
||||
self.cmd = cmd
|
||||
super().__init__()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.cmd)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ClanCmdError({self.cmd})"
|
||||
|
||||
|
||||
class TorSocksError(ClanError):
|
||||
def __init__(self, msg: str) -> None:
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class TorConnectionError(ClanError):
|
||||
def __init__(self, msg: str) -> None:
|
||||
super().__init__(msg)
|
||||
@@ -9,7 +9,6 @@ from typing import Any
|
||||
|
||||
from clan_cli.cmd import Log, RunOpts, run
|
||||
from clan_cli.dirs import nixpkgs_source, select_source, user_cache_dir
|
||||
from clan_cli.errors import ClanError
|
||||
from clan_cli.nix import (
|
||||
nix_build,
|
||||
nix_command,
|
||||
@@ -19,6 +18,8 @@ from clan_cli.nix import (
|
||||
nix_test_store,
|
||||
)
|
||||
|
||||
from clan_lib.errors import ClanError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
|
||||
from clan_cli.errors import ClanError
|
||||
from clan_cli.git import commit_file
|
||||
|
||||
from clan_lib.errors import ClanError
|
||||
from clan_lib.flake.flake import Flake
|
||||
from clan_lib.nix_models.inventory import Inventory
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ flattening, unmerging lists, finding duplicates, and calculating patches.
|
||||
from collections import Counter
|
||||
from typing import Any
|
||||
|
||||
from clan_cli.errors import ClanError
|
||||
from clan_lib.errors import ClanError
|
||||
|
||||
|
||||
def flatten_data(data: dict, parent_key: str = "", separator: str = ".") -> dict:
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from clan_cli.errors import ClanError
|
||||
|
||||
from clan_lib.errors import ClanError
|
||||
from clan_lib.persist.util import (
|
||||
apply_patch,
|
||||
calc_patches,
|
||||
|
||||
@@ -10,7 +10,6 @@ import clan_cli.clan.create
|
||||
import pytest
|
||||
from clan_cli.cmd import RunOpts, run
|
||||
from clan_cli.dirs import specific_machine_dir
|
||||
from clan_cli.errors import ClanError
|
||||
from clan_cli.inventory import patch_inventory_with
|
||||
from clan_cli.machines.create import CreateOptions as ClanCreateOptions
|
||||
from clan_cli.machines.create import create_machine
|
||||
@@ -25,6 +24,7 @@ from clan_cli.vars.generate import generate_vars_for_machine, get_generators_clo
|
||||
|
||||
from clan_lib.api.disk import hw_main_disk_options, set_machine_disk_schema
|
||||
from clan_lib.api.network import check_machine_online
|
||||
from clan_lib.errors import ClanError
|
||||
from clan_lib.flake.flake import Flake
|
||||
from clan_lib.nix_models.inventory import Machine as InventoryMachine
|
||||
from clan_lib.nix_models.inventory import MachineDeploy
|
||||
|
||||
Reference in New Issue
Block a user