add safe home assistant error handling
This commit is contained in:
1
app/core/__init__.py
Normal file
1
app/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Core application helpers."""
|
||||
23
app/core/exception_handlers.py
Normal file
23
app/core/exception_handlers.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI, Request, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from app.ha.exceptions import HaAuthError, HaClientError, HaHttpError, HaTimeoutError
|
||||
|
||||
|
||||
def register_exception_handlers(app: FastAPI) -> None:
|
||||
@app.exception_handler(HaClientError)
|
||||
async def handle_ha_client_error(_: Request, exc: HaClientError) -> JSONResponse:
|
||||
return JSONResponse(
|
||||
status_code=_status_code_for_ha_error(exc),
|
||||
content={"detail": exc.public_detail},
|
||||
)
|
||||
|
||||
|
||||
def _status_code_for_ha_error(exc: HaClientError) -> int:
|
||||
if isinstance(exc, HaTimeoutError):
|
||||
return status.HTTP_504_GATEWAY_TIMEOUT
|
||||
if isinstance(exc, (HaAuthError, HaHttpError)):
|
||||
return status.HTTP_502_BAD_GATEWAY
|
||||
return status.HTTP_502_BAD_GATEWAY
|
||||
@@ -6,6 +6,13 @@ from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
from app.ha.exceptions import (
|
||||
HaAuthError,
|
||||
HaHttpError,
|
||||
HaTimeoutError,
|
||||
HaUnexpectedPayloadError,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -26,13 +33,35 @@ class HaClient:
|
||||
})
|
||||
|
||||
def list_entities(self) -> list[dict[str, Any]]:
|
||||
response = self._session.get(
|
||||
f"{self._settings.url}/api/states",
|
||||
timeout=self._settings.timeout_seconds,
|
||||
)
|
||||
response.raise_for_status()
|
||||
payload = response.json()
|
||||
try:
|
||||
response = self._session.get(
|
||||
f"{self._settings.url}/api/states",
|
||||
timeout=self._settings.timeout_seconds,
|
||||
)
|
||||
except requests.Timeout as exc:
|
||||
raise HaTimeoutError("Home Assistant request timed out.") from exc
|
||||
except requests.RequestException as exc:
|
||||
raise HaHttpError(status_code=502, message="Home Assistant request failed.") from exc
|
||||
|
||||
if response.status_code in {401, 403}:
|
||||
raise HaAuthError(
|
||||
status_code=response.status_code,
|
||||
message="Home Assistant authentication failed.",
|
||||
)
|
||||
|
||||
try:
|
||||
response.raise_for_status()
|
||||
except requests.HTTPError as exc:
|
||||
raise HaHttpError(
|
||||
status_code=response.status_code,
|
||||
message="Home Assistant returned an HTTP error.",
|
||||
) from exc
|
||||
|
||||
try:
|
||||
payload = response.json()
|
||||
except ValueError as exc:
|
||||
raise HaUnexpectedPayloadError("Home Assistant returned invalid JSON.") from exc
|
||||
|
||||
if not isinstance(payload, list):
|
||||
msg = "Home Assistant states response must be a list."
|
||||
raise TypeError(msg)
|
||||
raise HaUnexpectedPayloadError("Home Assistant states response must be a list.")
|
||||
return payload
|
||||
|
||||
27
app/ha/exceptions.py
Normal file
27
app/ha/exceptions.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class HaClientError(Exception):
|
||||
"""Base class for Home Assistant integration failures."""
|
||||
|
||||
public_detail = "Home Assistant is currently unavailable."
|
||||
|
||||
|
||||
class HaTimeoutError(HaClientError):
|
||||
public_detail = "Home Assistant request timed out."
|
||||
|
||||
|
||||
class HaHttpError(HaClientError):
|
||||
public_detail = "Home Assistant returned an error."
|
||||
|
||||
def __init__(self, status_code: int, message: str | None = None) -> None:
|
||||
super().__init__(message or self.public_detail)
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
class HaAuthError(HaHttpError):
|
||||
public_detail = "Home Assistant authentication failed."
|
||||
|
||||
|
||||
class HaUnexpectedPayloadError(HaClientError):
|
||||
public_detail = "Home Assistant returned an unexpected response."
|
||||
@@ -5,6 +5,7 @@ from fastapi import FastAPI
|
||||
|
||||
from app.api.v1.entities import router as entities_router
|
||||
from app.config import load_settings
|
||||
from app.core.exception_handlers import register_exception_handlers
|
||||
from app.ha.client import HaClient, HaClientSettings
|
||||
from app.ha.reader import HaReader
|
||||
|
||||
@@ -31,6 +32,7 @@ app = FastAPI(
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
register_exception_handlers(app)
|
||||
|
||||
app.include_router(entities_router)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user