Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api improvements #15327

Merged
merged 9 commits into from
Dec 6, 2024
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
1,199 changes: 714 additions & 485 deletions docs/static/frigate-api.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions frigate/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
from markupsafe import escape
from peewee import operator

from frigate.api.defs.app_body import AppConfigSetBody
from frigate.api.defs.app_query_parameters import AppTimelineHourlyQueryParameters
from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryParameters
from frigate.api.defs.request.app_body import AppConfigSetBody
from frigate.api.defs.tags import Tags
from frigate.config import FrigateConfig
from frigate.const import CONFIG_DIR
Expand Down
2 changes: 1 addition & 1 deletion frigate/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from peewee import DoesNotExist
from slowapi import Limiter

from frigate.api.defs.app_body import (
from frigate.api.defs.request.app_body import (
AppPostLoginBody,
AppPostUsersBody,
AppPutPasswordBody,
Expand Down
File renamed without changes.
40 changes: 40 additions & 0 deletions frigate/api/defs/response/event_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from typing import Any, Optional

from pydantic import BaseModel


class EventResponse(BaseModel):
id: str
label: str
sub_label: Optional[str]
camera: str
start_time: float
end_time: Optional[float]
false_positive: bool
zones: list[str]
thumbnail: str
has_clip: bool
has_snapshot: bool
retain_indefinitely: bool
plus_id: Optional[str]
model_hash: Optional[str]
detector_type: Optional[str]
model_type: Optional[str]
data: dict[str, Any]


class EventCreateResponse(BaseModel):
success: bool
message: str
event_id: str


class EventMultiDeleteResponse(BaseModel):
success: bool
deleted_events: list[str]
not_found_events: list[str]


class EventUploadPlusResponse(BaseModel):
success: bool
plus_id: str
62 changes: 34 additions & 28 deletions frigate/api/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,32 @@
from peewee import JOIN, DoesNotExist, fn, operator
from playhouse.shortcuts import model_to_dict

from frigate.api.defs.events_body import (
from frigate.api.defs.query.events_query_parameters import (
DEFAULT_TIME_RANGE,
EventsQueryParams,
EventsSearchQueryParams,
EventsSummaryQueryParams,
)
from frigate.api.defs.query.regenerate_query_parameters import (
RegenerateQueryParameters,
)
from frigate.api.defs.request.events_body import (
EventsCreateBody,
EventsDeleteBody,
EventsDescriptionBody,
EventsEndBody,
EventsSubLabelBody,
SubmitPlusBody,
)
from frigate.api.defs.events_query_parameters import (
DEFAULT_TIME_RANGE,
EventsQueryParams,
EventsSearchQueryParams,
EventsSummaryQueryParams,
)
from frigate.api.defs.regenerate_query_parameters import (
RegenerateQueryParameters,
from frigate.api.defs.response.event_response import (
EventCreateResponse,
EventMultiDeleteResponse,
EventResponse,
EventUploadPlusResponse,
)
from frigate.api.defs.response.generic_response import GenericResponse
from frigate.api.defs.tags import Tags
from frigate.const import (
CLIPS_DIR,
)
from frigate.const import CLIPS_DIR
from frigate.embeddings import EmbeddingsContext
from frigate.events.external import ExternalEventProcessor
from frigate.models import Event, ReviewSegment, Timeline
Expand All @@ -46,7 +51,7 @@
router = APIRouter(tags=[Tags.events])


@router.get("/events")
@router.get("/events", response_model=list[EventResponse])
def events(params: EventsQueryParams = Depends()):
camera = params.camera
cameras = params.cameras
Expand Down Expand Up @@ -263,7 +268,7 @@ def events(params: EventsQueryParams = Depends()):
return JSONResponse(content=list(events))


@router.get("/events/explore")
@router.get("/events/explore", response_model=list[EventResponse])
def events_explore(limit: int = 10):
# get distinct labels for all events
distinct_labels = Event.select(Event.label).distinct().order_by(Event.label)
Expand Down Expand Up @@ -308,7 +313,8 @@ def process_events():
"data": {
k: v
for k, v in event.data.items()
if k in ["type", "score", "top_score", "description"]
if k
in ["type", "score", "top_score", "description", "sub_label_score"]
},
"event_count": label_counts[event.label],
}
Expand All @@ -324,7 +330,7 @@ def process_events():
return JSONResponse(content=processed_events)


@router.get("/event_ids")
@router.get("/event_ids", response_model=list[EventResponse])
def event_ids(ids: str):
ids = ids.split(",")

Expand Down Expand Up @@ -647,15 +653,15 @@ def events_summary(params: EventsSummaryQueryParams = Depends()):
return JSONResponse(content=[e for e in groups.dicts()])


@router.get("/events/{event_id}")
@router.get("/events/{event_id}", response_model=EventResponse)
def event(event_id: str):
try:
return model_to_dict(Event.get(Event.id == event_id))
except DoesNotExist:
return JSONResponse(content="Event not found", status_code=404)


@router.post("/events/{event_id}/retain")
@router.post("/events/{event_id}/retain", response_model=GenericResponse)
def set_retain(event_id: str):
try:
event = Event.get(Event.id == event_id)
Expand All @@ -674,7 +680,7 @@ def set_retain(event_id: str):
)


@router.post("/events/{event_id}/plus")
@router.post("/events/{event_id}/plus", response_model=EventUploadPlusResponse)
def send_to_plus(request: Request, event_id: str, body: SubmitPlusBody = None):
if not request.app.frigate_config.plus_api.is_active():
message = "PLUS_API_KEY environment variable is not set"
Expand Down Expand Up @@ -786,7 +792,7 @@ def send_to_plus(request: Request, event_id: str, body: SubmitPlusBody = None):
)


@router.put("/events/{event_id}/false_positive")
@router.put("/events/{event_id}/false_positive", response_model=EventUploadPlusResponse)
def false_positive(request: Request, event_id: str):
if not request.app.frigate_config.plus_api.is_active():
message = "PLUS_API_KEY environment variable is not set"
Expand Down Expand Up @@ -875,7 +881,7 @@ def false_positive(request: Request, event_id: str):
)


@router.delete("/events/{event_id}/retain")
@router.delete("/events/{event_id}/retain", response_model=GenericResponse)
def delete_retain(event_id: str):
try:
event = Event.get(Event.id == event_id)
Expand All @@ -894,7 +900,7 @@ def delete_retain(event_id: str):
)


@router.post("/events/{event_id}/sub_label")
@router.post("/events/{event_id}/sub_label", response_model=GenericResponse)
def set_sub_label(
request: Request,
event_id: str,
Expand Down Expand Up @@ -946,7 +952,7 @@ def set_sub_label(
)


@router.post("/events/{event_id}/description")
@router.post("/events/{event_id}/description", response_model=GenericResponse)
def set_description(
request: Request,
event_id: str,
Expand Down Expand Up @@ -993,7 +999,7 @@ def set_description(
)


@router.put("/events/{event_id}/description/regenerate")
@router.put("/events/{event_id}/description/regenerate", response_model=GenericResponse)
def regenerate_description(
request: Request, event_id: str, params: RegenerateQueryParameters = Depends()
):
Expand Down Expand Up @@ -1064,14 +1070,14 @@ def delete_single_event(event_id: str, request: Request) -> dict:
return {"success": True, "message": f"Event {event_id} deleted"}


@router.delete("/events/{event_id}")
@router.delete("/events/{event_id}", response_model=GenericResponse)
def delete_event(request: Request, event_id: str):
result = delete_single_event(event_id, request)
status_code = 200 if result["success"] else 404
return JSONResponse(content=result, status_code=status_code)


@router.delete("/events/")
@router.delete("/events/", response_model=EventMultiDeleteResponse)
def delete_events(request: Request, body: EventsDeleteBody):
if not body.event_ids:
return JSONResponse(
Expand All @@ -1097,7 +1103,7 @@ def delete_events(request: Request, body: EventsDeleteBody):
return JSONResponse(content=response, status_code=200)


@router.post("/events/{camera_name}/{label}/create")
@router.post("/events/{camera_name}/{label}/create", response_model=EventCreateResponse)
def create_event(
request: Request,
camera_name: str,
Expand Down Expand Up @@ -1153,7 +1159,7 @@ def create_event(
)


@router.put("/events/{event_id}/end")
@router.put("/events/{event_id}/end", response_model=GenericResponse)
def end_event(request: Request, event_id: str, body: EventsEndBody):
try:
end_time = body.end_time or datetime.datetime.now().timestamp()
Expand Down
12 changes: 12 additions & 0 deletions frigate/api/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from peewee import DoesNotExist
from playhouse.shortcuts import model_to_dict

from frigate.api.defs.request.export_recordings_body import ExportRecordingsBody
from frigate.api.defs.tags import Tags
Expand Down Expand Up @@ -207,3 +208,14 @@ def export_delete(event_id: str):
),
status_code=200,
)


@router.get("/exports/{export_id}")
def get_export(export_id: str):
try:
return JSONResponse(content=model_to_dict(Export.get(Export.id == export_id)))
except DoesNotExist:
return JSONResponse(
content={"success": False, "message": "Export not found"},
status_code=404,
)
2 changes: 1 addition & 1 deletion frigate/api/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from peewee import DoesNotExist, fn
from tzlocal import get_localzone_name

from frigate.api.defs.media_query_parameters import (
from frigate.api.defs.query.media_query_parameters import (
Extension,
MediaEventsSnapshotQueryParams,
MediaLatestFrameQueryParams,
Expand Down
10 changes: 5 additions & 5 deletions frigate/api/review.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
from peewee import Case, DoesNotExist, fn, operator
from playhouse.shortcuts import model_to_dict

from frigate.api.defs.generic_response import GenericResponse
from frigate.api.defs.review_body import ReviewModifyMultipleBody
from frigate.api.defs.review_query_parameters import (
from frigate.api.defs.query.review_query_parameters import (
ReviewActivityMotionQueryParams,
ReviewQueryParams,
ReviewSummaryQueryParams,
)
from frigate.api.defs.review_responses import (
from frigate.api.defs.request.review_body import ReviewModifyMultipleBody
from frigate.api.defs.response.generic_response import GenericResponse
from frigate.api.defs.response.review_response import (
ReviewActivityMotionResponse,
ReviewSegmentResponse,
ReviewSummaryResponse,
Expand Down Expand Up @@ -364,7 +364,7 @@ def delete_reviews(body: ReviewModifyMultipleBody):
ReviewSegment.delete().where(ReviewSegment.id << list_of_ids).execute()

return JSONResponse(
content=({"success": True, "message": "Delete reviews"}), status_code=200
content=({"success": True, "message": "Deleted review items."}), status_code=200
)


Expand Down
6 changes: 3 additions & 3 deletions frigate/test/http_api/test_http_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ def test_post_reviews_delete_non_existent_id(self):
assert response.status_code == 200
response_json = response.json()
assert response_json["success"] == True
assert response_json["message"] == "Delete reviews"
assert response_json["message"] == "Deleted review items."
# Verify that in DB the review segment was not deleted
review_ids_in_db_after = self._get_reviews([id])
assert len(review_ids_in_db_after) == 1
Expand All @@ -540,7 +540,7 @@ def test_post_reviews_delete(self):
assert response.status_code == 200
response_json = response.json()
assert response_json["success"] == True
assert response_json["message"] == "Delete reviews"
assert response_json["message"] == "Deleted review items."
# Verify that in DB the review segment was deleted
review_ids_in_db_after = self._get_reviews([id])
assert len(review_ids_in_db_after) == 0
Expand All @@ -562,7 +562,7 @@ def test_post_reviews_delete_many(self):
assert response.status_code == 200
response_json = response.json()
assert response_json["success"] == True
assert response_json["message"] == "Delete reviews"
assert response_json["message"] == "Deleted review items."

# Verify that in DB all review segments and recordings that were passed were deleted
review_ids_in_db_after = self._get_reviews(ids)
Expand Down
2 changes: 1 addition & 1 deletion frigate/test/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def test_get_good_event(self):

assert event
assert event["id"] == id
assert event == model_to_dict(Event.get(Event.id == id))
assert event["id"] == model_to_dict(Event.get(Event.id == id))["id"]

def test_get_bad_event(self):
app = create_fastapi_app(
Expand Down
Loading