"""Stays (accommodation) related models for Duffel API."""
from datetime import datetime
from pydantic import BaseModel, Field, field_validator, ConfigDict
from .common import ResponseFormat
class GeographicLocation(BaseModel):
"""Geographic coordinates for accommodation search."""
model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True, extra='forbid')
latitude: float = Field(
...,
description="Latitude coordinate (-90 to 90)",
ge=-90.0,
le=90.0
)
longitude: float = Field(
...,
description="Longitude coordinate (-180 to 180)",
ge=-180.0,
le=180.0
)
radius: int = Field(
...,
description="Search radius in kilometers (1-100)",
ge=1,
le=100
)
class AccommodationGuest(BaseModel):
"""Guest information for accommodation search."""
model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True, extra='forbid')
adult_count: int = Field(
...,
description="Number of adult guests (18+)",
ge=1,
le=20
)
child_count: int | None = Field(
default=0,
description="Number of child guests (under 18)",
ge=0,
le=20
)
class SearchAccommodationInput(BaseModel):
"""Input for searching accommodations."""
model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True, extra='forbid')
check_in_date: str = Field(
...,
description="Check-in date in YYYY-MM-DD format (e.g., '2025-12-25')"
)
check_out_date: str = Field(
...,
description="Check-out date in YYYY-MM-DD format (e.g., '2025-12-27')"
)
guests: AccommodationGuest = Field(
...,
description="Guest count for the accommodation"
)
rooms: int = Field(
default=1,
description="Number of rooms needed",
ge=1,
le=10
)
location: GeographicLocation | None = Field(
default=None,
description="Geographic location (latitude, longitude, radius) for search"
)
accommodation_ids: list[str] | None = Field(
default=None,
description="Specific accommodation IDs to search (alternative to location)"
)
response_format: ResponseFormat = Field(
default=ResponseFormat.MARKDOWN,
description="Output format: 'json' for raw data or 'markdown' for readable summary"
)
@field_validator('check_in_date', 'check_out_date')
@classmethod
def validate_date_format(cls, v: str) -> str:
"""Validate date is in correct format and not in the past."""
try:
date = datetime.strptime(v, "%Y-%m-%d").date()
if date < datetime.now().date():
raise ValueError("Date cannot be in the past")
return v
except ValueError as e:
raise ValueError(f"Invalid date format. Use YYYY-MM-DD: {e}")
class GetAccommodationInput(BaseModel):
"""Input for retrieving accommodation details."""
model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True, extra='forbid')
accommodation_id: str = Field(
...,
description="Accommodation ID (e.g., 'acc_00009htYpSCXrwaB9DnUm0')",
pattern="^acc_[a-zA-Z0-9]+$"
)
response_format: ResponseFormat = Field(
default=ResponseFormat.MARKDOWN,
description="Output format: 'json' for raw data or 'markdown' for readable summary"
)
class GetAccommodationRatesInput(BaseModel):
"""Input for getting rates for a search result."""
model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True, extra='forbid')
search_result_id: str = Field(
...,
description="Search result ID from accommodation search",
pattern="^srs_[a-zA-Z0-9]+$"
)
response_format: ResponseFormat = Field(
default=ResponseFormat.MARKDOWN,
description="Output format: 'json' for raw data or 'markdown' for readable summary"
)
class CreateStaysQuoteInput(BaseModel):
"""Input for creating a stays quote."""
model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True, extra='forbid')
rate_id: str = Field(
...,
description="Rate ID from accommodation rates (e.g., 'rat_00009htYpSCXrwaB9DnUm0')",
pattern="^rat_[a-zA-Z0-9]+$"
)
response_format: ResponseFormat = Field(
default=ResponseFormat.MARKDOWN,
description="Output format: 'json' for raw data or 'markdown' for readable summary"
)
class StaysGuestDetails(BaseModel):
"""Guest details for stays booking."""
model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True, extra='forbid')
given_name: str = Field(
...,
description="First/given name (e.g., 'John')",
min_length=1,
max_length=50
)
family_name: str = Field(
...,
description="Last/family name (e.g., 'Smith')",
min_length=1,
max_length=50
)
email: str = Field(
...,
description="Email address for booking confirmation"
)
phone_number: str | None = Field(
default=None,
description="Phone number with country code (e.g., '+14155551234')"
)
class CreateStaysBookingInput(BaseModel):
"""Input for creating a stays booking."""
model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True, extra='forbid')
quote_id: str = Field(
...,
description="Quote ID from stays quote (e.g., 'quo_00009htYpSCXrwaB9DnUm0')",
pattern="^quo_[a-zA-Z0-9]+$"
)
guests: list[StaysGuestDetails] = Field(
...,
description="Guest details (at least one guest required)",
min_length=1,
max_length=20
)
special_requests: str | None = Field(
default=None,
description="Special requests for the accommodation",
max_length=500
)
response_format: ResponseFormat = Field(
default=ResponseFormat.MARKDOWN,
description="Output format: 'json' for raw data or 'markdown' for readable summary"
)
class GetStaysBookingInput(BaseModel):
"""Input for retrieving stays booking details."""
model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True, extra='forbid')
booking_id: str = Field(
...,
description="Stays booking ID (e.g., 'bok_00009htYpSCXrwaB9DnUm0')",
pattern="^bok_[a-zA-Z0-9]+$"
)
response_format: ResponseFormat = Field(
default=ResponseFormat.MARKDOWN,
description="Output format: 'json' for raw data or 'markdown' for readable summary"
)