Enable mypy for DB and fix types (#19434)

* Install peewee type hints

* Models now have proper types

* Fix iterator type

* Enable debug builds with dev reqs installed

* Install as wheel

* Fix cast type
This commit is contained in:
Nicolas Mowen 2025-08-08 11:25:39 -06:00 committed by GitHub
parent 9b2f84d3e9
commit 6d078e565a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 31 additions and 16 deletions

View File

@ -107,7 +107,7 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Build - name: Build
run: make run: make debug
- name: Run mypy - name: Run mypy
run: docker run --rm --entrypoint=python3 frigate:latest -u -m mypy --config-file frigate/mypy.ini frigate run: docker run --rm --entrypoint=python3 frigate:latest -u -m mypy --config-file frigate/mypy.ini frigate
- name: Run tests - name: Run tests

View File

@ -20,6 +20,12 @@ local: version
--tag frigate:latest \ --tag frigate:latest \
--load --load
debug: version
docker buildx build --target=frigate --file docker/main/Dockerfile . \
--build-arg DEBUG=true \
--tag frigate:latest \
--load
amd64: amd64:
docker buildx build --target=frigate --file docker/main/Dockerfile . \ docker buildx build --target=frigate --file docker/main/Dockerfile . \
--tag $(IMAGE_REPO):$(VERSION)-$(COMMIT_HASH) \ --tag $(IMAGE_REPO):$(VERSION)-$(COMMIT_HASH) \

View File

@ -148,6 +148,7 @@ RUN --mount=type=bind,source=docker/main/install_s6_overlay.sh,target=/deps/inst
FROM base AS wheels FROM base AS wheels
ARG DEBIAN_FRONTEND ARG DEBIAN_FRONTEND
ARG TARGETARCH ARG TARGETARCH
ARG DEBUG=false
# Use a separate container to build wheels to prevent build dependencies in final image # Use a separate container to build wheels to prevent build dependencies in final image
RUN apt-get -qq update \ RUN apt-get -qq update \
@ -177,6 +178,8 @@ RUN wget -q https://bootstrap.pypa.io/get-pip.py -O get-pip.py \
&& python3 get-pip.py "pip" && python3 get-pip.py "pip"
COPY docker/main/requirements.txt /requirements.txt COPY docker/main/requirements.txt /requirements.txt
COPY docker/main/requirements-dev.txt /requirements-dev.txt
RUN pip3 install -r /requirements.txt RUN pip3 install -r /requirements.txt
# Build pysqlite3 from source # Build pysqlite3 from source
@ -184,7 +187,10 @@ COPY docker/main/build_pysqlite3.sh /build_pysqlite3.sh
RUN /build_pysqlite3.sh RUN /build_pysqlite3.sh
COPY docker/main/requirements-wheels.txt /requirements-wheels.txt COPY docker/main/requirements-wheels.txt /requirements-wheels.txt
RUN pip3 wheel --wheel-dir=/wheels -r /requirements-wheels.txt RUN pip3 wheel --wheel-dir=/wheels -r /requirements-wheels.txt && \
if [ "$DEBUG" = "true" ]; then \
pip3 wheel --wheel-dir=/wheels -r /requirements-dev.txt; \
fi
# Install HailoRT & Wheels # Install HailoRT & Wheels
RUN --mount=type=bind,source=docker/main/install_hailort.sh,target=/deps/install_hailort.sh \ RUN --mount=type=bind,source=docker/main/install_hailort.sh,target=/deps/install_hailort.sh \

View File

@ -1 +1,4 @@
ruff ruff
# types
types-peewee == 3.17.*

View File

@ -3,7 +3,7 @@
import datetime import datetime
import json import json
import logging import logging
from typing import Any, Callable, Optional from typing import Any, Callable, Optional, cast
from frigate.camera import PTZMetrics from frigate.camera import PTZMetrics
from frigate.camera.activity_manager import CameraActivityManager from frigate.camera.activity_manager import CameraActivityManager
@ -135,7 +135,7 @@ class Dispatcher:
def handle_update_event_description() -> None: def handle_update_event_description() -> None:
event: Event = Event.get(Event.id == payload["id"]) event: Event = Event.get(Event.id == payload["id"])
event.data["description"] = payload["description"] cast(dict, event.data)["description"] = payload["description"]
event.save() event.save()
self.publish( self.publish(
"tracked_object_update", "tracked_object_update",

View File

@ -70,7 +70,7 @@ class WebPushClient(Communicator):
# Pull keys from PEM or generate if they do not exist # Pull keys from PEM or generate if they do not exist
self.vapid = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem")) self.vapid = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem"))
users: list[User] = ( users: list[dict[str, Any]] = (
User.select(User.username, User.notification_tokens).dicts().iterator() User.select(User.username, User.notification_tokens).dicts().iterator()
) )
for user in users: for user in users:

View File

@ -13,7 +13,7 @@ from peewee import (
from playhouse.sqlite_ext import JSONField from playhouse.sqlite_ext import JSONField
class Event(Model): # type: ignore[misc] class Event(Model):
id = CharField(null=False, primary_key=True, max_length=30) id = CharField(null=False, primary_key=True, max_length=30)
label = CharField(index=True, max_length=20) label = CharField(index=True, max_length=20)
sub_label = CharField(max_length=100, null=True) sub_label = CharField(max_length=100, null=True)
@ -51,7 +51,7 @@ class Event(Model): # type: ignore[misc]
data = JSONField() # ex: tracked object box, region, etc. data = JSONField() # ex: tracked object box, region, etc.
class Timeline(Model): # type: ignore[misc] class Timeline(Model):
timestamp = DateTimeField() timestamp = DateTimeField()
camera = CharField(index=True, max_length=20) camera = CharField(index=True, max_length=20)
source = CharField(index=True, max_length=20) # ex: tracked object, audio, external source = CharField(index=True, max_length=20) # ex: tracked object, audio, external
@ -60,13 +60,13 @@ class Timeline(Model): # type: ignore[misc]
data = JSONField() # ex: tracked object id, region, box, etc. data = JSONField() # ex: tracked object id, region, box, etc.
class Regions(Model): # type: ignore[misc] class Regions(Model):
camera = CharField(null=False, primary_key=True, max_length=20) camera = CharField(null=False, primary_key=True, max_length=20)
grid = JSONField() # json blob of grid grid = JSONField() # json blob of grid
last_update = DateTimeField() last_update = DateTimeField()
class Recordings(Model): # type: ignore[misc] class Recordings(Model):
id = CharField(null=False, primary_key=True, max_length=30) id = CharField(null=False, primary_key=True, max_length=30)
camera = CharField(index=True, max_length=20) camera = CharField(index=True, max_length=20)
path = CharField(unique=True) path = CharField(unique=True)
@ -80,7 +80,7 @@ class Recordings(Model): # type: ignore[misc]
regions = IntegerField(null=True) regions = IntegerField(null=True)
class Export(Model): # type: ignore[misc] class Export(Model):
id = CharField(null=False, primary_key=True, max_length=30) id = CharField(null=False, primary_key=True, max_length=30)
camera = CharField(index=True, max_length=20) camera = CharField(index=True, max_length=20)
name = CharField(index=True, max_length=100) name = CharField(index=True, max_length=100)
@ -90,7 +90,7 @@ class Export(Model): # type: ignore[misc]
in_progress = BooleanField() in_progress = BooleanField()
class ReviewSegment(Model): # type: ignore[misc] class ReviewSegment(Model):
id = CharField(null=False, primary_key=True, max_length=30) id = CharField(null=False, primary_key=True, max_length=30)
camera = CharField(index=True, max_length=20) camera = CharField(index=True, max_length=20)
start_time = DateTimeField() start_time = DateTimeField()
@ -100,7 +100,7 @@ class ReviewSegment(Model): # type: ignore[misc]
data = JSONField() # additional data about detection like list of labels, zone, areas of significant motion data = JSONField() # additional data about detection like list of labels, zone, areas of significant motion
class UserReviewStatus(Model): # type: ignore[misc] class UserReviewStatus(Model):
user_id = CharField(max_length=30) user_id = CharField(max_length=30)
review_segment = ForeignKeyField(ReviewSegment, backref="user_reviews") review_segment = ForeignKeyField(ReviewSegment, backref="user_reviews")
has_been_reviewed = BooleanField(default=False) has_been_reviewed = BooleanField(default=False)
@ -109,7 +109,7 @@ class UserReviewStatus(Model): # type: ignore[misc]
indexes = ((("user_id", "review_segment"), True),) indexes = ((("user_id", "review_segment"), True),)
class Previews(Model): # type: ignore[misc] class Previews(Model):
id = CharField(null=False, primary_key=True, max_length=30) id = CharField(null=False, primary_key=True, max_length=30)
camera = CharField(index=True, max_length=20) camera = CharField(index=True, max_length=20)
path = CharField(unique=True) path = CharField(unique=True)
@ -119,14 +119,14 @@ class Previews(Model): # type: ignore[misc]
# Used for temporary table in record/cleanup.py # Used for temporary table in record/cleanup.py
class RecordingsToDelete(Model): # type: ignore[misc] class RecordingsToDelete(Model):
id = CharField(null=False, primary_key=False, max_length=30) id = CharField(null=False, primary_key=False, max_length=30)
class Meta: class Meta:
temporary = True temporary = True
class User(Model): # type: ignore[misc] class User(Model):
username = CharField(null=False, primary_key=True, max_length=30) username = CharField(null=False, primary_key=True, max_length=30)
role = CharField( role = CharField(
max_length=20, max_length=20,
@ -136,7 +136,7 @@ class User(Model): # type: ignore[misc]
notification_tokens = JSONField() notification_tokens = JSONField()
class Trigger(Model): # type: ignore[misc] class Trigger(Model):
camera = CharField(max_length=20) camera = CharField(max_length=20)
name = CharField() name = CharField()
type = CharField(max_length=10) type = CharField(max_length=10)