Skip to content

Commit

Permalink
Replace sys.exit calls with exceptions and return values
Browse files Browse the repository at this point in the history
Nearly all calls to sys.exit are replaced by khard's custom Cancelled
exception or an explicit return value of type str|int|None.  These are
the values that sys.exit can digest and they are handed outwards to the
exit call with the return value of main().

This should make the code more testable.

Additionally all custom exception are moved to module.
  • Loading branch information
lucc committed Dec 19, 2024
1 parent e0a6ff5 commit b721246
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 115 deletions.
4 changes: 3 additions & 1 deletion khard/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env python3

import sys

from .khard import main

main()
sys.exit(main())
20 changes: 1 addition & 19 deletions khard/address_book.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,13 @@
import vobject.base

from . import carddav_object
from .exceptions import AddressBookParseError
from .query import AnyQuery, Query


logger = logging.getLogger(__name__)


class AddressBookParseError(Exception):
"""Indicate an error while parsing data from an address book backend."""

def __init__(self, filename: str, abook: str, reason: Exception) -> None:
"""Store the filename that caused the error."""
super().__init__()
self.filename = filename
self.abook = abook
self.reason = reason

def __str__(self) -> str:
return "Error when parsing {} in address book {}: {}".format(
self.filename, self.abook, self.reason)


class AddressBookNameError(Exception):
"""Indicate an error with an address book name."""


class AddressBook(metaclass=abc.ABCMeta):
"""The base class of all address book implementations."""

Expand Down
9 changes: 4 additions & 5 deletions khard/carddav_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import logging
import os
import re
import sys
import time
from typing import Any, Callable, Literal, Optional, TypeVar, Union, \
Sequence, overload
Expand All @@ -23,6 +22,8 @@
from ruamel.yaml import YAML
import vobject

from khard.exceptions import Cancelled

from . import address_book # pylint: disable=unused-import # for type checking
from . import helpers
from .helpers.typing import (Date, ObjectType, PostAddress, StrList,
Expand Down Expand Up @@ -1518,11 +1519,9 @@ def write_to_file(self, overwrite: bool = False) -> None:
with atomic_write(self.filename, overwrite=overwrite) as f:
f.write(self.vcard.serialize())
except vobject.base.ValidateError as err:
print("Error: Vcard is not valid.\n{}".format(err))
sys.exit(4)
raise Cancelled(f"Vcard is not valid.\n{err}", 4)
except OSError as err:
print("Error: Can't write\n{}".format(err))
sys.exit(4)
raise Cancelled(f"Can't write\n{err}", 4)

def delete_vcard_file(self) -> None:
try:
Expand Down
3 changes: 2 additions & 1 deletion khard/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import sys

from .actions import Actions
from .exceptions import ConfigError
from .carddav_object import CarddavObject
from .config import Config, ConfigError
from .config import Config
from .query import AndQuery, AnyQuery, parse
from .version import version as khard_version

Expand Down
8 changes: 2 additions & 6 deletions khard/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import validate

from .actions import Actions
from .address_book import AddressBookCollection, AddressBookNameError, \
VdirAddressBook
from .address_book import AddressBookCollection, VdirAddressBook
from .exceptions import AddressBookNameError, ConfigError
from .query import Query


Expand All @@ -29,10 +29,6 @@
ConfigFile = Union[str, list[str], io.StringIO]


class ConfigError(Exception):
"""Errors during config file parsing"""


def validate_command(value: list[str]) -> list[str]:
"""Special validator to check shell commands
Expand Down
32 changes: 32 additions & 0 deletions khard/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Custom exceptions for khard"""


class Cancelled(Exception):
"""An exception indicating that the user canceled some operation or
some backend operation failed"""

def __init__(self, message: str = "Canceled", code: int = 1) -> None:
super().__init__(message)
self.code = code


class AddressBookParseError(Exception):
"""Indicate an error while parsing data from an address book backend."""

def __init__(self, filename: str, abook: str, reason: Exception) -> None:
"""Store the filename that caused the error."""
super().__init__()
self.filename = filename
self.abook = abook
self.reason = reason

def __str__(self) -> str:
return f"Error when parsing {self.filename} in address book {self.abook}: {self.reason}"


class AddressBookNameError(Exception):
"""Indicate an error with an address book name."""


class ConfigError(Exception):
"""Errors during config file parsing"""
11 changes: 3 additions & 8 deletions khard/helpers/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@
from tempfile import NamedTemporaryFile
from typing import Callable, Generator, Optional, Sequence, TypeVar, Union

from ..exceptions import Cancelled
from ..carddav_object import CarddavObject


T = TypeVar("T")


class Canceled(Exception):
"""An exception indicating that the user canceled some operation."""
def __init__(self, message: str = "Canceled") -> None:
super().__init__(message)


def confirm(message: str, accept_enter_key: bool = True) -> bool:
"""Ask the user for confirmation on the terminal.
Expand Down Expand Up @@ -74,7 +69,7 @@ def ask(message: str, choices: list[str], default: Optional[str] = None,
except (EOFError, IndexError, ValueError):
pass
except KeyboardInterrupt:
raise Canceled
raise Cancelled
if help is not None:
print(help)

Expand All @@ -97,7 +92,7 @@ def select(items: Sequence[T], include_none: bool = False) -> Optional[T]:
answer = input(prompt)
answer = answer.lower()
if answer == "q":
raise Canceled
raise Cancelled
index = int(answer)
if include_none and index == 0:
return None
Expand Down
Loading

0 comments on commit b721246

Please sign in to comment.