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