bookalimo 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bookalimo/wrapper.py ADDED
@@ -0,0 +1,396 @@
1
+ """
2
+ High-level API wrapper for Book-A-Limo operations.
3
+ Provides clean, LLM-friendly functions that abstract API complexities.
4
+ """
5
+
6
+ from types import TracebackType
7
+ from typing import Any, Optional
8
+
9
+ from httpx import AsyncClient
10
+
11
+ from ._client import BookALimoClient, BookALimoError
12
+ from .models import (
13
+ Address,
14
+ Airport,
15
+ BookRequest,
16
+ BookResponse,
17
+ CardHolderType,
18
+ City,
19
+ Credentials,
20
+ CreditCard,
21
+ DetailsRequest,
22
+ DetailsResponse,
23
+ EditableReservationRequest,
24
+ EditReservationResponse,
25
+ GetReservationResponse,
26
+ ListReservationsResponse,
27
+ Location,
28
+ LocationType,
29
+ Passenger,
30
+ PriceRequest,
31
+ PriceResponse,
32
+ RateType,
33
+ Stop,
34
+ )
35
+
36
+
37
+ class BookALimo:
38
+ """
39
+ High-level wrapper for Book-A-Limo API operations.
40
+ Provides small, LLM-friendly functions that map 1:1 to API endpoints.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ credentials: Credentials,
46
+ http_client: Optional[AsyncClient] = None,
47
+ sandbox: bool = False,
48
+ **kwargs: Any,
49
+ ):
50
+ """
51
+ Initializes the BookALimo API wrapper.
52
+
53
+ Args:
54
+ credentials: User ID and password hash for authentication.
55
+ sandbox: Set to True to use the sandbox environment.
56
+ http_client: Optional custom httpx.AsyncClient instance.
57
+ **kwargs: Additional options passed to the BookALimoClient.
58
+ """
59
+ self._owns_http_client = http_client is None
60
+ self.http_client = http_client or AsyncClient()
61
+ self.client = BookALimoClient(
62
+ credentials=credentials,
63
+ sandbox=sandbox,
64
+ client=self.http_client,
65
+ **kwargs,
66
+ )
67
+
68
+ async def aclose(self) -> None:
69
+ """Close the HTTP client if we own it."""
70
+ if self._owns_http_client and not self.http_client.is_closed:
71
+ await self.http_client.aclose()
72
+
73
+ async def __aenter__(self) -> "BookALimo":
74
+ """Async context manager entry."""
75
+ return self
76
+
77
+ async def __aexit__(
78
+ self,
79
+ exc_type: Optional[type[BaseException]],
80
+ exc_val: Optional[BaseException],
81
+ exc_tb: Optional[TracebackType],
82
+ ) -> None:
83
+ """Async context manager exit."""
84
+ await self.aclose()
85
+
86
+ async def list_reservations(
87
+ self, is_archive: bool = False
88
+ ) -> ListReservationsResponse:
89
+ """
90
+ List reservations for the user.
91
+
92
+ Args:
93
+ is_archive: If True, fetch archived reservations
94
+
95
+ Returns:
96
+ Dict with 'success', 'reservations' list, optional 'error'
97
+ """
98
+ try:
99
+ result = await self.client.list_reservations(is_archive)
100
+
101
+ return result
102
+ except Exception as e:
103
+ raise BookALimoError(f"Failed to list reservations: {str(e)}") from e
104
+
105
+ async def get_reservation(self, confirmation: str) -> GetReservationResponse:
106
+ """
107
+ Get detailed reservation information.
108
+
109
+ Args:
110
+ confirmation: Confirmation number
111
+
112
+ Returns:
113
+ Dict with reservation details, status, policies, breakdown
114
+ """
115
+ try:
116
+ result = await self.client.get_reservation(confirmation)
117
+
118
+ return result
119
+ except Exception as e:
120
+ raise BookALimoError(f"Failed to get reservation: {str(e)}") from e
121
+
122
+ async def get_prices(
123
+ self,
124
+ rate_type: RateType,
125
+ date_time: str,
126
+ pickup: Location,
127
+ dropoff: Location,
128
+ passengers: int,
129
+ luggage: int,
130
+ **kwargs: Any,
131
+ ) -> PriceResponse:
132
+ """
133
+ Get pricing for a trip.
134
+
135
+ Args:
136
+ rate_type: 0=P2P, 1=Hourly (or string names)
137
+ date_time: 'MM/dd/yyyy hh:mm tt' format
138
+ pickup_location: Location dict
139
+ dropoff_location: Location dict
140
+ passengers: Number of passengers
141
+ luggage: Number of luggage pieces
142
+ **kwargs: Optional fields like stops, account, car_class_code, etc.
143
+
144
+ Returns:
145
+ Dict with 'token' and 'prices' list
146
+ """
147
+ try:
148
+ # Build request with optional fields
149
+ request_data: dict[str, Any] = {
150
+ "rate_type": rate_type,
151
+ "date_time": date_time,
152
+ "pickup": pickup,
153
+ "dropoff": dropoff,
154
+ "passengers": passengers,
155
+ "luggage": luggage,
156
+ }
157
+
158
+ # Add optional fields if provided
159
+ optional_fields = [
160
+ "hours",
161
+ "stops",
162
+ "account",
163
+ "passenger",
164
+ "rewards",
165
+ "car_class_code",
166
+ "pets",
167
+ "car_seats",
168
+ "boosters",
169
+ "infants",
170
+ "customer_comment",
171
+ ]
172
+
173
+ for field in optional_fields:
174
+ if field in kwargs and kwargs[field] is not None:
175
+ request_data[field] = kwargs[field]
176
+
177
+ request_model = PriceRequest(**request_data)
178
+
179
+ result = await self.client.get_prices(request_model)
180
+
181
+ return result
182
+ except Exception as e:
183
+ raise BookALimoError(f"Failed to get prices: {str(e)}") from e
184
+
185
+ async def set_details(self, token: str, **details: Any) -> DetailsResponse:
186
+ """
187
+ Set reservation details and get updated pricing.
188
+
189
+ Args:
190
+ token: Session token from get_prices
191
+ **details: Fields to update (car_class_code, pickup, dropoff,
192
+ stops, account, passenger, rewards, pets, car_seats,
193
+ boosters, infants, customer_comment, ta_fee)
194
+
195
+ Returns:
196
+ Dict with 'price' and 'breakdown' list
197
+ """
198
+ try:
199
+ request_data: dict[str, Any] = {"token": token}
200
+
201
+ # Add provided details
202
+ for key, value in details.items():
203
+ if value is not None:
204
+ request_data[key] = value
205
+
206
+ request_model = DetailsRequest(**request_data)
207
+
208
+ result = await self.client.set_details(request_model)
209
+
210
+ return result
211
+ except Exception as e:
212
+ raise BookALimoError(f"Failed to set details: {str(e)}") from e
213
+
214
+ async def book(
215
+ self,
216
+ token: str,
217
+ method: Optional[str] = None,
218
+ credit_card: Optional[CreditCard] = None,
219
+ promo: Optional[str] = None,
220
+ ) -> BookResponse:
221
+ """
222
+ Book a reservation.
223
+
224
+ Args:
225
+ token: Session token from get_prices/set_details
226
+ method: 'charge' for charge accounts, None for credit card
227
+ credit_card: Credit card dict (required if method is not 'charge')
228
+ promo: Optional promo code
229
+
230
+ Returns:
231
+ Dict with 'reservation_id'
232
+ """
233
+ try:
234
+ request_data: dict[str, Any] = {"token": token}
235
+
236
+ if promo:
237
+ request_data["promo"] = promo
238
+
239
+ if method == "charge":
240
+ request_data["method"] = "charge"
241
+ elif credit_card:
242
+ request_data["credit_card"] = credit_card
243
+ else:
244
+ raise BookALimoError(
245
+ "Either method='charge' or credit_card must be provided"
246
+ )
247
+
248
+ request_model = BookRequest(**request_data)
249
+
250
+ result = await self.client.book_reservation(request_model)
251
+
252
+ return result
253
+ except Exception as e:
254
+ raise BookALimoError(f"Failed to book reservation: {str(e)}") from e
255
+
256
+ async def edit_reservation(
257
+ self, confirmation: str, is_cancel_request: bool = False, **changes: Any
258
+ ) -> EditReservationResponse:
259
+ """
260
+ Edit or cancel a reservation.
261
+
262
+ Args:
263
+ confirmation: Confirmation number
264
+ is_cancel_request: True to cancel the reservation
265
+ **changes: Fields to change (rate_type, pickup_date, pickup_time,
266
+ stops, passengers, luggage, pets, car_seats, boosters,
267
+ infants, other)
268
+
269
+ Returns:
270
+ EditReservationResponse
271
+ """
272
+ try:
273
+ request_data: dict[str, Any] = {
274
+ "confirmation": confirmation,
275
+ "is_cancel_request": is_cancel_request,
276
+ }
277
+
278
+ # Add changes if not canceling
279
+ if not is_cancel_request:
280
+ for key, value in changes.items():
281
+ if value is not None:
282
+ request_data[key] = value
283
+
284
+ request_model = EditableReservationRequest(**request_data)
285
+
286
+ return await self.client.edit_reservation(request_model)
287
+ except Exception as e:
288
+ raise BookALimoError(f"Failed to edit reservation: {str(e)}") from e
289
+
290
+
291
+ # Convenience functions for creating common data structures
292
+
293
+
294
+ def create_credentials(
295
+ user_id: str, password: str, is_customer: bool = False
296
+ ) -> Credentials:
297
+ """Create credentials dict with proper password hash."""
298
+ return Credentials(
299
+ id=user_id,
300
+ is_customer=is_customer,
301
+ password_hash=Credentials.create_hash(password, user_id),
302
+ )
303
+
304
+
305
+ def create_address_location(
306
+ address: str,
307
+ google_geocode: Optional[dict[str, Any]] = None,
308
+ district: Optional[str] = None,
309
+ building: Optional[str] = None,
310
+ suite: Optional[str] = None,
311
+ zip_code: Optional[str] = None,
312
+ ) -> Location:
313
+ """Create address-based location dict."""
314
+ return Location(
315
+ type=LocationType.ADDRESS,
316
+ address=Address(
317
+ city=City(
318
+ city_name=address,
319
+ country_code="US",
320
+ state_code="NY",
321
+ state_name="New York",
322
+ ),
323
+ district=district,
324
+ suite=suite,
325
+ google_geocode=google_geocode,
326
+ place_name=address if not google_geocode else None,
327
+ street_name=address if not google_geocode else None,
328
+ neighbourhood=district,
329
+ building=building,
330
+ zip=zip_code,
331
+ ),
332
+ )
333
+
334
+
335
+ def create_airport_location(
336
+ iata_code: str,
337
+ city_name: str,
338
+ airline_code: Optional[str] = None,
339
+ flight_number: Optional[str] = None,
340
+ terminal: Optional[str] = None,
341
+ meet_greet: Optional[int] = None,
342
+ airline_icao_code: Optional[str] = None,
343
+ ) -> Location:
344
+ """Create airport-based location dict."""
345
+ return Location(
346
+ type=LocationType.AIRPORT,
347
+ airport=Airport(
348
+ iata_code=iata_code,
349
+ country_code="US",
350
+ state_code="NY",
351
+ airline_iata_code=airline_code,
352
+ airline_icao_code=airline_icao_code,
353
+ flight_number=flight_number,
354
+ terminal=terminal,
355
+ arriving_from_city=City(
356
+ city_name=city_name,
357
+ country_code="US",
358
+ state_code="NY",
359
+ state_name="New York",
360
+ ),
361
+ meet_greet=meet_greet,
362
+ ),
363
+ )
364
+
365
+
366
+ def create_stop(description: str, is_en_route: bool = True) -> Stop:
367
+ """Create stop dict."""
368
+ return Stop(description=description, is_en_route=is_en_route)
369
+
370
+
371
+ def create_passenger(
372
+ first_name: str, last_name: str, phone: str, email: Optional[str] = None
373
+ ) -> Passenger:
374
+ """Create passenger dict."""
375
+ return Passenger(
376
+ first_name=first_name, last_name=last_name, phone=phone, email=email
377
+ )
378
+
379
+
380
+ def create_credit_card(
381
+ number: str,
382
+ card_holder: str,
383
+ holder_type: CardHolderType,
384
+ expiration: str,
385
+ cvv: str,
386
+ zip_code: Optional[str] = None,
387
+ ) -> CreditCard:
388
+ """Create credit card dict."""
389
+ return CreditCard(
390
+ number=number,
391
+ card_holder=card_holder,
392
+ holder_type=holder_type,
393
+ expiration=expiration,
394
+ cvv=cvv,
395
+ zip=zip_code,
396
+ )