bookalimo 1.0.1__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.
- bookalimo/client.py +45 -22
- bookalimo/integrations/google_places/client_async.py +56 -102
- bookalimo/integrations/google_places/client_sync.py +56 -100
- bookalimo/integrations/google_places/common.py +290 -12
- bookalimo/integrations/google_places/resolve_airport.py +148 -119
- bookalimo/integrations/google_places/transports.py +14 -7
- bookalimo/logging.py +103 -0
- bookalimo/schemas/__init__.py +121 -35
- bookalimo/schemas/base.py +74 -14
- bookalimo/schemas/places/__init__.py +3 -1
- bookalimo/schemas/places/common.py +1 -1
- bookalimo/schemas/places/field_mask.py +0 -9
- bookalimo/schemas/places/google.py +165 -10
- bookalimo/schemas/requests.py +214 -0
- bookalimo/schemas/responses.py +196 -0
- bookalimo/schemas/{booking.py → shared.py} +55 -218
- bookalimo/services/pricing.py +9 -129
- bookalimo/services/reservations.py +10 -100
- bookalimo/transport/auth.py +2 -2
- bookalimo/transport/httpx_async.py +41 -125
- bookalimo/transport/httpx_sync.py +30 -109
- bookalimo/transport/utils.py +204 -3
- bookalimo-1.0.2.dist-info/METADATA +245 -0
- bookalimo-1.0.2.dist-info/RECORD +40 -0
- bookalimo-1.0.1.dist-info/METADATA +0 -370
- bookalimo-1.0.1.dist-info/RECORD +0 -38
- {bookalimo-1.0.1.dist-info → bookalimo-1.0.2.dist-info}/WHEEL +0 -0
- {bookalimo-1.0.1.dist-info → bookalimo-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {bookalimo-1.0.1.dist-info → bookalimo-1.0.2.dist-info}/top_level.txt +0 -0
bookalimo/client.py
CHANGED
@@ -59,7 +59,7 @@ class AsyncBookalimo:
|
|
59
59
|
google_places_api_key="your-google-api-key"
|
60
60
|
) as client:
|
61
61
|
# Find locations using Google Places
|
62
|
-
places = await client.places.
|
62
|
+
places = await client.places.search("Empire State Building")
|
63
63
|
|
64
64
|
# Use in booking
|
65
65
|
quote = await client.pricing.quote(
|
@@ -93,8 +93,7 @@ class AsyncBookalimo:
|
|
93
93
|
transport: Custom transport instance (optional)
|
94
94
|
google_places_api_key: Google Places API key for location services (optional)
|
95
95
|
"""
|
96
|
-
|
97
|
-
transport_credentials = transport.credentials if transport else None
|
96
|
+
transport_credentials = transport._credentials if transport else None
|
98
97
|
|
99
98
|
both_provided = all([transport_credentials, credentials])
|
100
99
|
both_missing = not any([transport_credentials, credentials])
|
@@ -113,18 +112,21 @@ class AsyncBookalimo:
|
|
113
112
|
stacklevel=2,
|
114
113
|
)
|
115
114
|
|
116
|
-
# Use whichever exists when we need to build a transport ourselves
|
117
115
|
effective_credentials = (
|
118
|
-
|
116
|
+
transport_credentials if transport_credentials is not None else credentials
|
119
117
|
)
|
118
|
+
|
120
119
|
if transport:
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
120
|
+
if transport_credentials is None and credentials is not None:
|
121
|
+
transport.credentials = credentials
|
122
|
+
self._transport = transport
|
123
|
+
else:
|
124
|
+
self._transport = AsyncTransport(
|
125
|
+
base_url=base_url,
|
126
|
+
timeouts=timeouts,
|
127
|
+
user_agent=user_agent,
|
128
|
+
credentials=effective_credentials,
|
129
|
+
)
|
128
130
|
|
129
131
|
# Initialize service instances
|
130
132
|
self.reservations = AsyncReservationsService(self._transport)
|
@@ -149,7 +151,7 @@ class AsyncBookalimo:
|
|
149
151
|
Auth priority is as follows:
|
150
152
|
- provided api key in constructor
|
151
153
|
- GOOGLE_PLACES_API_KEY environment variable
|
152
|
-
- Google ADC -
|
154
|
+
- Google ADC - Not yet implemented.
|
153
155
|
"""
|
154
156
|
if not _GOOGLE_PLACES_AVAILABLE:
|
155
157
|
raise ImportError(
|
@@ -207,7 +209,7 @@ class Bookalimo:
|
|
207
209
|
google_places_api_key="your-google-api-key"
|
208
210
|
) as client:
|
209
211
|
# Find locations using Google Places
|
210
|
-
places = client.places.
|
212
|
+
places = client.places.search("Empire State Building")
|
211
213
|
|
212
214
|
# Use in booking
|
213
215
|
quote = client.pricing.quote(
|
@@ -241,20 +243,41 @@ class Bookalimo:
|
|
241
243
|
transport: Custom transport instance (optional)
|
242
244
|
google_places_api_key: Google Places API key for location services (optional)
|
243
245
|
"""
|
244
|
-
|
246
|
+
transport_credentials = transport.credentials if transport else None
|
247
|
+
|
248
|
+
both_provided = all([transport_credentials, credentials])
|
249
|
+
both_missing = not any([transport_credentials, credentials])
|
250
|
+
|
251
|
+
if both_provided:
|
245
252
|
warnings.warn(
|
246
253
|
"Credentials provided in both transport and constructor. "
|
247
254
|
"The transport credentials will be used.",
|
248
|
-
|
255
|
+
DuplicateCredentialsWarning,
|
249
256
|
stacklevel=2,
|
250
257
|
)
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
258
|
+
elif both_missing:
|
259
|
+
warnings.warn(
|
260
|
+
"No credentials provided in transport or constructor; proceeding unauthenticated.",
|
261
|
+
MissingCredentialsWarning,
|
262
|
+
stacklevel=2,
|
263
|
+
)
|
264
|
+
|
265
|
+
effective_credentials = (
|
266
|
+
transport_credentials if transport_credentials is not None else credentials
|
256
267
|
)
|
257
268
|
|
269
|
+
if transport:
|
270
|
+
if transport_credentials is None and credentials is not None:
|
271
|
+
transport.credentials = credentials
|
272
|
+
self._transport = transport
|
273
|
+
else:
|
274
|
+
self._transport = SyncTransport(
|
275
|
+
base_url=base_url,
|
276
|
+
timeouts=timeouts,
|
277
|
+
user_agent=user_agent,
|
278
|
+
credentials=effective_credentials,
|
279
|
+
)
|
280
|
+
|
258
281
|
# Initialize service instances
|
259
282
|
self.reservations = ReservationsService(self._transport)
|
260
283
|
self.pricing = PricingService(self._transport)
|
@@ -278,7 +301,7 @@ class Bookalimo:
|
|
278
301
|
Auth priority is as follows:
|
279
302
|
- provided api key in constructor
|
280
303
|
- GOOGLE_PLACES_API_KEY environment variable
|
281
|
-
- Google ADC -
|
304
|
+
- Google ADC - Not yet implemented.
|
282
305
|
"""
|
283
306
|
if not _GOOGLE_PLACES_AVAILABLE:
|
284
307
|
raise ImportError(
|
@@ -2,31 +2,27 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from os import getenv
|
4
4
|
from types import TracebackType
|
5
|
-
from typing import Any, Optional, TypeVar
|
5
|
+
from typing import Any, Optional, TypeVar
|
6
6
|
|
7
7
|
import httpx
|
8
|
-
from google.api_core import exceptions as gexc
|
9
8
|
from google.maps.places_v1 import PlacesAsyncClient
|
10
9
|
from typing_extensions import ParamSpec
|
11
10
|
|
12
|
-
from ...exceptions import BookalimoError
|
13
11
|
from ...logging import get_logger
|
14
12
|
from ...schemas.places import FieldMaskInput
|
15
13
|
from ...schemas.places import google as models
|
16
14
|
from .common import (
|
17
15
|
DEFAULT_PLACE_FIELDS,
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
16
|
+
create_search_text_request,
|
17
|
+
handle_autocomplete_impl_async,
|
18
|
+
handle_geocode_response,
|
19
|
+
handle_get_place_impl_async,
|
20
|
+
handle_resolve_airport_postprocessing,
|
21
|
+
handle_resolve_airport_preprocessing,
|
22
|
+
handle_search_impl_async,
|
23
|
+
prepare_geocode_params,
|
25
24
|
validate_autocomplete_inputs,
|
26
|
-
validate_resolve_airport_inputs,
|
27
25
|
)
|
28
|
-
from .proto_adapter import validate_proto_to_model
|
29
|
-
from .resolve_airport import resolve_airport
|
30
26
|
from .transports import GoogleAsyncTransport
|
31
27
|
|
32
28
|
logger = get_logger("places")
|
@@ -56,10 +52,10 @@ class AsyncGooglePlaces:
|
|
56
52
|
http_client: Optional `httpx.AsyncClient` instance.
|
57
53
|
"""
|
58
54
|
self.http_client = http_client or httpx.AsyncClient()
|
59
|
-
api_key = api_key or getenv("GOOGLE_PLACES_API_KEY")
|
60
|
-
if not api_key:
|
55
|
+
self.api_key = api_key or getenv("GOOGLE_PLACES_API_KEY")
|
56
|
+
if not self.api_key:
|
61
57
|
raise ValueError("Google Places API key is required.")
|
62
|
-
self.transport = GoogleAsyncTransport(api_key, client)
|
58
|
+
self.transport = GoogleAsyncTransport(self.api_key, client)
|
63
59
|
|
64
60
|
async def __aenter__(self) -> AsyncGooglePlaces:
|
65
61
|
return self
|
@@ -99,28 +95,19 @@ class AsyncGooglePlaces:
|
|
99
95
|
BookalimoError: If the API request fails.
|
100
96
|
"""
|
101
97
|
request = validate_autocomplete_inputs(input, request)
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
return validate_proto_to_model(proto, models.AutocompletePlacesResponse)
|
107
|
-
except gexc.GoogleAPICallError as e:
|
108
|
-
msg = f"Google Places Autocomplete failed: {fmt_exc(e)}"
|
109
|
-
logger.error(msg)
|
110
|
-
raise BookalimoError(msg) from e
|
98
|
+
return await handle_autocomplete_impl_async(
|
99
|
+
lambda req: self.transport.autocomplete_places(request=req),
|
100
|
+
request,
|
101
|
+
)
|
111
102
|
|
112
103
|
async def geocode(self, request: models.GeocodingRequest) -> dict[str, Any]:
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
except httpx.HTTPError as e:
|
121
|
-
msg = f"HTTP geocoding failed: {fmt_exc(e)}"
|
122
|
-
logger.error(msg)
|
123
|
-
raise BookalimoError(msg) from e
|
104
|
+
assert self.api_key is not None # Validated in __init__
|
105
|
+
params = prepare_geocode_params(request, self.api_key)
|
106
|
+
r = await self.http_client.get(
|
107
|
+
"https://maps.googleapis.com/maps/api/geocode/json",
|
108
|
+
params=params,
|
109
|
+
)
|
110
|
+
return handle_geocode_response(r)
|
124
111
|
|
125
112
|
async def search(
|
126
113
|
self,
|
@@ -143,24 +130,13 @@ class AsyncGooglePlaces:
|
|
143
130
|
BookalimoError: If the API request fails.
|
144
131
|
ValueError: If neither query nor request is provided, or if both are provided.
|
145
132
|
"""
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
)
|
154
|
-
return normalize_search_results(protos)
|
155
|
-
except gexc.InvalidArgument as e:
|
156
|
-
# Often caused by missing/invalid field mask
|
157
|
-
msg = f"Google Places Text Search invalid argument: {fmt_exc(e)}"
|
158
|
-
logger.error(msg)
|
159
|
-
raise BookalimoError(msg) from e
|
160
|
-
except gexc.GoogleAPICallError as e:
|
161
|
-
msg = f"Google Places Text Search failed: {fmt_exc(e)}"
|
162
|
-
logger.error(msg)
|
163
|
-
raise BookalimoError(msg) from e
|
133
|
+
return await handle_search_impl_async(
|
134
|
+
lambda req, meta: self.transport.search_text(request=req, metadata=meta),
|
135
|
+
query,
|
136
|
+
request,
|
137
|
+
fields,
|
138
|
+
**kwargs,
|
139
|
+
)
|
164
140
|
|
165
141
|
async def get(
|
166
142
|
self,
|
@@ -181,31 +157,19 @@ class AsyncGooglePlaces:
|
|
181
157
|
ValueError: If neither place_id nor request is provided.
|
182
158
|
BookalimoError: If the API request fails.
|
183
159
|
"""
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
metadata=metadata,
|
191
|
-
)
|
192
|
-
return normalize_place_from_proto(proto)
|
193
|
-
except gexc.NotFound:
|
194
|
-
return None
|
195
|
-
except gexc.InvalidArgument as e:
|
196
|
-
msg = f"Google Places Get Place invalid argument: {fmt_exc(e)}"
|
197
|
-
logger.error(msg)
|
198
|
-
raise BookalimoError(msg) from e
|
199
|
-
except gexc.GoogleAPICallError as e:
|
200
|
-
msg = f"Google Places Get Place failed: {fmt_exc(e)}"
|
201
|
-
logger.error(msg)
|
202
|
-
raise BookalimoError(msg) from e
|
160
|
+
return await handle_get_place_impl_async(
|
161
|
+
lambda req, meta: self.transport.get_place(request=req, metadata=meta),
|
162
|
+
place_id,
|
163
|
+
request,
|
164
|
+
fields,
|
165
|
+
)
|
203
166
|
|
204
167
|
async def resolve_airport(
|
205
168
|
self,
|
206
169
|
query: Optional[str] = None,
|
207
170
|
place_id: Optional[str] = None,
|
208
171
|
places: Optional[list[models.Place]] = None,
|
172
|
+
country_code: Optional[str] = None,
|
209
173
|
max_distance_km: Optional[float] = 100,
|
210
174
|
max_results: Optional[int] = 5,
|
211
175
|
confidence_threshold: Optional[float] = 0.5,
|
@@ -218,6 +182,7 @@ class AsyncGooglePlaces:
|
|
218
182
|
query: Text query for airport search (optional)
|
219
183
|
place_id: Google place ID for proximity matching (optional)
|
220
184
|
places: List of existing Place objects for proximity matching (optional)
|
185
|
+
country_code: Country code for proximity matching (optional)
|
221
186
|
max_distance_km: Maximum distance for proximity matching (default: 100km)
|
222
187
|
max_results: Maximum number of results to return (default: 5)
|
223
188
|
confidence_threshold: Minimum confidence threshold (default: 0.5)
|
@@ -242,46 +207,35 @@ class AsyncGooglePlaces:
|
|
242
207
|
ValueError on invalid inputs.
|
243
208
|
BookalimoError if underlying API requests fail.
|
244
209
|
"""
|
245
|
-
#
|
246
|
-
|
210
|
+
# Handle preprocessing that doesn't depend on sync/async
|
211
|
+
preprocessed_query, preprocessed_places, needs_call = (
|
212
|
+
handle_resolve_airport_preprocessing(
|
213
|
+
query, place_id, places, max_distance_km
|
214
|
+
)
|
215
|
+
)
|
247
216
|
|
248
|
-
#
|
217
|
+
# Handle the calls that do depend on sync/async
|
249
218
|
effective_places: list[models.Place]
|
250
|
-
|
251
219
|
if place_id is not None:
|
252
220
|
place = await self.get(place_id=place_id)
|
253
221
|
if place is None:
|
254
222
|
raise ValueError(f"Place with id {place_id!r} was not found.")
|
255
223
|
effective_places = [place]
|
256
|
-
|
257
|
-
|
258
|
-
if len(places) == 0 and (query is None or not str(query).strip()):
|
259
|
-
raise ValueError(
|
260
|
-
"Empty 'places' and no 'query' provided; nothing to resolve."
|
261
|
-
)
|
262
|
-
effective_places = places
|
263
|
-
|
264
|
-
else:
|
265
|
-
# Neither place_id nor places: fall back to query-driven search
|
266
|
-
if query is None or not str(query).strip():
|
267
|
-
raise ValueError("Either place_id, places, or query must be provided.")
|
224
|
+
elif needs_call and preprocessed_places == []:
|
225
|
+
# Need to perform search
|
268
226
|
effective_places = await self.search(
|
269
|
-
request=
|
270
|
-
|
271
|
-
|
227
|
+
request=create_search_text_request(
|
228
|
+
query=str(preprocessed_query).strip(),
|
229
|
+
region_code=country_code,
|
272
230
|
)
|
273
231
|
)
|
232
|
+
else:
|
233
|
+
effective_places = preprocessed_places
|
274
234
|
|
275
|
-
#
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
p.google_place for p in effective_places if p.google_place is not None
|
280
|
-
]
|
281
|
-
|
282
|
-
return resolve_airport(
|
283
|
-
effective_query,
|
284
|
-
google_places,
|
235
|
+
# Handle postprocessing
|
236
|
+
return handle_resolve_airport_postprocessing(
|
237
|
+
preprocessed_query,
|
238
|
+
effective_places,
|
285
239
|
max_distance_km,
|
286
240
|
max_results,
|
287
241
|
confidence_threshold,
|
@@ -2,31 +2,27 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from os import getenv
|
4
4
|
from types import TracebackType
|
5
|
-
from typing import Any, Optional, TypeVar
|
5
|
+
from typing import Any, Optional, TypeVar
|
6
6
|
|
7
7
|
import httpx
|
8
|
-
from google.api_core import exceptions as gexc
|
9
8
|
from google.maps.places_v1 import PlacesClient
|
10
9
|
from typing_extensions import ParamSpec
|
11
10
|
|
12
|
-
from ...exceptions import BookalimoError
|
13
11
|
from ...logging import get_logger
|
14
12
|
from ...schemas.places import FieldMaskInput
|
15
13
|
from ...schemas.places import google as models
|
16
14
|
from .common import (
|
17
15
|
DEFAULT_PLACE_FIELDS,
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
16
|
+
create_search_text_request,
|
17
|
+
handle_autocomplete_impl,
|
18
|
+
handle_geocode_response,
|
19
|
+
handle_get_place_impl,
|
20
|
+
handle_resolve_airport_postprocessing,
|
21
|
+
handle_resolve_airport_preprocessing,
|
22
|
+
handle_search_impl,
|
23
|
+
prepare_geocode_params,
|
25
24
|
validate_autocomplete_inputs,
|
26
|
-
validate_resolve_airport_inputs,
|
27
25
|
)
|
28
|
-
from .proto_adapter import validate_proto_to_model
|
29
|
-
from .resolve_airport import resolve_airport
|
30
26
|
from .transports import GoogleSyncTransport
|
31
27
|
|
32
28
|
logger = get_logger("places")
|
@@ -56,10 +52,10 @@ class GooglePlaces:
|
|
56
52
|
http_client: Optional `httpx.Client` instance.
|
57
53
|
"""
|
58
54
|
self.http_client = http_client or httpx.Client()
|
59
|
-
api_key = api_key or getenv("GOOGLE_PLACES_API_KEY")
|
60
|
-
if not api_key:
|
55
|
+
self.api_key = api_key or getenv("GOOGLE_PLACES_API_KEY")
|
56
|
+
if not self.api_key:
|
61
57
|
raise ValueError("Google Places API key is required.")
|
62
|
-
self.transport = GoogleSyncTransport(api_key, client)
|
58
|
+
self.transport = GoogleSyncTransport(self.api_key, client)
|
63
59
|
|
64
60
|
def __enter__(self) -> GooglePlaces:
|
65
61
|
return self
|
@@ -99,26 +95,19 @@ class GooglePlaces:
|
|
99
95
|
BookalimoError: If the API request fails.
|
100
96
|
"""
|
101
97
|
request = validate_autocomplete_inputs(input, request)
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
msg = f"Google Places Autocomplete failed: {fmt_exc(e)}"
|
107
|
-
logger.error(msg)
|
108
|
-
raise BookalimoError(msg) from e
|
98
|
+
return handle_autocomplete_impl(
|
99
|
+
lambda req: self.transport.autocomplete_places(request=req),
|
100
|
+
request,
|
101
|
+
)
|
109
102
|
|
110
103
|
def geocode(self, request: models.GeocodingRequest) -> dict[str, Any]:
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
except httpx.HTTPError as e:
|
119
|
-
msg = f"HTTP geocoding failed: {fmt_exc(e)}"
|
120
|
-
logger.error(msg)
|
121
|
-
raise BookalimoError(msg) from e
|
104
|
+
assert self.api_key is not None # Validated in __init__
|
105
|
+
params = prepare_geocode_params(request, self.api_key)
|
106
|
+
r = self.http_client.get(
|
107
|
+
"https://maps.googleapis.com/maps/api/geocode/json",
|
108
|
+
params=params,
|
109
|
+
)
|
110
|
+
return handle_geocode_response(r)
|
122
111
|
|
123
112
|
def search(
|
124
113
|
self,
|
@@ -141,24 +130,13 @@ class GooglePlaces:
|
|
141
130
|
BookalimoError: If the API request fails.
|
142
131
|
ValueError: If neither query nor request is provided, or if both are provided.
|
143
132
|
"""
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
)
|
152
|
-
return normalize_search_results(protos)
|
153
|
-
except gexc.InvalidArgument as e:
|
154
|
-
# Often caused by missing/invalid field mask
|
155
|
-
msg = f"Google Places Text Search invalid argument: {fmt_exc(e)}"
|
156
|
-
logger.error(msg)
|
157
|
-
raise BookalimoError(msg) from e
|
158
|
-
except gexc.GoogleAPICallError as e:
|
159
|
-
msg = f"Google Places Text Search failed: {fmt_exc(e)}"
|
160
|
-
logger.error(msg)
|
161
|
-
raise BookalimoError(msg) from e
|
133
|
+
return handle_search_impl(
|
134
|
+
lambda req, meta: self.transport.search_text(request=req, metadata=meta),
|
135
|
+
query,
|
136
|
+
request,
|
137
|
+
fields,
|
138
|
+
**kwargs,
|
139
|
+
)
|
162
140
|
|
163
141
|
def get(
|
164
142
|
self,
|
@@ -179,31 +157,19 @@ class GooglePlaces:
|
|
179
157
|
ValueError: If neither place_id nor request is provided.
|
180
158
|
BookalimoError: If the API request fails.
|
181
159
|
"""
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
metadata=metadata,
|
189
|
-
)
|
190
|
-
return normalize_place_from_proto(proto)
|
191
|
-
except gexc.NotFound:
|
192
|
-
return None
|
193
|
-
except gexc.InvalidArgument as e:
|
194
|
-
msg = f"Google Places Get Place invalid argument: {fmt_exc(e)}"
|
195
|
-
logger.error(msg)
|
196
|
-
raise BookalimoError(msg) from e
|
197
|
-
except gexc.GoogleAPICallError as e:
|
198
|
-
msg = f"Google Places Get Place failed: {fmt_exc(e)}"
|
199
|
-
logger.error(msg)
|
200
|
-
raise BookalimoError(msg) from e
|
160
|
+
return handle_get_place_impl(
|
161
|
+
lambda req, meta: self.transport.get_place(request=req, metadata=meta),
|
162
|
+
place_id,
|
163
|
+
request,
|
164
|
+
fields,
|
165
|
+
)
|
201
166
|
|
202
167
|
def resolve_airport(
|
203
168
|
self,
|
204
169
|
query: Optional[str] = None,
|
205
170
|
place_id: Optional[str] = None,
|
206
171
|
places: Optional[list[models.Place]] = None,
|
172
|
+
country_code: Optional[str] = None,
|
207
173
|
max_distance_km: Optional[float] = 100,
|
208
174
|
max_results: Optional[int] = 5,
|
209
175
|
confidence_threshold: Optional[float] = 0.5,
|
@@ -216,6 +182,7 @@ class GooglePlaces:
|
|
216
182
|
query: Text query for airport search (optional)
|
217
183
|
place_id: Google place ID for proximity matching (optional)
|
218
184
|
places: List of existing Place objects for proximity matching (optional)
|
185
|
+
country_code: Country code for proximity matching (optional)
|
219
186
|
max_distance_km: Maximum distance for proximity matching (default: 100km)
|
220
187
|
max_results: Maximum number of results to return (default: 5)
|
221
188
|
confidence_threshold: Minimum confidence threshold (default: 0.5)
|
@@ -240,46 +207,35 @@ class GooglePlaces:
|
|
240
207
|
ValueError on invalid inputs.
|
241
208
|
BookalimoError if underlying API requests fail.
|
242
209
|
"""
|
243
|
-
#
|
244
|
-
|
210
|
+
# Handle preprocessing that doesn't depend on sync/async
|
211
|
+
preprocessed_query, preprocessed_places, needs_call = (
|
212
|
+
handle_resolve_airport_preprocessing(
|
213
|
+
query, place_id, places, max_distance_km
|
214
|
+
)
|
215
|
+
)
|
245
216
|
|
246
|
-
#
|
217
|
+
# Handle the calls that do depend on sync/async
|
247
218
|
effective_places: list[models.Place]
|
248
|
-
|
249
219
|
if place_id is not None:
|
250
220
|
place = self.get(place_id=place_id)
|
251
221
|
if place is None:
|
252
222
|
raise ValueError(f"Place with id {place_id!r} was not found.")
|
253
223
|
effective_places = [place]
|
254
|
-
|
255
|
-
|
256
|
-
if len(places) == 0 and (query is None or not str(query).strip()):
|
257
|
-
raise ValueError(
|
258
|
-
"Empty 'places' and no 'query' provided; nothing to resolve."
|
259
|
-
)
|
260
|
-
effective_places = places
|
261
|
-
|
262
|
-
else:
|
263
|
-
# Neither place_id nor places: fall back to query-driven search
|
264
|
-
if query is None or not str(query).strip():
|
265
|
-
raise ValueError("Either place_id, places, or query must be provided.")
|
224
|
+
elif needs_call and preprocessed_places == []:
|
225
|
+
# Need to perform search
|
266
226
|
effective_places = self.search(
|
267
|
-
request=
|
268
|
-
|
269
|
-
|
227
|
+
request=create_search_text_request(
|
228
|
+
query=str(preprocessed_query).strip(),
|
229
|
+
region_code=country_code,
|
270
230
|
)
|
271
231
|
)
|
232
|
+
else:
|
233
|
+
effective_places = preprocessed_places
|
272
234
|
|
273
|
-
#
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
p.google_place for p in effective_places if p.google_place is not None
|
278
|
-
]
|
279
|
-
|
280
|
-
return resolve_airport(
|
281
|
-
effective_query,
|
282
|
-
google_places,
|
235
|
+
# Handle postprocessing
|
236
|
+
return handle_resolve_airport_postprocessing(
|
237
|
+
preprocessed_query,
|
238
|
+
effective_places,
|
283
239
|
max_distance_km,
|
284
240
|
max_results,
|
285
241
|
confidence_threshold,
|