bookalimo 0.1.5__py3-none-any.whl → 1.0.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.
Files changed (42) 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 +289 -0
  9. bookalimo/integrations/google_places/client_sync.py +287 -0
  10. bookalimo/integrations/google_places/common.py +231 -0
  11. bookalimo/integrations/google_places/proto_adapter.py +224 -0
  12. bookalimo/integrations/google_places/resolve_airport.py +397 -0
  13. bookalimo/integrations/google_places/transports.py +98 -0
  14. bookalimo/{_logging.py → logging.py} +45 -42
  15. bookalimo/schemas/__init__.py +103 -0
  16. bookalimo/schemas/base.py +56 -0
  17. bookalimo/{models.py → schemas/booking.py} +88 -100
  18. bookalimo/schemas/places/__init__.py +62 -0
  19. bookalimo/schemas/places/common.py +351 -0
  20. bookalimo/schemas/places/field_mask.py +221 -0
  21. bookalimo/schemas/places/google.py +883 -0
  22. bookalimo/schemas/places/place.py +334 -0
  23. bookalimo/services/__init__.py +11 -0
  24. bookalimo/services/pricing.py +191 -0
  25. bookalimo/services/reservations.py +227 -0
  26. bookalimo/transport/__init__.py +7 -0
  27. bookalimo/transport/auth.py +41 -0
  28. bookalimo/transport/base.py +44 -0
  29. bookalimo/transport/httpx_async.py +230 -0
  30. bookalimo/transport/httpx_sync.py +230 -0
  31. bookalimo/transport/retry.py +102 -0
  32. bookalimo/transport/utils.py +59 -0
  33. bookalimo-1.0.1.dist-info/METADATA +370 -0
  34. bookalimo-1.0.1.dist-info/RECORD +38 -0
  35. bookalimo-1.0.1.dist-info/licenses/LICENSE +21 -0
  36. bookalimo/_client.py +0 -420
  37. bookalimo/wrapper.py +0 -444
  38. bookalimo-0.1.5.dist-info/METADATA +0 -392
  39. bookalimo-0.1.5.dist-info/RECORD +0 -12
  40. bookalimo-0.1.5.dist-info/licenses/LICENSE +0 -0
  41. {bookalimo-0.1.5.dist-info → bookalimo-1.0.1.dist-info}/WHEEL +0 -0
  42. {bookalimo-0.1.5.dist-info → bookalimo-1.0.1.dist-info}/top_level.txt +0 -0
bookalimo/__init__.py CHANGED
@@ -1,31 +1,24 @@
1
1
  """
2
- Book-A-Limo API Wrapper Package.
3
- Provides a clean, typed interface to the Book-A-Limo API.
4
- """
2
+ Bookalimo SDK - Python client for the Book-A-Limo API.
5
3
 
6
- import importlib.metadata
4
+ Provides clean, typed interfaces for booking transportation services.
5
+ """
7
6
 
8
- from ._logging import disable_debug_logging, enable_debug_logging
9
- from .wrapper import (
10
- BookALimo,
11
- create_address_location,
12
- create_airport_location,
13
- create_credentials,
14
- create_credit_card,
15
- create_passenger,
16
- create_stop,
7
+ from ._version import __version__
8
+ from .client import AsyncBookalimo, Bookalimo
9
+ from .exceptions import (
10
+ BookalimoError,
11
+ BookalimoHTTPError,
12
+ BookalimoTimeout,
13
+ BookalimoValidationError,
17
14
  )
18
15
 
19
16
  __all__ = [
20
- "BookALimo",
21
- "create_credentials",
22
- "create_address_location",
23
- "create_airport_location",
24
- "create_stop",
25
- "create_passenger",
26
- "create_credit_card",
27
- "enable_debug_logging",
28
- "disable_debug_logging",
17
+ "Bookalimo",
18
+ "AsyncBookalimo",
19
+ "BookalimoError",
20
+ "BookalimoHTTPError",
21
+ "BookalimoTimeout",
22
+ "BookalimoValidationError",
23
+ "__version__",
29
24
  ]
30
-
31
- __version__ = importlib.metadata.version(__package__ or __name__)
bookalimo/_version.py ADDED
@@ -0,0 +1,9 @@
1
+ """Version information for the bookalimo package."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("bookalimo")
7
+ except PackageNotFoundError:
8
+ # Development/editable install fallback
9
+ __version__ = "0.0.0"
bookalimo/client.py ADDED
@@ -0,0 +1,310 @@
1
+ """Main client classes for the Bookalimo SDK."""
2
+
3
+ import warnings
4
+ from typing import TYPE_CHECKING, Any, Optional
5
+
6
+ if TYPE_CHECKING:
7
+ from .integrations.google_places import AsyncGooglePlaces, GooglePlaces
8
+
9
+ from .config import DEFAULT_BASE_URL, DEFAULT_TIMEOUTS, DEFAULT_USER_AGENT
10
+ from .exceptions import DuplicateCredentialsWarning, MissingCredentialsWarning
11
+ from .services import (
12
+ AsyncPricingService,
13
+ AsyncReservationsService,
14
+ PricingService,
15
+ ReservationsService,
16
+ )
17
+ from .transport import AsyncTransport, SyncTransport
18
+ from .transport.auth import Credentials
19
+
20
+ # Optional integrations
21
+ try:
22
+ from .integrations.google_places import (
23
+ AsyncGooglePlaces as _AsyncGooglePlaces,
24
+ )
25
+ from .integrations.google_places import (
26
+ GooglePlaces as _GooglePlaces,
27
+ )
28
+
29
+ _GOOGLE_PLACES_AVAILABLE = True
30
+ except ImportError:
31
+ _GOOGLE_PLACES_AVAILABLE = False
32
+ _AsyncGooglePlaces = None # type: ignore
33
+ _GooglePlaces = None # type: ignore
34
+
35
+
36
+ class AsyncBookalimo:
37
+ """
38
+ Async client for the Book-A-Limo API.
39
+
40
+ Provides access to reservations and pricing services through a clean,
41
+ resource-style interface. Optionally includes Google Places integration
42
+ for location services.
43
+
44
+ Examples:
45
+ # Basic usage
46
+ async with AsyncBookalimo(credentials=creds) as client:
47
+ quote = await client.pricing.quote(
48
+ rate_type=RateType.P2P,
49
+ date_time="09/10/2025 03:00 PM",
50
+ pickup=pickup_location,
51
+ dropoff=dropoff_location,
52
+ passengers=2,
53
+ luggage=2,
54
+ )
55
+
56
+ # With Google Places integration
57
+ async with AsyncBookalimo(
58
+ credentials=creds,
59
+ google_places_api_key="your-google-api-key"
60
+ ) as client:
61
+ # Find locations using Google Places
62
+ places = await client.places.search_text("Empire State Building")
63
+
64
+ # Use in booking
65
+ quote = await client.pricing.quote(
66
+ rate_type=RateType.P2P,
67
+ date_time="09/10/2025 03:00 PM",
68
+ pickup=pickup_location,
69
+ dropoff=dropoff_location,
70
+ passengers=2,
71
+ luggage=2,
72
+ )
73
+ """
74
+
75
+ def __init__(
76
+ self,
77
+ *,
78
+ credentials: Optional[Credentials] = None,
79
+ base_url: str = DEFAULT_BASE_URL,
80
+ timeouts: Any = DEFAULT_TIMEOUTS,
81
+ user_agent: str = DEFAULT_USER_AGENT,
82
+ transport: Optional[AsyncTransport] = None,
83
+ google_places_api_key: Optional[str] = None,
84
+ ):
85
+ """
86
+ Initialize the async Bookalimo client.
87
+
88
+ Args:
89
+ credentials: Authentication credentials (required for API calls)
90
+ base_url: API base URL
91
+ timeouts: Request timeout configuration
92
+ user_agent: User agent string
93
+ transport: Custom transport instance (optional)
94
+ google_places_api_key: Google Places API key for location services (optional)
95
+ """
96
+
97
+ transport_credentials = transport.credentials if transport else None
98
+
99
+ both_provided = all([transport_credentials, credentials])
100
+ both_missing = not any([transport_credentials, credentials])
101
+
102
+ if both_provided:
103
+ warnings.warn(
104
+ "Credentials provided in both transport and constructor. "
105
+ "The transport credentials will be used.",
106
+ DuplicateCredentialsWarning,
107
+ stacklevel=2,
108
+ )
109
+ elif both_missing:
110
+ warnings.warn(
111
+ "No credentials provided in transport or constructor; proceeding unauthenticated.",
112
+ MissingCredentialsWarning,
113
+ stacklevel=2,
114
+ )
115
+
116
+ # Use whichever exists when we need to build a transport ourselves
117
+ effective_credentials = (
118
+ credentials if credentials is not None else transport_credentials
119
+ )
120
+ if transport:
121
+ transport.credentials = effective_credentials
122
+ self._transport = transport or AsyncTransport(
123
+ base_url=base_url,
124
+ timeouts=timeouts,
125
+ user_agent=user_agent,
126
+ credentials=effective_credentials,
127
+ )
128
+
129
+ # Initialize service instances
130
+ self.reservations = AsyncReservationsService(self._transport)
131
+ self.pricing = AsyncPricingService(self._transport)
132
+
133
+ # Initialize Google Places integration if available
134
+ self._google_places_api_key = google_places_api_key
135
+ self._google_places_client: Optional[AsyncGooglePlaces] = None
136
+
137
+ @property
138
+ def places(self) -> "AsyncGooglePlaces":
139
+ """
140
+ Access Google Places integration for location services.
141
+
142
+ Returns:
143
+ AsyncGooglePlaces client instance
144
+
145
+ Raises:
146
+ ImportError: If Google Places dependencies are not installed
147
+
148
+ Note:
149
+ Auth priority is as follows:
150
+ - provided api key in constructor
151
+ - GOOGLE_PLACES_API_KEY environment variable
152
+ - Google ADC - Except for Geocoding API.
153
+ """
154
+ if not _GOOGLE_PLACES_AVAILABLE:
155
+ raise ImportError(
156
+ "Google Places integration requires the 'places' extra. "
157
+ "Install with: pip install bookalimo[places]"
158
+ )
159
+
160
+ if self._google_places_client is None:
161
+ if _AsyncGooglePlaces is None:
162
+ raise ImportError("Google Places integration not available")
163
+ self._google_places_client = _AsyncGooglePlaces(
164
+ api_key=self._google_places_api_key
165
+ )
166
+
167
+ return self._google_places_client
168
+
169
+ async def aclose(self) -> None:
170
+ """Close the client and clean up resources."""
171
+ await self._transport.aclose()
172
+ if self._google_places_client is not None:
173
+ await self._google_places_client.aclose()
174
+
175
+ async def __aenter__(self) -> "AsyncBookalimo":
176
+ """Async context manager entry."""
177
+ return self
178
+
179
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
180
+ """Async context manager exit."""
181
+ await self.aclose()
182
+
183
+
184
+ class Bookalimo:
185
+ """
186
+ Sync client for the Book-A-Limo API.
187
+
188
+ Provides access to reservations and pricing services through a clean,
189
+ resource-style interface. Optionally includes Google Places integration
190
+ for location services.
191
+
192
+ Examples:
193
+ # Basic usage
194
+ with Bookalimo(credentials=creds) as client:
195
+ quote = client.pricing.quote(
196
+ rate_type=RateType.P2P,
197
+ date_time="09/10/2025 03:00 PM",
198
+ pickup=pickup_location,
199
+ dropoff=dropoff_location,
200
+ passengers=2,
201
+ luggage=2,
202
+ )
203
+
204
+ # With Google Places integration
205
+ with Bookalimo(
206
+ credentials=creds,
207
+ google_places_api_key="your-google-api-key"
208
+ ) as client:
209
+ # Find locations using Google Places
210
+ places = client.places.search_text("Empire State Building")
211
+
212
+ # Use in booking
213
+ quote = client.pricing.quote(
214
+ rate_type=RateType.P2P,
215
+ date_time="09/10/2025 03:00 PM",
216
+ pickup=pickup_location,
217
+ dropoff=dropoff_location,
218
+ passengers=2,
219
+ luggage=2,
220
+ )
221
+ """
222
+
223
+ def __init__(
224
+ self,
225
+ *,
226
+ credentials: Optional[Credentials] = None,
227
+ base_url: str = DEFAULT_BASE_URL,
228
+ timeouts: Any = DEFAULT_TIMEOUTS,
229
+ user_agent: str = DEFAULT_USER_AGENT,
230
+ transport: Optional[SyncTransport] = None,
231
+ google_places_api_key: Optional[str] = None,
232
+ ):
233
+ """
234
+ Initialize the sync Bookalimo client.
235
+
236
+ Args:
237
+ credentials: Authentication credentials (required for API calls)
238
+ base_url: API base URL
239
+ timeouts: Request timeout configuration
240
+ user_agent: User agent string
241
+ transport: Custom transport instance (optional)
242
+ google_places_api_key: Google Places API key for location services (optional)
243
+ """
244
+ if transport and transport.credentials is not None and credentials is not None:
245
+ warnings.warn(
246
+ "Credentials provided in both transport and constructor. "
247
+ "The transport credentials will be used.",
248
+ UserWarning,
249
+ stacklevel=2,
250
+ )
251
+ self._transport = transport or SyncTransport(
252
+ base_url=base_url,
253
+ timeouts=timeouts,
254
+ user_agent=user_agent,
255
+ credentials=credentials,
256
+ )
257
+
258
+ # Initialize service instances
259
+ self.reservations = ReservationsService(self._transport)
260
+ self.pricing = PricingService(self._transport)
261
+
262
+ # Initialize Google Places integration if available
263
+ self._google_places_api_key = google_places_api_key
264
+ self._google_places_client: Optional[GooglePlaces] = None
265
+
266
+ @property
267
+ def places(self) -> "GooglePlaces":
268
+ """
269
+ Access Google Places integration for location services.
270
+
271
+ Returns:
272
+ GooglePlaces client instance
273
+
274
+ Raises:
275
+ ImportError: If Google Places dependencies are not installed
276
+
277
+ Note:
278
+ Auth priority is as follows:
279
+ - provided api key in constructor
280
+ - GOOGLE_PLACES_API_KEY environment variable
281
+ - Google ADC - Except for Geocoding API.
282
+ """
283
+ if not _GOOGLE_PLACES_AVAILABLE:
284
+ raise ImportError(
285
+ "Google Places integration requires the 'places' extra. "
286
+ "Install with: pip install bookalimo[places]"
287
+ )
288
+
289
+ if self._google_places_client is None:
290
+ if _GooglePlaces is None:
291
+ raise ImportError("Google Places integration not available")
292
+ self._google_places_client = _GooglePlaces(
293
+ api_key=self._google_places_api_key
294
+ )
295
+
296
+ return self._google_places_client
297
+
298
+ def close(self) -> None:
299
+ """Close the client and clean up resources."""
300
+ self._transport.close()
301
+ if self._google_places_client is not None:
302
+ self._google_places_client.close()
303
+
304
+ def __enter__(self) -> "Bookalimo":
305
+ """Context manager entry."""
306
+ return self
307
+
308
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
309
+ """Context manager exit."""
310
+ self.close()
bookalimo/config.py ADDED
@@ -0,0 +1,16 @@
1
+ """Configuration defaults for the Bookalimo SDK."""
2
+
3
+ from ._version import __version__
4
+
5
+ # Default API configuration
6
+ DEFAULT_BASE_URL = "https://www.bookalimo.com/web/api"
7
+ DEFAULT_TIMEOUT = 5.0
8
+ DEFAULT_USER_AGENT = f"bookalimo-python/{__version__}"
9
+
10
+ # Default retry configuration
11
+ DEFAULT_RETRIES = 2
12
+ DEFAULT_BACKOFF = 0.3
13
+ DEFAULT_STATUS_FORCELIST = (500, 502, 503, 504)
14
+
15
+ # Default timeouts (can be dict for httpx.Timeout)
16
+ DEFAULT_TIMEOUTS = DEFAULT_TIMEOUT
bookalimo/exceptions.py CHANGED
@@ -1,15 +1,125 @@
1
- from typing import Any, Optional
1
+ """Exception classes for the Bookalimo SDK."""
2
2
 
3
+ from typing import Any, Literal, Optional
3
4
 
4
- class BookALimoError(Exception):
5
- """Base exception for Book-A-Limo API errors."""
5
+ from pydantic import ValidationError
6
+ from pydantic_core import ErrorDetails, InitErrorDetails
7
+
8
+
9
+ class BookalimoError(Exception): ...
10
+
11
+
12
+ class BookalimoValidationError(BookalimoError):
13
+ """Validation error for input data that wraps Pydantic ValidationError."""
14
+
15
+ def __init__(
16
+ self, message: str, validation_error: Optional[ValidationError] = None
17
+ ):
18
+ """Initialize validation error with message and optional Pydantic ValidationError."""
19
+ super().__init__(message)
20
+ self._validation_error = validation_error
21
+ self._message = message
22
+
23
+ @property
24
+ def message(self) -> str:
25
+ """Get the error message."""
26
+ return self._message
27
+
28
+ def errors(self, **kwargs: Any) -> list[ErrorDetails]:
29
+ """Get validation errors if available."""
30
+ if self._validation_error:
31
+ return self._validation_error.errors(**kwargs)
32
+ return []
33
+
34
+ def error_count(self) -> int:
35
+ """Get error count."""
36
+ if self._validation_error:
37
+ return self._validation_error.error_count()
38
+ return 1
39
+
40
+ @property
41
+ def title(self) -> str:
42
+ """Get validation error title."""
43
+ if self._validation_error:
44
+ return self._validation_error.title
45
+ return "BookalimoValidationError"
46
+
47
+ def json(self, **kwargs: Any) -> str:
48
+ """Get errors as JSON string."""
49
+ if self._validation_error:
50
+ return self._validation_error.json(**kwargs)
51
+ import json
52
+
53
+ return json.dumps([{"type": "error", "msg": self._message, "loc": []}])
54
+
55
+ @classmethod
56
+ def from_exception_data(
57
+ cls,
58
+ title: str,
59
+ line_errors: list[InitErrorDetails],
60
+ input_type: Literal["python", "json"] = "python",
61
+ hide_input: bool = False,
62
+ ) -> "BookalimoValidationError":
63
+ """Create validation error from Pydantic error details."""
64
+ # Create a proper ValidationError
65
+ validation_error = ValidationError.from_exception_data(
66
+ title, line_errors, input_type, hide_input
67
+ )
68
+ return cls(f"Validation error in {title}", validation_error)
69
+
70
+ @classmethod
71
+ def from_validation_error(
72
+ cls, validation_error: ValidationError, message: Optional[str] = None
73
+ ) -> "BookalimoValidationError":
74
+ """Create BookalimoValidationError from an existing ValidationError."""
75
+ if message is None:
76
+ message = f"Validation error: {validation_error}"
77
+ return cls(message, validation_error)
78
+
79
+
80
+ class BookalimoRequestError(BookalimoError): ...
81
+
82
+
83
+ class BookalimoConnectionError(BookalimoError): ...
84
+
85
+
86
+ class BookalimoHTTPError(BookalimoError):
87
+ """HTTP-related errors (4xx, 5xx responses)."""
6
88
 
7
89
  def __init__(
8
90
  self,
9
91
  message: str,
92
+ *,
10
93
  status_code: Optional[int] = None,
11
- response_data: Optional[dict[str, Any]] = None,
94
+ payload: Optional[dict[str, Any]] = None,
12
95
  ):
13
96
  super().__init__(message)
97
+ self.message = message
14
98
  self.status_code = status_code
15
- self.response_data = response_data or {}
99
+ self.payload = payload
100
+
101
+ def __str__(self) -> str:
102
+ base = super().__str__()
103
+ if self.payload and isinstance(self.payload, dict) and "error" in self.payload:
104
+ error_msg = self.payload["error"]
105
+ if self.status_code:
106
+ return f"{base}: {error_msg} (status_code={self.status_code})"
107
+ else:
108
+ return f"{base}: {error_msg}"
109
+ return f"{base} (status_code={self.status_code})" if self.status_code else base
110
+
111
+
112
+ class BookalimoTimeout(BookalimoHTTPError):
113
+ """Request timeout errors."""
114
+
115
+ def __init__(self, message: str = "Request timeout", **kwargs: Any):
116
+ super().__init__(message, status_code=408, **kwargs)
117
+ self.message = message
118
+ self.status_code = 408
119
+ self.payload = kwargs.get("payload", None)
120
+
121
+
122
+ class DuplicateCredentialsWarning(UserWarning): ...
123
+
124
+
125
+ class MissingCredentialsWarning(UserWarning): ...
@@ -0,0 +1 @@
1
+ """Optional integrations for the Bookalimo SDK."""
@@ -0,0 +1,31 @@
1
+ """
2
+ Google Places API integration for Bookalimo.
3
+
4
+ This integration requires the 'places' extra:
5
+ pip install bookalimo[places]
6
+
7
+ Examples:
8
+ # Async client
9
+ from bookalimo.integrations.google_places import AsyncGooglePlaces
10
+
11
+ async with AsyncGooglePlaces() as places:
12
+ results = await places.autocomplete("Empire State Building")
13
+
14
+ # Sync client
15
+ from bookalimo.integrations.google_places import GooglePlaces
16
+
17
+ with GooglePlaces() as places:
18
+ results = places.autocomplete("Empire State Building")
19
+ """
20
+
21
+ try:
22
+ from .client_async import AsyncGooglePlaces
23
+ from .client_sync import GooglePlaces
24
+
25
+ __all__ = ["AsyncGooglePlaces", "GooglePlaces"]
26
+
27
+ except ImportError as e:
28
+ raise ImportError(
29
+ "Google Places integration requires the 'places' extra. "
30
+ "Install with: pip install bookalimo[places]"
31
+ ) from e