Files
workout_watcher/services/logic/app/schemas.py
T
Artem Kashaev 800dee31b2 Add boto3 dependency and update exercise/machine assets
- Added boto3 as a dependency in pyproject.toml and uv.lock.
- Introduced multiple new exercise images in various formats (jpg, webp, avif, png).
- Added new machine images to enhance the workout assets library.
2026-05-29 15:50:33 +05:00

218 lines
5.5 KiB
Python

from __future__ import annotations
import uuid
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, model_validator
ActivityKind = Literal["exercise", "machine"]
ActivityCategory = Literal[
"chest",
"back",
"legs",
"shoulders",
"biceps",
"triceps",
"core",
"cardio",
"full_body",
"other",
]
ActivityEquipment = Literal[
"barbell",
"dumbbell",
"machine",
"cable",
"bodyweight",
"kettlebell",
"cardio_machine",
"other",
]
MeasurementType = Literal[
"weight_reps",
"reps_only",
"duration",
"distance_duration",
"duration_calories",
]
Difficulty = Literal["beginner", "intermediate", "advanced"]
class ActivitySourceCreate(BaseModel):
slug: str | None = Field(default=None, min_length=1, max_length=180)
kind: ActivityKind
title: str = Field(min_length=1, max_length=160)
description: str | None = None
category: ActivityCategory = "other"
equipment: ActivityEquipment = "other"
measurement_type: MeasurementType = "weight_reps"
difficulty: Difficulty = "intermediate"
image_s3_url: str | None = None
image_s3_key: str | None = None
default_calories_per_minute: float | None = None
class ActivitySourceRead(ActivitySourceCreate):
id: uuid.UUID
slug: str
owner_user_id: uuid.UUID | None
is_builtin: bool
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
class EquipmentCreate(BaseModel):
name: str = Field(min_length=1, max_length=160)
description: str | None = None
image_s3_url: str | None = None
image_s3_key: str | None = None
class EquipmentRead(EquipmentCreate):
id: uuid.UUID
owner_user_id: uuid.UUID | None
is_builtin: bool
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
class ExerciseCreate(BaseModel):
name: str = Field(min_length=1, max_length=160)
description: str | None = None
equipment_id: uuid.UUID | None = None
image_s3_url: str | None = None
image_s3_key: str | None = None
default_calories_per_minute: float | None = None
class ExerciseRead(ExerciseCreate):
id: uuid.UUID
owner_user_id: uuid.UUID | None
is_builtin: bool
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
class WorkoutCreate(BaseModel):
started_at: datetime | None = None
notes: str | None = None
class WorkoutUpdate(BaseModel):
finished_at: datetime | None = None
notes: str | None = None
class WorkoutFinishRequest(BaseModel):
notes: str | None = None
class WorkoutSetCreate(BaseModel):
weight: float = Field(default=0, ge=0)
reps: int = Field(default=0, ge=0)
duration_seconds: int | None = None
distance_km: float | None = Field(default=None, ge=0)
calories: float | None = None
completed_at: datetime | None = None
class WorkoutSetBatchCreate(BaseModel):
sets: list[WorkoutSetCreate] = Field(min_length=1)
class WorkoutSetUpdate(BaseModel):
weight: float | None = Field(default=None, ge=0)
reps: int | None = Field(default=None, ge=0)
duration_seconds: int | None = None
distance_km: float | None = Field(default=None, ge=0)
calories: float | None = None
completed_at: datetime | None = None
@model_validator(mode="after")
def at_least_one_field(self) -> WorkoutSetUpdate:
if not self.model_fields_set:
raise ValueError("Provide at least one field to update")
return self
class WorkoutSetRead(WorkoutSetCreate):
id: uuid.UUID
workout_item_id: uuid.UUID
set_index: int
completed_at: datetime
model_config = ConfigDict(from_attributes=True)
class WorkoutItemCreate(BaseModel):
activity_source_id: uuid.UUID | None = None
exercise_id: uuid.UUID | None = None
equipment_id: uuid.UUID | None = None
order_index: int | None = None
planned_working_weight: float | None = None
@model_validator(mode="after")
def exactly_one_entity(self) -> WorkoutItemCreate:
provided = [self.activity_source_id, self.exercise_id, self.equipment_id]
if sum(value is not None for value in provided) != 1:
raise ValueError("Provide exactly one activity source, exercise, or equipment id")
return self
class WorkoutItemRead(WorkoutItemCreate):
id: uuid.UUID
workout_id: uuid.UUID
source_kind: Literal["exercise", "machine", "equipment"]
title_snapshot: str
image_s3_url_snapshot: str | None
measurement_type_snapshot: MeasurementType
category_snapshot: ActivityCategory
equipment_snapshot: ActivityEquipment
order_index: int
created_at: datetime
sets: list[WorkoutSetRead] = []
model_config = ConfigDict(from_attributes=True)
class WorkoutRead(BaseModel):
id: uuid.UUID
user_id: uuid.UUID
status: Literal["active", "finished", "discarded"]
started_at: datetime
finished_at: datetime | None
notes: str | None
total_sets: int
total_volume: float
estimated_calories: float
created_at: datetime
updated_at: datetime
items: list[WorkoutItemRead] = []
model_config = ConfigDict(from_attributes=True)
class ProgressionPoint(BaseModel):
date: str
max_weight: float
volume: float
class ProgressionRead(BaseModel):
last_weight: float | None
max_weight: float | None
previous_delta: float | None
points: list[ProgressionPoint]
class CaloriesRead(BaseModel):
total_calories: float
workouts: list[dict[str, str | float]]