Skip to content

Commit

Permalink
backend: Handling temperature sensor not available error
Browse files Browse the repository at this point in the history
  • Loading branch information
kkrings committed Apr 18, 2024
1 parent f826316 commit ef5f552
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 9 deletions.
9 changes: 9 additions & 0 deletions apps/rasptherm-backend/rasptherm_backend/models/error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pydantic import BaseModel


class ErrorDetailModel(BaseModel):
msg: str


class ServiceNotAvailableModel(BaseModel):
detail: ErrorDetailModel
13 changes: 10 additions & 3 deletions apps/rasptherm-backend/rasptherm_backend/routers/sensor.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
from typing import Annotated

from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, status

from rasptherm_backend.models.error import ServiceNotAvailableModel
from rasptherm_backend.models.sensor import ReadSensorModel
from rasptherm_backend.services.sensor import Sensor, get_sensor
from rasptherm_backend.services.sensor import get_sensor
from rasptherm_backend.services.sensor import read_sensor as _read_sensor
from rasptherm_backend.types.sensor import Sensor

sensor_router = APIRouter(prefix="/sensor", tags=["sensor"])


@sensor_router.get("/read")
@sensor_router.get(
"/read",
responses={
status.HTTP_503_SERVICE_UNAVAILABLE: {"model": ServiceNotAvailableModel}
},
)
async def read_sensor(
sensor: Annotated[Sensor, Depends(get_sensor)],
) -> ReadSensorModel:
Expand Down
10 changes: 4 additions & 6 deletions apps/rasptherm-backend/rasptherm_backend/services/sensor.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
from collections.abc import AsyncIterator
from typing import Annotated, Protocol
from typing import Annotated

from fastapi import Depends
from rasptherm_sensor.client import non_ssl_sensor_connection, ssl_sensor_connection
from rasptherm_sensor.types import SensorReadout

from rasptherm_backend.models.sensor import ReadSensorModel
from rasptherm_backend.services.certs import read_sensor_certs
from rasptherm_backend.services.settings import Settings, get_settings


class Sensor(Protocol):
async def read_sensor(self) -> SensorReadout: ...
from rasptherm_backend.types.sensor import Sensor
from rasptherm_backend.utils.sensor import sensor_not_available_handling


async def get_sensor(
Expand All @@ -34,6 +31,7 @@ async def get_sensor(
yield sensor


@sensor_not_available_handling
async def read_sensor(sensor: Sensor) -> ReadSensorModel:
readout = await sensor.read_sensor()

Expand Down
Empty file.
7 changes: 7 additions & 0 deletions apps/rasptherm-backend/rasptherm_backend/types/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from typing import Protocol

from rasptherm_sensor.types import SensorReadout


class Sensor(Protocol):
async def read_sensor(self) -> SensorReadout: ...
31 changes: 31 additions & 0 deletions apps/rasptherm-backend/rasptherm_backend/utils/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from collections.abc import Awaitable, Callable
from functools import wraps
from typing import TypeVar

from fastapi import HTTPException, status
from grpc import StatusCode # type: ignore
from grpc.aio import AioRpcError # type: ignore

from rasptherm_backend.types.sensor import Sensor

_SensorOutput = TypeVar("_SensorOutput")
_SensorFunc = Callable[[Sensor], Awaitable[_SensorOutput]]


def sensor_not_available_handling(
sensor_func: _SensorFunc[_SensorOutput],
) -> _SensorFunc[_SensorOutput]:
@wraps(sensor_func)
async def handle_sensor_not_awailable(sensor: Sensor) -> _SensorOutput:
try:
return await sensor_func(sensor)
except AioRpcError as rpc_exception:
if rpc_exception.code() != StatusCode.UNAVAILABLE:
raise rpc_exception

raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail={"msg": "The requested sensor is currently not available"},
)

return handle_sensor_not_awailable
26 changes: 26 additions & 0 deletions apps/rasptherm-backend/tests/routers/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import pytest
from fastapi import FastAPI, status
from fastapi.testclient import TestClient
from grpc import StatusCode # type: ignore
from grpc.aio import AioRpcError # type: ignore
from rasptherm_sensor.types import SensorReadout

from rasptherm_backend.models.sensor import ReadSensorModel
Expand All @@ -18,6 +20,12 @@ def test_read_sensor(client: TestClient, expected_readout: ReadSensorModel) -> N
assert response.json() == expected_readout.model_dump(mode="json", by_alias=True)


@pytest.mark.usefixtures("override_get_sensor_with_not_available_sensor")
def test_read_sensor_service_unavailable(client: TestClient) -> None:
response = client.get("/sensor/read")
assert response.status_code == status.HTTP_503_SERVICE_UNAVAILABLE


class FakeSensor:
def __init__(self, readout: ReadSensorModel) -> None:
self._readout = readout
Expand All @@ -30,6 +38,15 @@ async def read_sensor(self) -> SensorReadout:
)


class FakeSensorNotAvailable:
async def read_sensor(self) -> SensorReadout:
raise AioRpcError(
code=StatusCode.UNAVAILABLE,
initial_metadata=None,
trailing_metadata=None,
)


@pytest.fixture
def expected_readout() -> ReadSensorModel:
return ReadSensorModel(
Expand All @@ -48,3 +65,12 @@ async def get_fake_sensor() -> AsyncIterator[FakeSensor]:

with override_dependency(app, get_sensor, get_fake_sensor):
yield


@pytest.fixture
def override_get_sensor_with_not_available_sensor(app: FastAPI) -> Iterator[None]:
async def get_fake_sensor() -> AsyncIterator[FakeSensorNotAvailable]:
yield FakeSensorNotAvailable()

with override_dependency(app, get_sensor, get_fake_sensor):
yield

0 comments on commit ef5f552

Please sign in to comment.