88from otf_api import exceptions as exc
99from otf_api import models
1010from otf_api .api import utils
11- from otf_api .api .client import OtfClient
1211from otf_api .models .bookings import HISTORICAL_BOOKING_STATUSES , ClassFilter
1312
1413from .booking_client import BookingClient
1514
1615if typing .TYPE_CHECKING :
1716 from otf_api import Otf
17+ from otf_api .api .client import OtfClient
1818
1919LOGGER = getLogger (__name__ )
2020
2121
2222class BookingApi :
23- def __init__ (self , otf : "Otf" , otf_client : OtfClient ):
23+ def __init__ (self , otf : "Otf" , otf_client : " OtfClient" ):
2424 """Initialize the Booking API client.
2525
2626 Args:
@@ -30,6 +30,39 @@ def __init__(self, otf: "Otf", otf_client: OtfClient):
3030 self .otf = otf
3131 self .client = BookingClient (otf_client )
3232
33+ def _get_all_bookings_new (
34+ self , exclude_cancelled : bool = True , remove_duplicates : bool = True
35+ ) -> list [models .BookingV2 ]:
36+ """Get bookings from the new endpoint with no date filters.
37+
38+ This is marked as private to avoid random users calling it.
39+ Useful for testing and validating models.
40+
41+ Args:
42+ exclude_cancelled (bool): Whether to exclude cancelled bookings. Default is True.
43+ remove_duplicates (bool): Whether to remove duplicate bookings. Default is True.
44+
45+ Returns:
46+ list[BookingV2]: List of bookings that match the search criteria.
47+ """
48+ start_date = pendulum .datetime (1970 , 1 , 1 )
49+ end_date = pendulum .today ().start_of ("day" ).add (days = 45 )
50+ return self .get_bookings_new (start_date , end_date , exclude_cancelled , remove_duplicates )
51+
52+ def _get_all_bookings_new_by_date (self ) -> dict [datetime , models .BookingV2 ]:
53+ """Get all bookings from the new endpoint by date.
54+
55+ This is marked as private to avoid random users calling it.
56+ Useful for testing and validating models.
57+
58+ Returns:
59+ dict[datetime, BookingV2]: Dictionary of bookings by date.
60+ """
61+ start_date = pendulum .datetime (1970 , 1 , 1 )
62+ end_date = pendulum .today ().start_of ("day" ).add (days = 45 )
63+ bookings = self .get_bookings_new_by_date (start_date , end_date )
64+ return bookings
65+
3366 def get_bookings_new (
3467 self ,
3568 start_date : datetime | date | str | None = None ,
@@ -79,6 +112,7 @@ def get_bookings_new(
79112 bookings_resp = self .client .get_bookings_new (
80113 ends_before = end_date , starts_after = start_date , include_canceled = include_canceled , expand = expand
81114 )
115+ LOGGER .debug ("Found %d bookings between %s and %s" , len (bookings_resp ), start_date , end_date )
82116
83117 # filter out bookings with ids that start with "no-booking-id"
84118 # no idea what these are, but I am praying for the poor sap stuck with maintaining OTF's data model
@@ -89,7 +123,7 @@ def get_bookings_new(
89123 try :
90124 results .append (models .BookingV2 .create (** b , api = self .otf ))
91125 except ValueError as e :
92- LOGGER .warning ( f "Failed to create BookingV2 from response: { e } . Booking data:\n { b } " )
126+ LOGGER .error ( "Failed to create BookingV2 from response: %s . Booking data:\n %s" , e , b )
93127 continue
94128
95129 if not remove_duplicates :
@@ -112,6 +146,9 @@ def _deduplicate_bookings(
112146 list[BookingV2]: The deduplicated list of bookings.
113147 """
114148 # remove duplicates by class_id, keeping the one with the most recent updated_at timestamp
149+
150+ orig_count = len (results )
151+
115152 seen_classes : dict [str , models .BookingV2 ] = {}
116153
117154 for booking in results :
@@ -127,11 +164,20 @@ def _deduplicate_bookings(
127164 "this is unexpected behavior."
128165 )
129166 if booking .updated_at > existing_booking .updated_at :
167+ LOGGER .debug (
168+ "Replacing existing booking for class_id %s with more recent booking %s" , class_id , booking
169+ )
130170 seen_classes [class_id ] = booking
131171
132172 results = list (seen_classes .values ())
133173 results = sorted (results , key = lambda x : x .starts_at )
134174
175+ new_count = len (results )
176+ diff = orig_count - new_count
177+
178+ if diff :
179+ LOGGER .debug ("Removed %d duplicate bookings, returning %d unique bookings" , diff , new_count )
180+
135181 return results
136182
137183 def get_bookings_new_by_date (
@@ -615,36 +661,3 @@ def rate_class(
615661 if e .response .status_code == 403 :
616662 raise exc .AlreadyRatedError (f"Workout { performance_summary_id } is already rated." ) from None
617663 raise
618-
619- def _get_all_bookings_new (
620- self , exclude_cancelled : bool = True , remove_duplicates : bool = True
621- ) -> list [models .BookingV2 ]:
622- """Get bookings from the new endpoint with no date filters.
623-
624- This is marked as private to avoid random users calling it.
625- Useful for testing and validating models.
626-
627- Args:
628- exclude_cancelled (bool): Whether to exclude cancelled bookings. Default is True.
629- remove_duplicates (bool): Whether to remove duplicate bookings. Default is True.
630-
631- Returns:
632- list[BookingV2]: List of bookings that match the search criteria.
633- """
634- start_date = pendulum .datetime (1970 , 1 , 1 )
635- end_date = pendulum .today ().start_of ("day" ).add (days = 45 )
636- return self .get_bookings_new (start_date , end_date , exclude_cancelled , remove_duplicates )
637-
638- def _get_all_bookings_new_by_date (self ) -> dict [datetime , models .BookingV2 ]:
639- """Get all bookings from the new endpoint by date.
640-
641- This is marked as private to avoid random users calling it.
642- Useful for testing and validating models.
643-
644- Returns:
645- dict[datetime, BookingV2]: Dictionary of bookings by date.
646- """
647- start_date = pendulum .datetime (1970 , 1 , 1 )
648- end_date = pendulum .today ().start_of ("day" ).add (days = 45 )
649- bookings = self .get_bookings_new_by_date (start_date , end_date )
650- return bookings
0 commit comments