bookalimo 1.0.0__py3-none-any.whl → 1.0.2__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.
@@ -1,6 +1,7 @@
1
1
  """
2
- Pydantic schemas for Book-A-Limo booking operations.
3
- Includes all models related to pricing, reservations, and booking requests.
2
+ Shared base models for Book-A-Limo API data structures.
3
+ Contains field definitions without serialization opinions -
4
+ request/response variants inherit from these and set appropriate serialization.
4
5
  """
5
6
 
6
7
  import warnings
@@ -14,7 +15,7 @@ import us
14
15
  from pydantic import Field, model_validator
15
16
  from typing_extensions import Self
16
17
 
17
- from .base import ApiModel
18
+ from .base import SharedModel
18
19
 
19
20
 
20
21
  @lru_cache(maxsize=1)
@@ -28,15 +29,16 @@ def _load_iata_index() -> tuple[dict[str, Any], dict[str, list[str]]]:
28
29
  return data, by_country
29
30
 
30
31
 
32
+ # Enums (no serialization issues)
31
33
  class RateType(Enum):
32
34
  """Rate types for reservations."""
33
35
 
34
- P2P = 0 # Point-to-Point (best guess from context)
35
- HOURLY = 1 # Hourly (best guess from context)
36
+ P2P = 0 # Point-to-Point
37
+ HOURLY = 1 # Hourly
36
38
  DAILY = 2 # Daily
37
39
  TOUR = 3 # Tour
38
40
  ROUND_TRIP = 4 # Round Trip
39
- RT_HALF = 5 # RT Half
41
+ RT_HALF = 5 # Round Trip Half Day
40
42
 
41
43
 
42
44
  class LocationType(Enum):
@@ -76,20 +78,17 @@ class ReservationStatus(Enum):
76
78
 
77
79
 
78
80
  class CardHolderType(Enum):
79
- """
80
- Credit card holder types (API Documentation Unclear).
81
- TODO: Update when API documentation is clarified by the API author.
82
- Best guess based on typical credit card processing:
83
- """
81
+ """Credit card holder types."""
84
82
 
85
- PERSONAL = 0 # Personal/Individual account (best guess)
86
- BUSINESS = 1 # Business/Corporate account (best guess)
87
- # Note: Add UNKNOWN = 3 if you see this value in responses
88
- UNKNOWN = 3 # From example in API doc
83
+ CORPORATE = 0
84
+ AGENCY = 1
85
+ THIRD_PARTY = 2
86
+ SAME_AS_PASSENGER = 3
89
87
 
90
88
 
91
- class City(ApiModel):
92
- """City information."""
89
+ # Shared Data Models
90
+ class CityBase(SharedModel):
91
+ """Base city information."""
93
92
 
94
93
  city_name: str
95
94
  country_code: str = Field(..., description="ISO 3166-1 alpha-2 country code")
@@ -130,15 +129,18 @@ class City(ApiModel):
130
129
  )
131
130
 
132
131
 
133
- class Address(ApiModel):
134
- """
135
- Address information.
136
- """
132
+ class AddressBase(SharedModel):
133
+ """Base address information."""
137
134
 
138
135
  google_geocode: Optional[dict[str, Any]] = Field(
139
- default=None, description="Raw Google Geocoding API response (recommended)"
136
+ default=None,
137
+ description=(
138
+ "Raw Google Geocoding API result (recommended). "
139
+ "Common mistake is to use the response object instead of the result object. "
140
+ "You must use geocode_response['results'][0] to get the result object."
141
+ ),
140
142
  )
141
- city: Optional[City] = Field(
143
+ city: Optional["CityBase"] = Field(
142
144
  default=None, description="Use only if google_geocode not available"
143
145
  )
144
146
  district: Optional[str] = Field(default=None, description="e.g., Manhattan")
@@ -179,8 +181,8 @@ class Address(ApiModel):
179
181
  return self
180
182
 
181
183
 
182
- class Airport(ApiModel):
183
- """Airport information."""
184
+ class AirportBase(SharedModel):
185
+ """Base airport information."""
184
186
 
185
187
  iata_code: str = Field(..., description="3-letter IATA code, e.g., JFK")
186
188
  country_code: Optional[str] = Field(default=None, description="ISO 3166-1 alpha-2")
@@ -195,7 +197,7 @@ class Airport(ApiModel):
195
197
  )
196
198
  flight_number: Optional[str] = Field(default=None, description="e.g., UA1234")
197
199
  terminal: Optional[str] = Field(default=None, description="e.g., 7")
198
- arriving_from_city: Optional[City] = None
200
+ arriving_from_city: Optional["CityBase"] = None
199
201
  meet_greet: Optional[int] = Field(
200
202
  default=None,
201
203
  description="Meet & greet option ID. Leave empty on price request to see options.",
@@ -226,12 +228,12 @@ class Airport(ApiModel):
226
228
  return self
227
229
 
228
230
 
229
- class Location(ApiModel):
230
- """Location (address or airport)."""
231
+ class LocationBase(SharedModel):
232
+ """Base location (address or airport)."""
231
233
 
232
234
  type: LocationType
233
- address: Optional[Address] = None
234
- airport: Optional[Airport] = None
235
+ address: Optional["AddressBase"] = None
236
+ airport: Optional["AirportBase"] = None
235
237
 
236
238
  @model_validator(mode="after")
237
239
  def validate_location(self) -> Self:
@@ -244,15 +246,15 @@ class Location(ApiModel):
244
246
  return self
245
247
 
246
248
 
247
- class Stop(ApiModel):
248
- """Stop information."""
249
+ class StopBase(SharedModel):
250
+ """Base stop information."""
249
251
 
250
252
  description: str = Field(..., description="Address, place name, or comment")
251
253
  is_en_route: bool = Field(..., description="True if stop is en-route")
252
254
 
253
255
 
254
- class Account(ApiModel):
255
- """Travel agency or corporate account info."""
256
+ class AccountBase(SharedModel):
257
+ """Base travel agency or corporate account info."""
256
258
 
257
259
  id: str = Field(..., description="TA or corporate account number")
258
260
  department: Optional[str] = None
@@ -262,8 +264,8 @@ class Account(ApiModel):
262
264
  booker_phone: Optional[str] = Field(default=None, description="E164 format")
263
265
 
264
266
 
265
- class Passenger(ApiModel):
266
- """Passenger information."""
267
+ class PassengerBase(SharedModel):
268
+ """Base passenger information."""
267
269
 
268
270
  first_name: str
269
271
  last_name: str
@@ -271,15 +273,15 @@ class Passenger(ApiModel):
271
273
  phone: str = Field(..., description="E164 format recommended")
272
274
 
273
275
 
274
- class Reward(ApiModel):
275
- """Reward account information."""
276
+ class RewardBase(SharedModel):
277
+ """Base reward account information."""
276
278
 
277
279
  type: RewardType
278
280
  value: str = Field(..., description="Reward account number")
279
281
 
280
282
 
281
- class CreditCard(ApiModel):
282
- """Credit card information."""
283
+ class CreditCardBase(SharedModel):
284
+ """Base credit card information."""
283
285
 
284
286
  number: str
285
287
  expiration: str = Field(..., description="MM/YY format")
@@ -288,12 +290,12 @@ class CreditCard(ApiModel):
288
290
  zip: Optional[str] = None
289
291
  holder_type: Optional[CardHolderType] = Field(
290
292
  default=None,
291
- description="Card holder type - API documentation unclear, using best guess",
293
+ description="Card holder type",
292
294
  )
293
295
 
294
296
 
295
- class BreakdownItem(ApiModel):
296
- """Price breakdown item."""
297
+ class BreakdownItemBase(SharedModel):
298
+ """Base price breakdown item."""
297
299
 
298
300
  name: str
299
301
  value: float
@@ -302,28 +304,28 @@ class BreakdownItem(ApiModel):
302
304
  )
303
305
 
304
306
 
305
- class MeetGreetAdditional(ApiModel):
306
- """Additional meet & greet charges."""
307
+ class MeetGreetAdditionalBase(SharedModel):
308
+ """Base additional meet & greet charges."""
307
309
 
308
310
  name: str
309
311
  price: float
310
312
 
311
313
 
312
- class MeetGreet(ApiModel):
313
- """Meet & greet option."""
314
+ class MeetGreetBase(SharedModel):
315
+ """Base meet & greet option."""
314
316
 
315
317
  id: int
316
318
  name: str
317
319
  base_price: float
318
320
  instructions: str
319
- additional: list[MeetGreetAdditional]
321
+ additional: list["MeetGreetAdditionalBase"]
320
322
  total_price: float
321
323
  fees: float
322
324
  reservation_price: float
323
325
 
324
326
 
325
- class Price(ApiModel):
326
- """Car class pricing information."""
327
+ class PriceBase(SharedModel):
328
+ """Base car class pricing information."""
327
329
 
328
330
  car_class: str
329
331
  car_description: str
@@ -335,11 +337,11 @@ class Price(ApiModel):
335
337
  image_256: str = Field(alias="image256")
336
338
  image_512: str = Field(alias="image512")
337
339
  default_meet_greet: Optional[int] = None
338
- meet_greets: list[MeetGreet] = Field(default_factory=list)
340
+ meet_greets: list["MeetGreetBase"] = Field(default_factory=list)
339
341
 
340
342
 
341
- class Reservation(ApiModel):
342
- """Basic reservation information."""
343
+ class ReservationBase(SharedModel):
344
+ """Base reservation information."""
343
345
 
344
346
  confirmation_number: str
345
347
  is_archive: bool
@@ -353,168 +355,3 @@ class Reservation(ApiModel):
353
355
  dropoff: str
354
356
  car_class: str
355
357
  status: Optional[ReservationStatus] = None
356
-
357
-
358
- class EditableReservationRequest(ApiModel):
359
- """
360
- Editable reservation for modifications.
361
-
362
- Note: API documentation inconsistency - credit_card marked as required in model
363
- but omitted in edit examples. Making it optional as edit requests may not need it.
364
- TODO: Clarify with API author when credit_card is actually required.
365
- """
366
-
367
- confirmation: str
368
- is_cancel_request: bool = False
369
- rate_type: Optional[RateType] = None
370
- pickup_date: Optional[str] = Field(default=None, description="MM/dd/yyyy format")
371
- pickup_time: Optional[str] = Field(default=None, description="hh:mm tt format")
372
- stops: Optional[list[Stop]] = None
373
- credit_card: Optional[CreditCard] = Field(
374
- default=None,
375
- description="Conditionally required - unclear from API docs when exactly",
376
- )
377
- passengers: Optional[int] = None
378
- luggage: Optional[int] = None
379
- pets: Optional[int] = None
380
- car_seats: Optional[int] = None
381
- boosters: Optional[int] = None
382
- infants: Optional[int] = None
383
- other: Optional[str] = Field(default=None, description="Other changes not listed")
384
-
385
-
386
- # Request/Response Models
387
-
388
-
389
- class PriceRequest(ApiModel):
390
- """Request for getting prices."""
391
-
392
- rate_type: RateType
393
- date_time: str = Field(..., description="MM/dd/yyyy hh:mm tt format")
394
- pickup: Location
395
- dropoff: Location
396
- hours: Optional[int] = Field(default=None, description="For hourly rate_type only")
397
- passengers: int
398
- luggage: int
399
- stops: Optional[list[Stop]] = None
400
- account: Optional[Account] = Field(
401
- default=None, description="TAs must provide for commission"
402
- )
403
- passenger: Optional[Passenger] = None
404
- rewards: Optional[list[Reward]] = None
405
- car_class_code: Optional[str] = Field(
406
- default=None, description="e.g., 'SD' for specific car class"
407
- )
408
- pets: Optional[int] = None
409
- car_seats: Optional[int] = None
410
- boosters: Optional[int] = None
411
- infants: Optional[int] = None
412
- customer_comment: Optional[str] = None
413
-
414
-
415
- class PriceResponse(ApiModel):
416
- """Response from get prices."""
417
-
418
- token: str
419
- prices: list[Price]
420
-
421
-
422
- class DetailsRequest(ApiModel):
423
- """Request for setting reservation details."""
424
-
425
- token: str
426
- car_class_code: Optional[str] = None
427
- pickup: Optional[Location] = None
428
- dropoff: Optional[Location] = None
429
- stops: Optional[list[Stop]] = None
430
- account: Optional[Account] = None
431
- passenger: Optional[Passenger] = None
432
- rewards: Optional[list[Reward]] = None
433
- pets: Optional[int] = None
434
- car_seats: Optional[int] = None
435
- boosters: Optional[int] = None
436
- infants: Optional[int] = None
437
- customer_comment: Optional[str] = None
438
- ta_fee: Optional[float] = Field(
439
- default=None, description="For Travel Agencies - additional fee in USD"
440
- )
441
-
442
-
443
- class DetailsResponse(ApiModel):
444
- """Response from set details."""
445
-
446
- price: float
447
- breakdown: list[BreakdownItem]
448
-
449
-
450
- class BookRequest(ApiModel):
451
- """Request for booking reservation."""
452
-
453
- token: str
454
- promo: Optional[str] = None
455
- method: Optional[str] = Field(
456
- default=None, description="'charge' for charge accounts"
457
- )
458
- credit_card: Optional[CreditCard] = None
459
-
460
- @model_validator(mode="after")
461
- def validate_book_request(self) -> Self:
462
- """Validate that either method or credit_card is provided."""
463
- if not self.method and not self.credit_card:
464
- raise ValueError("Either method='charge' or credit_card must be provided")
465
-
466
- return self
467
-
468
-
469
- class BookResponse(ApiModel):
470
- """Response from book reservation."""
471
-
472
- reservation_id: str
473
-
474
-
475
- class ListReservationsRequest(ApiModel):
476
- """Request for listing reservations."""
477
-
478
- is_archive: bool = False
479
-
480
-
481
- class ListReservationsResponse(ApiModel):
482
- """Response from list reservations."""
483
-
484
- success: bool
485
- reservations: list[Reservation] = Field(default_factory=list)
486
- error: Optional[str] = None
487
-
488
-
489
- class GetReservationRequest(ApiModel):
490
- """Request for getting reservation details."""
491
-
492
- confirmation: str
493
-
494
-
495
- class GetReservationResponse(ApiModel):
496
- """Response from get reservation."""
497
-
498
- reservation: EditableReservationRequest
499
- is_editable: bool
500
- status: Optional[ReservationStatus] = None
501
- is_cancellation_pending: bool
502
- car_description: Optional[str] = None
503
- cancellation_policy: Optional[str] = None
504
- pickup_type: LocationType
505
- pickup_description: str
506
- dropoff_type: LocationType
507
- dropoff_description: str
508
- additional_services: Optional[str] = None
509
- payment_method: Optional[str] = None
510
- breakdown: list[BreakdownItem] = Field(default_factory=list)
511
- passenger_name: Optional[str] = None
512
- evoucher_url: Optional[str] = None
513
- receipt_url: Optional[str] = None
514
- pending_changes: list[list[str]] = Field(default_factory=list)
515
-
516
-
517
- class EditReservationResponse(ApiModel):
518
- """Response from edit reservation."""
519
-
520
- success: bool
@@ -1,14 +1,10 @@
1
1
  """Pricing service for getting quotes and updating trip details."""
2
2
 
3
- from typing import Any
4
-
5
- from ..schemas.booking import (
3
+ from ..schemas import (
6
4
  DetailsRequest,
7
5
  DetailsResponse,
8
- Location,
9
6
  PriceRequest,
10
7
  PriceResponse,
11
- RateType,
12
8
  )
13
9
  from ..transport.base import AsyncBaseTransport, BaseTransport
14
10
 
@@ -19,86 +15,28 @@ class AsyncPricingService:
19
15
  def __init__(self, transport: AsyncBaseTransport):
20
16
  self._transport = transport
21
17
 
22
- async def quote(
23
- self,
24
- rate_type: RateType,
25
- date_time: str,
26
- pickup: Location,
27
- dropoff: Location,
28
- passengers: int,
29
- luggage: int,
30
- **opts: Any,
31
- ) -> PriceResponse:
18
+ async def quote(self, request: PriceRequest) -> PriceResponse:
32
19
  """
33
20
  Get pricing for a trip.
34
21
 
35
22
  Args:
36
- rate_type: Rate type (P2P, HOURLY, etc.)
37
- date_time: Date and time in 'MM/dd/yyyy hh:mm tt' format
38
- pickup: Pickup location
39
- dropoff: Dropoff location
40
- passengers: Number of passengers
41
- luggage: Number of luggage pieces
42
- **opts: Optional fields like hours, stops, account, car_class_code,
43
- passenger, rewards, pets, car_seats, boosters, infants,
44
- customer_comment
23
+ request: Complete pricing request with all trip details
45
24
 
46
25
  Returns:
47
26
  PriceResponse with pricing information and session token
48
27
  """
49
- # Build request with optional fields
50
- request_data: dict[str, Any] = {
51
- "rate_type": rate_type,
52
- "date_time": date_time,
53
- "pickup": pickup,
54
- "dropoff": dropoff,
55
- "passengers": passengers,
56
- "luggage": luggage,
57
- }
58
-
59
- # Add optional fields if provided
60
- optional_fields = [
61
- "hours",
62
- "stops",
63
- "account",
64
- "passenger",
65
- "rewards",
66
- "car_class_code",
67
- "pets",
68
- "car_seats",
69
- "boosters",
70
- "infants",
71
- "customer_comment",
72
- ]
73
-
74
- for field in optional_fields:
75
- if field in opts and opts[field] is not None:
76
- request_data[field] = opts[field]
77
-
78
- request = PriceRequest(**request_data)
79
28
  return await self._transport.post("/booking/price/", request, PriceResponse)
80
29
 
81
- async def update_details(self, token: str, **details: Any) -> DetailsResponse:
30
+ async def update_details(self, request: DetailsRequest) -> DetailsResponse:
82
31
  """
83
32
  Update reservation details and get updated pricing.
84
33
 
85
34
  Args:
86
- token: Session token from quote()
87
- **details: Fields to update (car_class_code, pickup, dropoff,
88
- stops, account, passenger, rewards, pets, car_seats,
89
- boosters, infants, customer_comment, ta_fee)
35
+ request: Complete details request with token and fields to update
90
36
 
91
37
  Returns:
92
38
  DetailsResponse with updated pricing
93
39
  """
94
- request_data: dict[str, Any] = {"token": token}
95
-
96
- # Add provided details
97
- for key, value in details.items():
98
- if value is not None:
99
- request_data[key] = value
100
-
101
- request = DetailsRequest(**request_data)
102
40
  return await self._transport.post("/booking/details/", request, DetailsResponse)
103
41
 
104
42
 
@@ -108,84 +46,26 @@ class PricingService:
108
46
  def __init__(self, transport: BaseTransport):
109
47
  self._transport = transport
110
48
 
111
- def quote(
112
- self,
113
- rate_type: RateType,
114
- date_time: str,
115
- pickup: Location,
116
- dropoff: Location,
117
- passengers: int,
118
- luggage: int,
119
- **opts: Any,
120
- ) -> PriceResponse:
49
+ def quote(self, request: PriceRequest) -> PriceResponse:
121
50
  """
122
51
  Get pricing for a trip.
123
52
 
124
53
  Args:
125
- rate_type: Rate type (P2P, HOURLY, etc.)
126
- date_time: Date and time in 'MM/dd/yyyy hh:mm tt' format
127
- pickup: Pickup location
128
- dropoff: Location
129
- passengers: Number of passengers
130
- luggage: Number of luggage pieces
131
- **opts: Optional fields like hours, stops, account, car_class_code,
132
- passenger, rewards, pets, car_seats, boosters, infants,
133
- customer_comment
54
+ request: Complete pricing request with all trip details
134
55
 
135
56
  Returns:
136
57
  PriceResponse with pricing information and session token
137
58
  """
138
- # Build request with optional fields
139
- request_data: dict[str, Any] = {
140
- "rate_type": rate_type,
141
- "date_time": date_time,
142
- "pickup": pickup,
143
- "dropoff": dropoff,
144
- "passengers": passengers,
145
- "luggage": luggage,
146
- }
147
-
148
- # Add optional fields if provided
149
- optional_fields = [
150
- "hours",
151
- "stops",
152
- "account",
153
- "passenger",
154
- "rewards",
155
- "car_class_code",
156
- "pets",
157
- "car_seats",
158
- "boosters",
159
- "infants",
160
- "customer_comment",
161
- ]
162
-
163
- for field in optional_fields:
164
- if field in opts and opts[field] is not None:
165
- request_data[field] = opts[field]
166
-
167
- request = PriceRequest(**request_data)
168
59
  return self._transport.post("/booking/price/", request, PriceResponse)
169
60
 
170
- def update_details(self, token: str, **details: Any) -> DetailsResponse:
61
+ def update_details(self, request: DetailsRequest) -> DetailsResponse:
171
62
  """
172
63
  Update reservation details and get updated pricing.
173
64
 
174
65
  Args:
175
- token: Session token from quote()
176
- **details: Fields to update (car_class_code, pickup, dropoff,
177
- stops, account, passenger, rewards, pets, car_seats,
178
- boosters, infants, customer_comment, ta_fee)
66
+ request: Complete details request with token and fields to update
179
67
 
180
68
  Returns:
181
69
  DetailsResponse with updated pricing
182
70
  """
183
- request_data: dict[str, Any] = {"token": token}
184
-
185
- # Add provided details
186
- for key, value in details.items():
187
- if value is not None:
188
- request_data[key] = value
189
-
190
- request = DetailsRequest(**request_data)
191
71
  return self._transport.post("/booking/details/", request, DetailsResponse)