TEST-BE-PR-001 fix calendar schema import recursion

This commit is contained in:
zhi
2026-04-01 10:04:50 +00:00
parent 2cc07b9c3e
commit 45ab4583de

View File

@@ -9,10 +9,10 @@ BE-CAL-API-004: TimeSlot cancel schemas.
from __future__ import annotations from __future__ import annotations
from datetime import date, time, datetime from datetime import date as dt_date, time as dt_time, datetime as dt_datetime
from enum import Enum from enum import Enum
from pydantic import BaseModel, Field, model_validator, field_validator from pydantic import BaseModel, Field, model_validator, field_validator
from typing import Any, Optional from typing import Optional
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -102,17 +102,17 @@ class SlotStatusEnum(str, Enum):
class TimeSlotCreate(BaseModel): class TimeSlotCreate(BaseModel):
"""Request body for creating a single calendar slot.""" """Request body for creating a single calendar slot."""
date: Optional[date] = Field(None, description="Target date (defaults to today)") date: Optional[dt_date] = Field(None, description="Target date (defaults to today)")
slot_type: SlotTypeEnum = Field(..., description="work | on_call | entertainment | system") slot_type: SlotTypeEnum = Field(..., description="work | on_call | entertainment | system")
scheduled_at: time = Field(..., description="Planned start time HH:MM (00:00-23:00)") scheduled_at: dt_time = Field(..., description="Planned start time HH:MM (00:00-23:00)")
estimated_duration: int = Field(..., ge=1, le=50, description="Duration in minutes (1-50)") estimated_duration: int = Field(..., ge=1, le=50, description="Duration in minutes (1-50)")
event_type: Optional[EventTypeEnum] = Field(None, description="job | entertainment | system_event") event_type: Optional[EventTypeEnum] = Field(None, description="job | entertainment | system_event")
event_data: Optional[dict[str, Any]] = Field(None, description="Event details JSON") event_data: Optional[dict] = Field(None, description="Event details JSON")
priority: int = Field(0, ge=0, le=99, description="Priority 0-99") priority: int = Field(0, ge=0, le=99, description="Priority 0-99")
@field_validator("scheduled_at") @field_validator("scheduled_at")
@classmethod @classmethod
def _validate_scheduled_at(cls, v: time) -> time: def _validate_scheduled_at(cls, v: dt_time) -> dt_time:
if v.hour > 23: if v.hour > 23:
raise ValueError("scheduled_at hour must be between 00 and 23") raise ValueError("scheduled_at hour must be between 00 and 23")
return v return v
@@ -132,7 +132,7 @@ class TimeSlotResponse(BaseModel):
"""Response for a single TimeSlot.""" """Response for a single TimeSlot."""
id: int id: int
user_id: int user_id: int
date: date date: dt_date
slot_type: str slot_type: str
estimated_duration: int estimated_duration: int
scheduled_at: str # HH:MM:SS ISO format scheduled_at: str # HH:MM:SS ISO format
@@ -140,12 +140,12 @@ class TimeSlotResponse(BaseModel):
attended: bool attended: bool
actual_duration: Optional[int] = None actual_duration: Optional[int] = None
event_type: Optional[str] = None event_type: Optional[str] = None
event_data: Optional[dict[str, Any]] = None event_data: Optional[dict] = None
priority: int priority: int
status: str status: str
plan_id: Optional[int] = None plan_id: Optional[int] = None
created_at: Optional[datetime] = None created_at: Optional[dt_datetime] = None
updated_at: Optional[datetime] = None updated_at: Optional[dt_datetime] = None
class Config: class Config:
from_attributes = True from_attributes = True
@@ -169,15 +169,15 @@ class TimeSlotEdit(BaseModel):
``virtual_id`` (for plan-generated virtual slots) in the URL path. ``virtual_id`` (for plan-generated virtual slots) in the URL path.
""" """
slot_type: Optional[SlotTypeEnum] = Field(None, description="New slot type") slot_type: Optional[SlotTypeEnum] = Field(None, description="New slot type")
scheduled_at: Optional[time] = Field(None, description="New start time HH:MM") scheduled_at: Optional[dt_time] = Field(None, description="New start time HH:MM")
estimated_duration: Optional[int] = Field(None, ge=1, le=50, description="New duration in minutes (1-50)") estimated_duration: Optional[int] = Field(None, ge=1, le=50, description="New duration in minutes (1-50)")
event_type: Optional[EventTypeEnum] = Field(None, description="New event type") event_type: Optional[EventTypeEnum] = Field(None, description="New event type")
event_data: Optional[dict[str, Any]] = Field(None, description="New event details JSON") event_data: Optional[dict] = Field(None, description="New event details JSON")
priority: Optional[int] = Field(None, ge=0, le=99, description="New priority 0-99") priority: Optional[int] = Field(None, ge=0, le=99, description="New priority 0-99")
@field_validator("scheduled_at") @field_validator("scheduled_at")
@classmethod @classmethod
def _validate_scheduled_at(cls, v: Optional[time]) -> Optional[time]: def _validate_scheduled_at(cls, v: Optional[dt_time]) -> Optional[dt_time]:
if v is not None and v.hour > 23: if v is not None and v.hour > 23:
raise ValueError("scheduled_at hour must be between 00 and 23") raise ValueError("scheduled_at hour must be between 00 and 23")
return v return v
@@ -214,7 +214,7 @@ class CalendarSlotItem(BaseModel):
id: Optional[int] = Field(None, description="Real slot DB id (None for virtual)") id: Optional[int] = Field(None, description="Real slot DB id (None for virtual)")
virtual_id: Optional[str] = Field(None, description="Virtual slot id (None for real)") virtual_id: Optional[str] = Field(None, description="Virtual slot id (None for real)")
user_id: int user_id: int
date: date date: dt_date
slot_type: str slot_type: str
estimated_duration: int estimated_duration: int
scheduled_at: str # HH:MM:SS ISO format scheduled_at: str # HH:MM:SS ISO format
@@ -222,12 +222,12 @@ class CalendarSlotItem(BaseModel):
attended: bool attended: bool
actual_duration: Optional[int] = None actual_duration: Optional[int] = None
event_type: Optional[str] = None event_type: Optional[str] = None
event_data: Optional[dict[str, Any]] = None event_data: Optional[dict] = None
priority: int priority: int
status: str status: str
plan_id: Optional[int] = None plan_id: Optional[int] = None
created_at: Optional[datetime] = None created_at: Optional[dt_datetime] = None
updated_at: Optional[datetime] = None updated_at: Optional[dt_datetime] = None
class Config: class Config:
from_attributes = True from_attributes = True
@@ -235,7 +235,7 @@ class CalendarSlotItem(BaseModel):
class CalendarDayResponse(BaseModel): class CalendarDayResponse(BaseModel):
"""Response for a single-day calendar query.""" """Response for a single-day calendar query."""
date: date date: dt_date
user_id: int user_id: int
slots: list[CalendarSlotItem] = Field( slots: list[CalendarSlotItem] = Field(
default_factory=list, default_factory=list,
@@ -290,16 +290,16 @@ class SchedulePlanCreate(BaseModel):
"""Request body for creating a recurring schedule plan.""" """Request body for creating a recurring schedule plan."""
slot_type: SlotTypeEnum = Field(..., description="work | on_call | entertainment | system") slot_type: SlotTypeEnum = Field(..., description="work | on_call | entertainment | system")
estimated_duration: int = Field(..., ge=1, le=50, description="Duration in minutes (1-50)") estimated_duration: int = Field(..., ge=1, le=50, description="Duration in minutes (1-50)")
at_time: time = Field(..., description="Daily scheduled time (HH:MM)") at_time: dt_time = Field(..., description="Daily scheduled time (HH:MM)")
on_day: Optional[DayOfWeekEnum] = Field(None, description="Day of week (sun-sat)") on_day: Optional[DayOfWeekEnum] = Field(None, description="Day of week (sun-sat)")
on_week: Optional[int] = Field(None, ge=1, le=4, description="Week of month (1-4)") on_week: Optional[int] = Field(None, ge=1, le=4, description="Week of month (1-4)")
on_month: Optional[MonthOfYearEnum] = Field(None, description="Month (jan-dec)") on_month: Optional[MonthOfYearEnum] = Field(None, description="Month (jan-dec)")
event_type: Optional[EventTypeEnum] = Field(None, description="job | entertainment | system_event") event_type: Optional[EventTypeEnum] = Field(None, description="job | entertainment | system_event")
event_data: Optional[dict[str, Any]] = Field(None, description="Event details JSON") event_data: Optional[dict] = Field(None, description="Event details JSON")
@field_validator("at_time") @field_validator("at_time")
@classmethod @classmethod
def _validate_at_time(cls, v: time) -> time: def _validate_at_time(cls, v: dt_time) -> dt_time:
if v.hour > 23: if v.hour > 23:
raise ValueError("at_time hour must be between 00 and 23") raise ValueError("at_time hour must be between 00 and 23")
return v return v
@@ -325,10 +325,10 @@ class SchedulePlanResponse(BaseModel):
on_week: Optional[int] = None on_week: Optional[int] = None
on_month: Optional[str] = None on_month: Optional[str] = None
event_type: Optional[str] = None event_type: Optional[str] = None
event_data: Optional[dict[str, Any]] = None event_data: Optional[dict] = None
is_active: bool is_active: bool
created_at: Optional[datetime] = None created_at: Optional[dt_datetime] = None
updated_at: Optional[datetime] = None updated_at: Optional[dt_datetime] = None
class Config: class Config:
from_attributes = True from_attributes = True
@@ -352,19 +352,19 @@ class SchedulePlanEdit(BaseModel):
""" """
slot_type: Optional[SlotTypeEnum] = Field(None, description="New slot type") slot_type: Optional[SlotTypeEnum] = Field(None, description="New slot type")
estimated_duration: Optional[int] = Field(None, ge=1, le=50, description="New duration in minutes (1-50)") estimated_duration: Optional[int] = Field(None, ge=1, le=50, description="New duration in minutes (1-50)")
at_time: Optional[time] = Field(None, description="New daily time (HH:MM)") at_time: Optional[dt_time] = Field(None, description="New daily time (HH:MM)")
on_day: Optional[DayOfWeekEnum] = Field(None, description="New day of week (sun-sat), use 'clear' param to remove") on_day: Optional[DayOfWeekEnum] = Field(None, description="New day of week (sun-sat), use 'clear' param to remove")
on_week: Optional[int] = Field(None, ge=1, le=4, description="New week of month (1-4), use 'clear' param to remove") on_week: Optional[int] = Field(None, ge=1, le=4, description="New week of month (1-4), use 'clear' param to remove")
on_month: Optional[MonthOfYearEnum] = Field(None, description="New month (jan-dec), use 'clear' param to remove") on_month: Optional[MonthOfYearEnum] = Field(None, description="New month (jan-dec), use 'clear' param to remove")
event_type: Optional[EventTypeEnum] = Field(None, description="New event type") event_type: Optional[EventTypeEnum] = Field(None, description="New event type")
event_data: Optional[dict[str, Any]] = Field(None, description="New event details JSON") event_data: Optional[dict] = Field(None, description="New event details JSON")
clear_on_day: bool = Field(False, description="Clear on_day (set to NULL)") clear_on_day: bool = Field(False, description="Clear on_day (set to NULL)")
clear_on_week: bool = Field(False, description="Clear on_week (set to NULL)") clear_on_week: bool = Field(False, description="Clear on_week (set to NULL)")
clear_on_month: bool = Field(False, description="Clear on_month (set to NULL)") clear_on_month: bool = Field(False, description="Clear on_month (set to NULL)")
@field_validator("at_time") @field_validator("at_time")
@classmethod @classmethod
def _validate_at_time(cls, v: Optional[time]) -> Optional[time]: def _validate_at_time(cls, v: Optional[dt_time]) -> Optional[dt_time]:
if v is not None and v.hour > 23: if v is not None and v.hour > 23:
raise ValueError("at_time hour must be between 00 and 23") raise ValueError("at_time hour must be between 00 and 23")
return v return v
@@ -403,7 +403,7 @@ class DateListResponse(BaseModel):
Returns only dates that have at least one materialized (real) future Returns only dates that have at least one materialized (real) future
slot. Pure plan-generated (virtual) dates are excluded. slot. Pure plan-generated (virtual) dates are excluded.
""" """
dates: list[date] = Field( dates: list[dt_date] = Field(
default_factory=list, default_factory=list,
description="Sorted list of future dates with materialized slots", description="Sorted list of future dates with materialized slots",
) )