mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-27 17:17:40 +03:00
12 KiB
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
Development Container (Recommended)
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
- Fork and Clone: Fork the repository and clone locally
- Branch: Create feature branches from master
- Develop: Follow coding standards and patterns
- Test: Write and run tests for new features
- Document: Update documentation as needed
- Pull Request: Submit PR with clear description
- Review: Address review feedback promptly
Common Development Tasks
Adding a New Detector
- Create detector plugin in
frigate/detectors/plugins/ - Add configuration model in
frigate/detectors/detector_config.py - Update detector factory in
frigate/detectors/__init__.py - Add tests and documentation
Adding a New API Endpoint
- Define request/response models with Pydantic
- Create endpoint in appropriate router module
- Add authentication/authorization if needed
- Write tests for the endpoint
- Update API documentation
Adding a New React Component
- Create component in appropriate directory
- Define TypeScript interfaces for props
- Implement with proper error handling
- Add to component exports
- Write tests and stories (if using Storybook)
This development guide provides the foundation for contributing to Frigate while maintaining code quality and architectural consistency.