bachya/regenmaschine

View on GitHub
regenmaschine/errors.py

Summary

Maintainability
A
0 mins
Test Coverage
"""Define package errors."""

from __future__ import annotations

from typing import Any

from aiohttp import ClientResponse
from aiohttp.client_exceptions import ClientError
from yarl import URL


class RainMachineError(Exception):
    """Define a base error."""

    pass


class RequestError(RainMachineError):
    """Define an error related to invalid requests."""

    pass


class TokenExpiredError(RequestError):
    """Define an error for expired access tokens that can't be refreshed."""

    pass


class UnknownAPICallError(RequestError):
    """Define an error for an unknown API call."""

    pass


class UnvalidatedEmailError(RequestError):
    """Define an error for unvalidated email addresses."""

    pass


LOCAL_ERROR_CODE_EXCEPTION_MAPPING = {
    2: TokenExpiredError,
    13: UnknownAPICallError,
}

REMOTE_ERROR_CODE_EXCEPTION_MAPPING = {
    1: UnvalidatedEmailError("The email has not been validated"),
}


def _raise_local_api_error(url: URL, error_code: int, message: str) -> None:
    """Raise the appropriate error for a remote error code.

    Args:
        url: The URL that raised the exception:
        error_code: The RainMachine error code.
        message: The RainMachine error message.

    Raises:
        exc: A RequestError subclass.
    """
    exc: RequestError
    try:
        exc_obj = LOCAL_ERROR_CODE_EXCEPTION_MAPPING[error_code]
        exc = exc_obj(message)
    except KeyError:
        exc = RequestError(
            f"Unknown error returned for {url}: {error_code} -> {message}"
        )
    raise exc


def _raise_remote_api_error(url: URL, error_code: int) -> None:
    """Raise the appropriate error for a remote error code.

    Args:
        url: The URL that raised the exception:
        error_code: The RainMachine error code.

    Raises:
        exc: A RequestError subclass.
    """
    exc: RequestError
    try:
        exc = REMOTE_ERROR_CODE_EXCEPTION_MAPPING[error_code]
    except KeyError:
        exc = RequestError(f"Unknown error code returned for {url}: {error_code}")
    raise exc


def raise_for_error(resp: ClientResponse, data: dict[str, Any] | None) -> None:
    """Raise an error from the remote API if necessary.

    Args:
        resp: The aiohttp ClientResponse that generated the exception.
        data: An optional API response payload.

    Raises:
        RequestError: Raised when any error occurs.
    """
    if data:
        if data.get("errorType") and data["errorType"] > 0:
            # RainMachine's remote cloud uses "errorType" to show errors, so if we find
            # that, we assume we need to raise a remote error:
            _raise_remote_api_error(resp.url, data["errorType"])

        if data.get("statusCode") and data["statusCode"] != 200:
            # RainMachine's local cloud uses "statusCode" to show errors, so if we find
            # that, we assume we need to raise a local error:
            _raise_local_api_error(resp.url, data["statusCode"], data["message"])
    else:
        try:
            resp.raise_for_status()
        except ClientError as err:
            raise RequestError(f"Error while requesting {resp.url}: {err}") from err