Add ruff linter/formatter for Python

- Add ruff as dev dependency
- Configure ruff in pyproject.toml with strict 88-char line limit
- Ignore B008 (FastAPI Depends pattern is standard)
- Allow longer lines in tests for readability
- Fix all lint issues in source files
- Add Makefile targets: lint-backend, format-backend, fix-backend
This commit is contained in:
counterweight 2025-12-21 21:54:26 +01:00
parent 69bc8413e0
commit 6c218130e9
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
31 changed files with 1234 additions and 876 deletions

View file

@ -1,5 +1,6 @@
"""Pydantic schemas for API request/response models."""
from datetime import datetime, date, time
from datetime import date, datetime, time
from typing import Generic, TypeVar
from pydantic import BaseModel, EmailStr, field_validator
@ -9,6 +10,7 @@ from shared_constants import NOTE_MAX_LENGTH
class UserCredentials(BaseModel):
"""Base model for user email/password."""
email: EmailStr
password: str
@ -19,6 +21,7 @@ UserLogin = UserCredentials
class UserResponse(BaseModel):
"""Response model for authenticated user info."""
id: int
email: str
roles: list[str]
@ -27,6 +30,7 @@ class UserResponse(BaseModel):
class RegisterWithInvite(BaseModel):
"""Request model for registration with invite."""
email: EmailStr
password: str
invite_identifier: str
@ -34,12 +38,14 @@ class RegisterWithInvite(BaseModel):
class SumRequest(BaseModel):
"""Request model for sum calculation."""
a: float
b: float
class SumResponse(BaseModel):
"""Response model for sum calculation."""
a: float
b: float
result: float
@ -47,6 +53,7 @@ class SumResponse(BaseModel):
class CounterRecordResponse(BaseModel):
"""Response model for a counter audit record."""
id: int
user_email: str
value_before: int
@ -56,6 +63,7 @@ class CounterRecordResponse(BaseModel):
class SumRecordResponse(BaseModel):
"""Response model for a sum audit record."""
id: int
user_email: str
a: float
@ -69,6 +77,7 @@ RecordT = TypeVar("RecordT", bound=BaseModel)
class PaginatedResponse(BaseModel, Generic[RecordT]):
"""Generic paginated response wrapper."""
records: list[RecordT]
total: int
page: int
@ -82,6 +91,7 @@ PaginatedSumRecords = PaginatedResponse[SumRecordResponse]
class ProfileResponse(BaseModel):
"""Response model for profile data."""
contact_email: str | None
telegram: str | None
signal: str | None
@ -91,6 +101,7 @@ class ProfileResponse(BaseModel):
class ProfileUpdate(BaseModel):
"""Request model for updating profile."""
contact_email: str | None = None
telegram: str | None = None
signal: str | None = None
@ -99,6 +110,7 @@ class ProfileUpdate(BaseModel):
class InviteCheckResponse(BaseModel):
"""Response for invite check endpoint."""
valid: bool
status: str | None = None
error: str | None = None
@ -106,11 +118,13 @@ class InviteCheckResponse(BaseModel):
class InviteCreate(BaseModel):
"""Request model for creating an invite."""
godfather_id: int
class InviteResponse(BaseModel):
"""Response model for invite data (admin view)."""
id: int
identifier: str
godfather_id: int
@ -125,6 +139,7 @@ class InviteResponse(BaseModel):
class UserInviteResponse(BaseModel):
"""Response model for a user's invite (simpler than admin view)."""
id: int
identifier: str
status: str
@ -138,6 +153,7 @@ PaginatedInviteRecords = PaginatedResponse[InviteResponse]
class AdminUserResponse(BaseModel):
"""Minimal user info for admin dropdowns."""
id: int
email: str
@ -146,11 +162,13 @@ class AdminUserResponse(BaseModel):
# Availability Schemas
# =============================================================================
class TimeSlot(BaseModel):
"""A single time slot (start and end time)."""
start_time: time
end_time: time
@field_validator("start_time", "end_time")
@classmethod
def validate_15min_boundary(cls, v: time) -> time:
@ -164,23 +182,27 @@ class TimeSlot(BaseModel):
class AvailabilityDay(BaseModel):
"""Availability for a single day."""
date: date
slots: list[TimeSlot]
class AvailabilityResponse(BaseModel):
"""Response model for availability query."""
days: list[AvailabilityDay]
class SetAvailabilityRequest(BaseModel):
"""Request to set availability for a specific date."""
date: date
slots: list[TimeSlot]
class CopyAvailabilityRequest(BaseModel):
"""Request to copy availability from one day to others."""
source_date: date
target_dates: list[date]
@ -189,20 +211,24 @@ class CopyAvailabilityRequest(BaseModel):
# Booking Schemas
# =============================================================================
class BookableSlot(BaseModel):
"""A bookable 15-minute slot."""
start_time: datetime
end_time: datetime
class AvailableSlotsResponse(BaseModel):
"""Response for available slots on a given date."""
date: date
slots: list[BookableSlot]
class BookingRequest(BaseModel):
"""Request to book an appointment."""
slot_start: datetime
note: str | None = None
@ -216,6 +242,7 @@ class BookingRequest(BaseModel):
class AppointmentResponse(BaseModel):
"""Response model for an appointment."""
id: int
user_id: int
user_email: str
@ -234,8 +261,10 @@ PaginatedAppointments = PaginatedResponse[AppointmentResponse]
# Meta/Constants Schemas
# =============================================================================
class ConstantsResponse(BaseModel):
"""Response model for shared constants."""
permissions: list[str]
roles: list[str]
invite_statuses: list[str]