resa-padel/resa_padel/models.py

490 lines
14 KiB
Python

from enum import Enum
from pathlib import Path
from typing import Optional
from urllib.parse import urljoin
import config
import pendulum
from exceptions import MissingProperty
from pendulum import Date, Time
from pydantic import BaseModel, ConfigDict, Field, field_validator
from pydantic_extra_types.pendulum_dt import DateTime
class User(BaseModel):
login: str
password: str = Field(repr=False)
club_id: Optional[str] = Field(default=None)
class BookingOpening(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
days_before: Optional[int] = Field(default=7, alias="daysBefore")
opening_time: Optional[str] = Field(alias="time", default=None, repr=False)
time_after_booking: Optional[str] = Field(
alias="timeAfterBookingTime", default=None, repr=False
)
def __repr__(self):
base = super().__repr__()
time = f", time: {self.time})" if self.time else ""
time_after_booking = (
f", time_after_booking_time: {self.time_after_booking_time})"
if self.time_after_booking_time
else ""
)
return base.removesuffix(")") + time + time_after_booking
@property
def time(self):
return pendulum.parse(self.opening_time).time()
@property
def time_after_booking_time(self):
return (
pendulum.parse(self.time_after_booking).time()
if self.time_after_booking
else None
)
class TotalBookings(BaseModel):
peak_hours: int | str = Field(alias="peakHours")
off_peak_hours: int | str = Field(alias="offPeakHours")
class Court(BaseModel):
id: int
name: str
number: int
is_indoor: Optional[bool] = Field(alias="isIndoor")
class Sport(BaseModel):
name: str
id: int
duration: int
price: int
players: int
courts: list[Court]
class Url(BaseModel):
name: str
path: str
parameter: Optional[str] = Field(default=None)
payload_template: Optional[str] = Field(default=None, alias="payloadTemplate")
class BookingPlatform(BaseModel):
id: str
club_id: int = Field(alias="clubId")
url: str
hours_before_cancellation: int = Field(alias="hoursBeforeCancellation")
booking_opening: BookingOpening = Field(alias="bookingOpening")
total_bookings: TotalBookings = Field(alias="totalBookings")
sports: list[Sport]
urls: dict[str, Url]
def get_url_path(self, name: str) -> str:
"""
Get the URL path for the service with the given name
:param name: the name of the service
:return: the URL path
"""
self.check_url_path_exists(name)
return urljoin(self.url, self.urls.get(name).path)
def get_payload_template(self, name: str) -> Path:
"""
Get the path to the template file for the service with the given name
:param name: the name of the service
:return: the path to the template file
"""
self.check_payload_template_exists(name)
return config.get_resources_folder() / self.urls.get(name).payload_template
def get_url_parameter(self, name: str) -> str:
self.check_url_path_exists(name)
return self.urls.get(name).parameter
def check_url_path_exists(self, name: str) -> None:
"""
Check that the URL path for the given service is defined
:param name: the name of the service
"""
if (
self.urls is None
or self.urls.get(name) is None
or self.urls.get(name).path is None
):
raise MissingProperty(
f"The booking platform internal URL path for page {name} are not set"
)
def check_payload_template_exists(self, name: str) -> None:
"""
Check that the payload template for the given service is defined
:param name: the name of the service
"""
if (
self.urls is None
or self.urls.get(name) is None
or self.urls.get(name).path is None
):
raise ValueError(
f"The booking platform internal URL path for page {name} are not set"
)
@property
def landing_url(self) -> str:
"""
Get the URL to the landing page of Gestion-Sports
:return: the URL to the landing page
"""
return self.get_url_path("landing-page")
@property
def login_url(self) -> str:
"""
Get the URL to the connection login of Gestion-Sports
:return: the URL to the login page
"""
return self.get_url_path("login")
@property
def login_template(self) -> Path:
"""
Get the payload template to send to log in the website
:return: the payload template for logging in
"""
return self.get_payload_template("login")
@property
def booking_url(self) -> str:
"""
Get the URL to the booking page of Gestion-Sports
:return: the URL to the booking page
"""
return self.get_url_path("booking")
@property
def booking_template(self) -> Path:
"""
Get the payload template to send to book a court
:return: the payload template for booking a court
"""
return self.get_payload_template("booking")
@property
def user_bookings_url(self) -> str:
"""
Get the URL where all the user's bookings are available
:return: the URL to the user's bookings
"""
return self.get_url_path("user-bookings")
@property
def user_bookings_template(self) -> Path:
"""
Get the payload template to send to get all the user's bookings that are
available
:return: the payload template for the user's bookings
"""
return self.get_payload_template("user-bookings")
@property
def booking_cancellation_url(self) -> str:
"""
Get the URL where all the user's bookings are available
:return: the URL to the user's bookings
"""
return self.get_url_path("cancellation")
@property
def booking_cancel_template(self) -> Path:
"""
Get the payload template to send to get all the user's bookings that are
available
:return: the payload template for the user's bookings
"""
return self.get_payload_template("cancellation")
@property
def tournaments_sessions_url(self) -> str:
return self.get_url_path("tournament-sessions")
@property
def tournaments_sessions_template(self) -> Path:
return self.get_payload_template("tournament-sessions")
@property
def tournaments_list_url(self) -> str:
return self.get_url_path("tournaments-list")
@property
def available_sports(self) -> dict[str, Sport]:
"""
Get a dictionary of all sports, the key is the sport name lowered case
:return: the dictionary of all sports
"""
return {sport.name.lower(): sport for sport in self.sports}
class Club(BaseModel):
id: str
name: str
url: str
booking_platform: BookingPlatform = Field(alias="bookingPlatform")
@property
def landing_url(self) -> str:
"""
Get the URL to the landing page of Gestion-Sports
:return: the URL to the landing page
"""
return self.booking_platform.landing_url
@property
def login_url(self) -> str:
"""
Get the URL to the connection login of Gestion-Sports
:return: the URL to the login page
"""
return self.booking_platform.login_url
@property
def login_template(self) -> Path:
"""
Get the payload template to send to log in the website
:return: the payload template for logging in
"""
return self.booking_platform.login_template
@property
def booking_url(self) -> str:
"""
Get the URL to the booking page of Gestion-Sports
:return: the URL to the booking page
"""
return self.booking_platform.booking_url
@property
def booking_template(self) -> Path:
"""
Get the payload template to send to book a court
:return: the payload template for booking a court
"""
return self.booking_platform.booking_template
@property
def user_bookings_url(self) -> str:
"""
Get the URL where all the user's bookings are available
:return: the URL to the user's bookings
"""
return self.booking_platform.user_bookings_url
@property
def user_bookings_template(self) -> Path:
"""
Get the payload template to send to get all the user's bookings that are
available
:return: the payload template for the user's bookings
"""
return self.booking_platform.user_bookings_template
@property
def cancel_url(self) -> str:
"""
Get the URL where all the user's bookings are available
:return: the URL to the user's bookings
"""
return self.booking_platform.booking_cancellation_url
@property
def cancel_template(self) -> Path:
"""
Get the payload template to send to get all the user's bookings that are
available
:return: the payload template for the user's bookings
"""
return self.booking_platform.booking_cancel_template
@property
def sessions_url(self) -> str:
return self.booking_platform.tournaments_sessions_url
@property
def sessions_template(self) -> Path:
return self.booking_platform.tournaments_sessions_template
@property
def tournaments_url(self) -> str:
return self.booking_platform.tournaments_list_url
@property
def sports(self) -> dict[str, Sport]:
"""
Get a dictionary of all sports, the key is the sport name lowered case
:return: the dictionary of all sports
"""
return self.booking_platform.available_sports
class PlatformDefinition(BaseModel):
id: str
name: str
url: str
urls: list[Url]
class BookingFilter(BaseModel):
date: DateTime
sport_name: str
@field_validator("sport_name", mode="before")
@classmethod
def to_lower_case(cls, d: str) -> str:
return d.lower()
class Booking(BaseModel):
id: int
booking_date: DateTime = Field(alias="dateResa")
start_time: DateTime = Field(alias="startTime")
sport: str
court: str
game_creation: Optional[int] = Field(default=None, alias="creaPartie")
game_creation_limit: Optional[DateTime] = Field(
default=None, alias="limitCreaPartie"
)
cancel: Optional[bool] = Field(default=True)
block_player_replacement: Optional[int] = Field(
default=None, alias="bloquerRemplacementJoueur"
)
can_remove_parteners: bool = Field(default=True, alias="canRemovePartners")
end_time: Optional[DateTime] = Field(default=None, alias="endTime")
day_fr: Optional[str] = Field(default=None, alias="dayFr")
live_xperience_code: Optional[str] = Field(default=None, alias="codeLiveXperience")
spartime_qr_code: Optional[str] = Field(default=None, alias="qrCodeSpartime")
remaining_places: int = Field(default=3, alias="remainingPlaces")
is_captain: bool = Field(default=True, alias="isCaptain")
dt_start: Optional[DateTime] = Field(default=None, alias="dtStart")
credit_card_guaranty: Optional[str] = Field(default=None, alias="garantieCb")
certificate_validity_duration: Optional[int] = Field(
alias="dureeValidCertif", default=None
)
charge_id: Optional[str] = Field(default=None, alias="chargeId")
partners: Optional[list] = Field(default=[])
player_status: Optional[int] = Field(default=None, alias="playerStatus")
products: Optional[list] = Field(default=[])
@field_validator("booking_date", mode="before")
@classmethod
def validate_date(cls, d: str) -> DateTime:
return pendulum.from_format(
d, "DD/MM/YYYY", tz=pendulum.timezone("Europe/Paris")
)
@field_validator("start_time", "end_time", mode="before")
@classmethod
def validate_time(cls, t: str) -> DateTime:
return pendulum.from_format(t, "HH:mm", tz=pendulum.timezone("Europe/Paris"))
@field_validator("game_creation_limit", mode="before")
@classmethod
def validate_datetime_add_tz(cls, dt: str) -> DateTime:
return pendulum.parse(dt, tz=pendulum.timezone("Europe/Paris"))
@field_validator("dt_start", mode="before")
@classmethod
def validate_datetime(cls, dt: str) -> DateTime:
return pendulum.parse(dt)
@field_validator("sport", mode="before")
@classmethod
def to_lower_case(cls, d: str) -> str:
return d.lower()
def matches(self, booking_filter: BookingFilter) -> bool:
"""
Check if the booking matches the booking filter
:param booking_filter: the conditions the booking should meet
:return: true if the booking matches the conditions, false otherwise
"""
return (
self.is_same_sport(booking_filter.sport_name)
and self.is_same_date(booking_filter.date.date())
and self.is_same_time(booking_filter.date.time())
)
def is_same_sport(self, sport: str) -> bool:
"""
Check if the booking and the booking filter are about the same sport
:param sport: the sport to test
:return: true if the sport matches booking sport, false otherwise
"""
return self.sport == sport
def is_same_date(self, date: Date) -> bool:
"""
Check if the booking filter has the same date as the booking
:param date: the date to test
:return: true if the date matches the booking date, false otherwise
"""
return self.booking_date.date() == date
def is_same_time(self, time: Time) -> bool:
"""
Check if the booking filter has the same time as the booking
:param time: the time to test
:return: true if the time matches the booking time, false otherwise
"""
return self.start_time.time() == time
class Action(Enum):
BOOK = "book"
CANCEL = "cancel"
TOURNAMENTS = "tournaments"
class Tournament(BaseModel):
name: str
price: str
start_date: DateTime
end_date: DateTime
gender: str
places_left: str | int