"""
Exceptions raised in :mod:`aioqbt`.
"""
import logging
import sys
from typing import Optional
import aiohttp
from typing_extensions import Self
[docs]
class AQError(Exception):
"""
Generic error class.
"""
[docs]
class MapperError(AQError):
"""
Raised when mapper operations failed.
"""
[docs]
class VersionError(AQError):
"""
Version check failed.
"""
[docs]
class APIError(AQError):
"""
Base class of API errors.
"""
message: str
"""Error message or HTTP reason phrase"""
status: int
"""HTTP status"""
resp: Optional[aiohttp.ClientResponse]
"""The closed response object"""
def __init__(
self,
message: str = "",
status: int = 0,
resp: Optional[aiohttp.ClientResponse] = None,
):
self.message = message
self.status = status
self.resp = resp
[docs]
@classmethod
def from_response(
cls,
resp: aiohttp.ClientResponse,
message: str = "",
) -> Self:
"""Create an exception instance based on a response."""
return cls(
message=message or str(resp.reason),
status=resp.status,
resp=resp,
)
def __repr__(self) -> str:
return f"{type(self).__name__}(status={self.status!r}, message={self.message!r})"
def __str__(self) -> str:
return repr(self)
[docs]
class LoginError(APIError):
"""
Login has failed.
This is raised by :meth:`.AuthAPI.login` and HTTP status is 200.
"""
[docs]
class BadRequestError(APIError):
"""
Bad request.
The error is usually raised because of missing or invalid parameters.
This may be caused by empty value sometimes.
HTTP status is usually 400.
"""
[docs]
class ForbiddenError(APIError):
"""
Forbidden.
The request to resources is explicitly denied due to permission.
HTTP status is usually 403.
"""
[docs]
class NotFoundError(APIError):
"""
Not found.
It is likely that the API endpoint is misspelled or qBittorrent need an update.
HTTP status is usually 404.
"""
[docs]
class ConflictError(APIError):
"""
Conflict.
HTTP status is usually 409.
"""
[docs]
class AddTorrentError(ConflictError):
"""
No **new** torrents were added.
This is raised by :meth:`.TorrentsAPI.add` and HTTP status is 409, or 200 before qBittorrent v5.2.0.
"""
# lookup error class by status
# {status: exc_class}
_ERROR_TABLE = {
400: BadRequestError,
403: ForbiddenError,
404: NotFoundError,
409: ConflictError,
415: UnsupportedMediaTypeError,
}
def _add_note(
ex: Optional[BaseException],
note: str,
*,
logger: Optional[logging.Logger] = None,
log_level: Optional[int] = None,
) -> None:
"""
Add a note to exception if Python version >= 3.11 or record with logger
"""
if sys.version_info >= (3, 11):
ex.add_note(note)
return
# fallback to logging
if logger is None:
logger = logging.getLogger(f"{__name__}.note")
if log_level is None:
log_level = logging.WARNING
logger.log(log_level, note, exc_info=ex)