frigate/.cursor/development.md
2025-07-04 13:07:15 +03:00

12 KiB

Frigate Development Guide

Development Environment Setup

Prerequisites

  • Docker: For containerized development
  • Python 3.11+: Backend development
  • Node.js 20+: Frontend development
  • Bun: Package manager (preferred over npm)
  • Git: Version control

The repository includes a devcontainer configuration for consistent development:

# Open in VS Code with Dev Containers extension
code .
# Select "Reopen in Container" when prompted

Local Development Setup

Backend Setup

# Install Python dependencies
cd frigate/
pip install -r requirements-dev.txt

# Set up pre-commit hooks
pre-commit install

Frontend Setup

# Install frontend dependencies
cd web/
bun install

# Start development server
PROXY_HOST=localhost:5000 bun run dev

Development Workflow

Code Style and Quality

Python

  • Formatter: Black (line length: 88)
  • Import Sorting: isort
  • Linting: flake8, pylint
  • Type Checking: mypy
  • Testing: pytest
# Format code
black frigate/
isort frigate/

# Run linting
flake8 frigate/
mypy frigate/

# Run tests
pytest frigate/test/

TypeScript/React

  • Formatter: Prettier
  • Linting: ESLint with TypeScript rules
  • Type Checking: TypeScript compiler
# Format and lint
cd web/
bun run lint
bun run format

# Type checking
bun run type-check

# Run tests
bun run test

Testing Guidelines

Backend Testing

# Test file structure: frigate/test/
# Example test file: test_config.py

import pytest
from frigate.config import FrigateConfig

def test_config_validation():
    # Test configuration validation
    pass

def test_api_endpoint():
    # Test API endpoints
    pass

# Use fixtures for common setup
@pytest.fixture
def test_config():
    return FrigateConfig.load_file("test_config.yml")

Frontend Testing

// Test file structure: web/src/**/*.test.tsx
// Example: components/Button.test.tsx

import { render, screen } from "@testing-library/react";
import { Button } from "./Button";

test("renders button with text", () => {
  render(<Button>Click me</Button>);
  expect(screen.getByText("Click me")).toBeInTheDocument();
});

Component Development Patterns

Python Process Pattern

import multiprocessing as mp
from multiprocessing.synchronize import Event as MpEvent
import logging

class MyProcess(mp.Process):
    def __init__(self, config: dict, stop_event: MpEvent):
        super().__init__()
        self.config = config
        self.stop_event = stop_event
        self.logger = logging.getLogger(__name__)

    def run(self):
        """Main process loop"""
        self.logger.info("Starting process")

        while not self.stop_event.is_set():
            try:
                # Main processing logic
                self.process_frame()

            except Exception as e:
                self.logger.error(f"Error in process: {e}")

        self.logger.info("Process stopped")

    def process_frame(self):
        """Process individual frames"""
        pass

React Component Pattern

interface ComponentProps {
  data: DataType;
  onAction: (id: string) => void;
  className?: string;
}

const Component = ({ data, onAction, className }: ComponentProps) => {
  const [loading, setLoading] = useState(false);

  // Event handler with handle prefix
  const handleSubmit = async () => {
    // Early return pattern
    if (loading) return;

    setLoading(true);
    try {
      await onAction(data.id);
    } catch (error) {
      console.error("Action failed:", error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className={cn("base-classes", className)}>
      <button
        onClick={handleSubmit}
        disabled={loading}
        className="button-classes"
        aria-label="Submit action"
      >
        {loading ? "Loading..." : "Submit"}
      </button>
    </div>
  );
};

Configuration Model Pattern

from pydantic import BaseModel, Field, field_validator
from typing import Optional

class ComponentConfig(BaseModel):
    enabled: bool = Field(default=True, description="Enable component")
    threshold: float = Field(default=0.5, ge=0.0, le=1.0)
    name: Optional[str] = Field(default=None)

    @field_validator('name')
    def validate_name(cls, v):
        if v is not None and len(v) < 1:
            raise ValueError("Name must not be empty")
        return v

    class Config:
        extra = "forbid"  # Prevent additional fields

API Development

FastAPI Endpoint Pattern

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from typing import List

router = APIRouter()

class ResponseModel(BaseModel):
    id: str
    name: str
    status: str

@router.get("/items", response_model=List[ResponseModel])
async def get_items(
    limit: int = 10,
    current_user = Depends(get_current_user)
):
    """Get list of items"""
    try:
        items = await fetch_items(limit)
        return [ResponseModel(**item) for item in items]
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.post("/items/{item_id}/action")
async def perform_action(
    item_id: str,
    current_user = Depends(get_current_user)
):
    """Perform action on item"""
    # Implementation
    return {"status": "success"}

Database Development

Model Definition

from peewee import *
from frigate.models import Model

class NewModel(Model):
    id = AutoField()
    name = CharField(max_length=100, unique=True)
    created_at = DateTimeField(default=datetime.now)
    data = JSONField()

    class Meta:
        table_name = 'new_model'
        indexes = (
            (('name', 'created_at'), False),
        )

Migration Pattern

# migrations/XXX_add_new_feature.py

def migrate(migrator, database, fake=False, **kwargs):
    """Migration logic"""
    migrator.add_column('events', 'new_field', CharField(null=True))

def rollback(migrator, database, fake=False, **kwargs):
    """Rollback logic"""
    migrator.drop_column('events', 'new_field')

Real-time Features

WebSocket Integration

// Frontend WebSocket hook
import { useWebSocket } from "@/hooks/use-websocket";

const Component = () => {
  const { socket, connected } = useWebSocket();

  useEffect(() => {
    if (!socket) return;

    const handleEvent = (data: EventData) => {
      // Handle real-time event
    };

    socket.on("event", handleEvent);
    return () => socket.off("event", handleEvent);
  }, [socket]);

  return <div>Status: {connected ? "Connected" : "Disconnected"}</div>;
};

Python WebSocket Handler

from frigate.comms.ws import WebSocket

class EventWebSocket(WebSocket):
    def on_connect(self):
        """Handle client connection"""
        self.send_json({"type": "connected"})

    def on_message(self, message):
        """Handle incoming message"""
        if message["type"] == "subscribe":
            self.subscribe_to_events()

    def send_event(self, event_data):
        """Send event to client"""
        self.send_json({
            "type": "event",
            "data": event_data
        })

Performance Optimization

Shared Memory Usage

import multiprocessing as mp
from frigate.util.image import SharedMemoryFrameManager

class FrameProcessor:
    def __init__(self):
        self.frame_manager = SharedMemoryFrameManager()

    def process_frame(self, camera_name: str, frame_data: bytes):
        # Store frame in shared memory
        frame_id = self.frame_manager.create(
            camera_name,
            frame_data.shape,
            frame_data.dtype
        )

        # Copy frame data
        shm_frame = self.frame_manager.get(frame_id)
        shm_frame[:] = frame_data

        return frame_id

Frontend Performance

// Use React.memo for expensive components
const ExpensiveComponent = React.memo(({ data }: Props) => {
  return <div>{/* Expensive rendering */}</div>;
});

// Use useMemo for expensive calculations
const ProcessedData = ({ rawData }: Props) => {
  const processedData = useMemo(() => {
    return expensiveProcessing(rawData);
  }, [rawData]);

  return <div>{processedData}</div>;
};

// Use useCallback for stable references
const Parent = () => {
  const handleAction = useCallback((id: string) => {
    // Handle action
  }, []);

  return <Child onAction={handleAction} />;
};

Debugging

Python Debugging

import logging
import pdb

# Set up detailed logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

def debug_function():
    logger.debug("Debug information")

    # Set breakpoint for debugging
    pdb.set_trace()

    # Process continues...

Frontend Debugging

// Use React Developer Tools
// Add debugging props in development
const DebugComponent = ({ data }: Props) => {
  useEffect(() => {
    if (process.env.NODE_ENV === "development") {
      console.log("Component data:", data);
    }
  }, [data]);

  return <div>{/* Component content */}</div>;
};

Docker Development

Development Dockerfile

FROM frigate:dev as dev

# Install development dependencies
RUN pip install debugpy pytest-cov

# Enable development features
ENV PYTHONPATH=/workspace/frigate
ENV FRIGATE_CONFIG_FILE=/config/config.yml

# Expose debug port
EXPOSE 5678

CMD ["python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "--wait-for-client", "-m", "frigate"]

Docker Compose for Development

version: "3.8"
services:
  frigate:
    build:
      context: .
      dockerfile: docker/main/Dockerfile
      target: devcontainer
    volumes:
      - .:/workspace/frigate
      - ./config:/config
    ports:
      - "5000:5000" # Web interface
      - "5678:5678" # Debug port
    environment:
      - PYTHONPATH=/workspace/frigate

Integration Testing

Test Configuration

# test_config.yml
cameras:
  test_camera:
    ffmpeg:
      inputs:
        - path: /dev/video0
          roles: [detect]
    detect:
      enabled: true

detectors:
  cpu:
    type: cpu

End-to-End Testing

import pytest
from frigate.app import FrigateApp
from frigate.config import FrigateConfig

@pytest.fixture
def test_app():
    config = FrigateConfig.load_file("test_config.yml")
    app = FrigateApp(config)
    yield app
    app.stop()

def test_detection_pipeline(test_app):
    # Test full detection pipeline
    pass

Contributing Guidelines

  1. Fork and Clone: Fork the repository and clone locally
  2. Branch: Create feature branches from master
  3. Develop: Follow coding standards and patterns
  4. Test: Write and run tests for new features
  5. Document: Update documentation as needed
  6. Pull Request: Submit PR with clear description
  7. Review: Address review feedback promptly

Common Development Tasks

Adding a New Detector

  1. Create detector plugin in frigate/detectors/plugins/
  2. Add configuration model in frigate/detectors/detector_config.py
  3. Update detector factory in frigate/detectors/__init__.py
  4. Add tests and documentation

Adding a New API Endpoint

  1. Define request/response models with Pydantic
  2. Create endpoint in appropriate router module
  3. Add authentication/authorization if needed
  4. Write tests for the endpoint
  5. Update API documentation

Adding a New React Component

  1. Create component in appropriate directory
  2. Define TypeScript interfaces for props
  3. Implement with proper error handling
  4. Add to component exports
  5. Write tests and stories (if using Storybook)

This development guide provides the foundation for contributing to Frigate while maintaining code quality and architectural consistency.