Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/app/api/api_v1/endpoints/cameras.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from app.crud.crud_pose import PoseCRUD
from app.models import Camera, Role, UserRole
from app.schemas.cameras import (
CameraAngleOfView,
CameraCreate,
CameraDeviceConfig,
CameraEdit,
Expand Down Expand Up @@ -199,6 +200,24 @@ async def update_camera_name(
return CameraOut(**camera.model_dump())


@router.patch(
"/{camera_id}/angle_of_view", status_code=status.HTTP_200_OK, summary="Update the angle of view of a camera"
)
async def update_camera_angle_of_view(
payload: CameraAngleOfView,
camera_id: int = Path(..., gt=0),
cameras: CameraCRUD = Depends(get_camera_crud),
token_payload: TokenPayload = Security(get_jwt, scopes=[UserRole.ADMIN]),
) -> CameraOut:
telemetry_client.capture(
token_payload.sub,
event="cameras-update-angle-of-view",
properties={"camera_id": camera_id, "angle_of_view": payload.angle_of_view},
)
camera = await cameras.update(camera_id, payload)
return CameraOut(**camera.model_dump())


@router.patch(
"/{camera_id}/trustable", status_code=status.HTTP_200_OK, summary="Update the trustable status of a camera"
)
Expand Down
7 changes: 6 additions & 1 deletion src/app/crud/crud_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from app.crud.base import BaseCRUD
from app.models import Camera
from app.schemas.cameras import (
CameraAngleOfView,
CameraCreate,
CameraDeviceConfig,
CameraEdit,
Expand All @@ -20,7 +21,11 @@


class CameraCRUD(
BaseCRUD[Camera, CameraCreate, LastActive | CameraEdit | CameraName | CameraDeviceConfig | CameraTrustable]
BaseCRUD[
Camera,
CameraCreate,
LastActive | CameraEdit | CameraName | CameraAngleOfView | CameraDeviceConfig | CameraTrustable,
]
):
def __init__(self, session: AsyncSession) -> None:
super().__init__(session, Camera)
10 changes: 10 additions & 0 deletions src/app/schemas/cameras.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ class CameraName(BaseModel):
name: ResourceName = Field(..., description="name of the camera")


class CameraAngleOfView(BaseModel):
angle_of_view: float = Field(
...,
gt=0,
le=360,
description="angle between left and right camera view",
json_schema_extra={"examples": [51.36]},
)


class CameraTrustable(BaseModel):
is_trustable: bool = Field(..., description="whether the detection from this camera can be trusted")

Expand Down
43 changes: 43 additions & 0 deletions src/tests/endpoints/test_cameras.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,49 @@ async def test_update_camera_name(
assert all(response.json()[k] == v for k, v in payload.items())


@pytest.mark.parametrize(
("user_idx", "cam_id", "payload", "status_code", "status_detail"),
[
(None, 1, {"angle_of_view": 51.36}, 401, "Not authenticated"),
(0, 1, {"angle_of_view": 0}, 422, None),
(0, 1, {"angle_of_view": 360.5}, 422, None),
(0, 1, {}, 422, None),
(0, 999, {"angle_of_view": 51.36}, 404, "Table Camera has no corresponding entry."),
(0, 1, {"angle_of_view": 51.36}, 200, None),
(0, 2, {"angle_of_view": 360}, 200, None),
(1, 1, {"angle_of_view": 51.36}, 403, "Incompatible token scope."),
(2, 2, {"angle_of_view": 51.36}, 403, "Incompatible token scope."),
],
)
@pytest.mark.asyncio
async def test_update_camera_angle_of_view(
async_client: AsyncClient,
camera_session: AsyncSession,
user_idx: Union[int, None],
cam_id: int,
payload: Dict[str, Any],
status_code: int,
status_detail: Union[str, None],
):
auth = None
if isinstance(user_idx, int):
auth = pytest.get_token(
pytest.user_table[user_idx]["id"],
pytest.user_table[user_idx]["role"].split(),
pytest.user_table[user_idx]["organization_id"],
)

response = await async_client.patch(f"/cameras/{cam_id}/angle_of_view", json=payload, headers=auth)
assert response.status_code == status_code, print(response.__dict__)
if isinstance(status_detail, str):
assert response.json()["detail"] == status_detail
if response.status_code // 100 == 2:
assert response.json()["angle_of_view"] == payload["angle_of_view"]
# Only the angle of view changes
before = next(cam for cam in pytest.camera_table if cam["id"] == cam_id)
assert all(response.json()[k] == before[k] for k in ("name", "lat", "lon", "elevation"))


@pytest.mark.parametrize("name_length", [2, 3, 51, 100, 101])
@pytest.mark.asyncio
async def test_camera_name_create_rename_and_read_round_trip(async_client, camera_session, name_length):
Expand Down
Loading