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.
- bookalimo/client.py +45 -22
- bookalimo/config.py +1 -1
- bookalimo/integrations/google_places/client_async.py +141 -156
- bookalimo/integrations/google_places/client_sync.py +142 -156
- bookalimo/integrations/google_places/common.py +464 -200
- bookalimo/integrations/google_places/resolve_airport.py +426 -0
- bookalimo/integrations/google_places/transports.py +105 -0
- bookalimo/logging.py +103 -0
- bookalimo/schemas/__init__.py +126 -34
- bookalimo/schemas/base.py +74 -14
- bookalimo/schemas/places/__init__.py +27 -0
- bookalimo/schemas/places/common.py +155 -2
- bookalimo/schemas/places/field_mask.py +212 -0
- bookalimo/schemas/places/google.py +458 -16
- bookalimo/schemas/places/place.py +25 -28
- 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.2.dist-info/licenses/LICENSE +21 -0
- bookalimo-1.0.0.dist-info/METADATA +0 -307
- bookalimo-1.0.0.dist-info/RECORD +0 -35
- bookalimo-1.0.0.dist-info/licenses/LICENSE +0 -0
- {bookalimo-1.0.0.dist-info → bookalimo-1.0.2.dist-info}/WHEEL +0 -0
- {bookalimo-1.0.0.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(
|
bookalimo/config.py
CHANGED
@@ -5,7 +5,7 @@ from ._version import __version__
|
|
5
5
|
# Default API configuration
|
6
6
|
DEFAULT_BASE_URL = "https://www.bookalimo.com/web/api"
|
7
7
|
DEFAULT_TIMEOUT = 5.0
|
8
|
-
DEFAULT_USER_AGENT = f"bookalimo-python/{__version__}
|
8
|
+
DEFAULT_USER_AGENT = f"bookalimo-python/{__version__}"
|
9
9
|
|
10
10
|
# Default retry configuration
|
11
11
|
DEFAULT_RETRIES = 2
|
@@ -1,30 +1,29 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import re
|
4
3
|
from os import getenv
|
5
4
|
from types import TracebackType
|
6
|
-
from typing import Any, Optional, TypeVar
|
5
|
+
from typing import Any, Optional, TypeVar
|
7
6
|
|
8
7
|
import httpx
|
9
|
-
from google.api_core import exceptions as gexc
|
10
|
-
from google.api_core.client_options import ClientOptions
|
11
8
|
from google.maps.places_v1 import PlacesAsyncClient
|
12
9
|
from typing_extensions import ParamSpec
|
13
10
|
|
14
|
-
from ...exceptions import BookalimoError
|
15
11
|
from ...logging import get_logger
|
12
|
+
from ...schemas.places import FieldMaskInput
|
16
13
|
from ...schemas.places import google as models
|
17
|
-
from ...schemas.places.place import Place as GooglePlace
|
18
14
|
from .common import (
|
19
|
-
ADDRESS_TYPES,
|
20
15
|
DEFAULT_PLACE_FIELDS,
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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,
|
24
|
+
validate_autocomplete_inputs,
|
26
25
|
)
|
27
|
-
from .
|
26
|
+
from .transports import GoogleAsyncTransport
|
28
27
|
|
29
28
|
logger = get_logger("places")
|
30
29
|
|
@@ -32,40 +31,9 @@ P = ParamSpec("P")
|
|
32
31
|
R = TypeVar("R")
|
33
32
|
|
34
33
|
|
35
|
-
def _strip_html(s: str) -> str:
|
36
|
-
# Simple fallback for adr_format_address (which is HTML)
|
37
|
-
return re.sub(r"<[^>]+>", "", s) if s else s
|
38
|
-
|
39
|
-
|
40
|
-
def _infer_place_type(m: GooglePlace) -> str:
|
41
|
-
# 1) Airport wins outright
|
42
|
-
tset = set(m.types or [])
|
43
|
-
ptype = (m.primary_type or "").lower() if getattr(m, "primary_type", None) else ""
|
44
|
-
if "airport" in tset or ptype == "airport":
|
45
|
-
return "airport"
|
46
|
-
# 2) Anything that looks like a geocoded address
|
47
|
-
if tset & ADDRESS_TYPES:
|
48
|
-
return "address"
|
49
|
-
# 3) Otherwise treat as a point of interest
|
50
|
-
return "poi"
|
51
|
-
|
52
|
-
|
53
|
-
def _get_lat_lng(model: GooglePlace) -> tuple[float, float]:
|
54
|
-
if model.location:
|
55
|
-
lat = model.location.latitude
|
56
|
-
lng = model.location.longitude
|
57
|
-
elif model.viewport:
|
58
|
-
lat = model.viewport.low.latitude
|
59
|
-
lng = model.viewport.low.longitude
|
60
|
-
else:
|
61
|
-
lat = 0
|
62
|
-
lng = 0
|
63
|
-
return lat, lng
|
64
|
-
|
65
|
-
|
66
34
|
class AsyncGooglePlaces:
|
67
35
|
"""
|
68
|
-
Google Places API client for address validation, geocoding, and autocomplete.
|
36
|
+
Google Places API asynchronous client for address validation, geocoding, and autocomplete.
|
69
37
|
Provides location resolution services that integrate seamlessly with
|
70
38
|
Book-A-Limo location factory functions.
|
71
39
|
"""
|
@@ -84,15 +52,10 @@ class AsyncGooglePlaces:
|
|
84
52
|
http_client: Optional `httpx.AsyncClient` instance.
|
85
53
|
"""
|
86
54
|
self.http_client = http_client or httpx.AsyncClient()
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
if not api_key:
|
92
|
-
raise ValueError("Google Places API key is required.")
|
93
|
-
self.client = PlacesAsyncClient(
|
94
|
-
client_options=ClientOptions(api_key=api_key),
|
95
|
-
)
|
55
|
+
self.api_key = api_key or getenv("GOOGLE_PLACES_API_KEY")
|
56
|
+
if not self.api_key:
|
57
|
+
raise ValueError("Google Places API key is required.")
|
58
|
+
self.transport = GoogleAsyncTransport(self.api_key, client)
|
96
59
|
|
97
60
|
async def __aenter__(self) -> AsyncGooglePlaces:
|
98
61
|
return self
|
@@ -108,151 +71,173 @@ class AsyncGooglePlaces:
|
|
108
71
|
async def aclose(self) -> None:
|
109
72
|
"""Close underlying transports safely."""
|
110
73
|
try:
|
111
|
-
await self.
|
74
|
+
await self.transport.close()
|
112
75
|
finally:
|
113
76
|
await self.http_client.aclose()
|
114
77
|
|
115
78
|
async def autocomplete(
|
116
|
-
self,
|
79
|
+
self,
|
80
|
+
input: Optional[str] = None,
|
81
|
+
*,
|
82
|
+
request: Optional[models.AutocompletePlacesRequest] = None,
|
117
83
|
) -> models.AutocompletePlacesResponse:
|
118
84
|
"""
|
119
85
|
Get autocomplete suggestions for a location query.
|
120
86
|
Args:
|
87
|
+
input: The text string on which to search.
|
121
88
|
request: AutocompletePlacesRequest object.
|
122
|
-
**kwargs: Additional parameters for the Google Places Autocomplete API.
|
123
89
|
Returns:
|
124
90
|
`AutocompletePlacesResponse` object.
|
91
|
+
Note:
|
92
|
+
If both input and request are provided, request will be used.
|
125
93
|
Raises:
|
94
|
+
ValueError: If neither input nor request is provided, or if both are provided.
|
126
95
|
BookalimoError: If the API request fails.
|
127
96
|
"""
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
except gexc.GoogleAPICallError as e:
|
134
|
-
msg = f"Google Places Autocomplete failed: {fmt_exc(e)}"
|
135
|
-
logger.error(msg)
|
136
|
-
raise BookalimoError(msg) from e
|
97
|
+
request = validate_autocomplete_inputs(input, request)
|
98
|
+
return await handle_autocomplete_impl_async(
|
99
|
+
lambda req: self.transport.autocomplete_places(request=req),
|
100
|
+
request,
|
101
|
+
)
|
137
102
|
|
138
103
|
async def geocode(self, request: models.GeocodingRequest) -> dict[str, Any]:
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
except httpx.HTTPError as e:
|
147
|
-
msg = f"HTTP geocoding failed: {fmt_exc(e)}"
|
148
|
-
logger.error(msg)
|
149
|
-
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)
|
150
111
|
|
151
112
|
async def search(
|
152
113
|
self,
|
153
|
-
query: str,
|
114
|
+
query: Optional[str] = None,
|
154
115
|
*,
|
155
|
-
|
116
|
+
request: Optional[models.SearchTextRequest] = None,
|
117
|
+
fields: FieldMaskInput = DEFAULT_PLACE_FIELDS,
|
156
118
|
**kwargs: Any,
|
157
119
|
) -> list[models.Place]:
|
158
120
|
"""
|
159
|
-
Search for places using a text query.
|
121
|
+
Search for places using a text query or SearchTextRequest.
|
160
122
|
Args:
|
161
|
-
query:
|
123
|
+
query: Simple text query to search for. Either query or request must be provided.
|
124
|
+
request: SearchTextRequest object with advanced search parameters. Either query or request must be provided.
|
125
|
+
fields: Field mask for response data.
|
162
126
|
**kwargs: Additional parameters for the Text Search API.
|
163
127
|
Returns:
|
164
128
|
list[models.Place]
|
165
129
|
Raises:
|
166
130
|
BookalimoError: If the API request fails.
|
131
|
+
ValueError: If neither query nor request is provided, or if both are provided.
|
167
132
|
"""
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
validate_proto_to_model(proto, GooglePlace) for proto in protos.places
|
176
|
-
]
|
177
|
-
place_models: list[models.Place] = []
|
178
|
-
for model in pydantic_models:
|
179
|
-
adr_format = getattr(model, "adr_format_address", None)
|
180
|
-
addr = (
|
181
|
-
getattr(model, "formatted_address", None)
|
182
|
-
or getattr(model, "short_formatted_address", None)
|
183
|
-
or _strip_html(adr_format or "")
|
184
|
-
or ""
|
185
|
-
)
|
186
|
-
lat, lng = _get_lat_lng(model)
|
187
|
-
place_models.append(
|
188
|
-
models.Place(
|
189
|
-
formatted_address=addr,
|
190
|
-
lat=lat,
|
191
|
-
lng=lng,
|
192
|
-
place_type=_infer_place_type(model),
|
193
|
-
iata_code=None,
|
194
|
-
google_place=model,
|
195
|
-
)
|
196
|
-
)
|
197
|
-
return place_models
|
198
|
-
except gexc.InvalidArgument as e:
|
199
|
-
# Often caused by missing/invalid field mask
|
200
|
-
msg = f"Google Places Text Search invalid argument: {fmt_exc(e)}"
|
201
|
-
logger.error(msg)
|
202
|
-
raise BookalimoError(msg) from e
|
203
|
-
except gexc.GoogleAPICallError as e:
|
204
|
-
msg = f"Google Places Text Search failed: {fmt_exc(e)}"
|
205
|
-
logger.error(msg)
|
206
|
-
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
|
+
)
|
207
140
|
|
208
141
|
async def get(
|
209
142
|
self,
|
210
|
-
place_id:
|
143
|
+
place_id: Optional[str] = None,
|
211
144
|
*,
|
212
|
-
|
213
|
-
|
145
|
+
request: Optional[models.GetPlaceRequest] = None,
|
146
|
+
fields: FieldMaskInput = DEFAULT_PLACE_FIELDS,
|
214
147
|
) -> Optional[models.Place]:
|
215
148
|
"""
|
216
149
|
Get details for a specific place.
|
217
150
|
Args:
|
218
151
|
place_id: The ID of the place to retrieve details for.
|
219
|
-
|
152
|
+
request: GetPlaceRequest object with place resource name.
|
153
|
+
fields: Optional field mask for response data.
|
220
154
|
Returns:
|
221
155
|
A models.Place object or None if not found.
|
222
156
|
Raises:
|
157
|
+
ValueError: If neither place_id nor request is provided.
|
223
158
|
BookalimoError: If the API request fails.
|
224
159
|
"""
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
+
)
|
166
|
+
|
167
|
+
async def resolve_airport(
|
168
|
+
self,
|
169
|
+
query: Optional[str] = None,
|
170
|
+
place_id: Optional[str] = None,
|
171
|
+
places: Optional[list[models.Place]] = None,
|
172
|
+
country_code: Optional[str] = None,
|
173
|
+
max_distance_km: Optional[float] = 100,
|
174
|
+
max_results: Optional[int] = 5,
|
175
|
+
confidence_threshold: Optional[float] = 0.5,
|
176
|
+
text_weight: float = 0.5,
|
177
|
+
) -> list[models.ResolvedAirport]:
|
178
|
+
"""
|
179
|
+
Resolve airport candidates given either a natural language text query, a place_id, or a list of Places.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
query: Text query for airport search (optional)
|
183
|
+
place_id: Google place ID for proximity matching (optional)
|
184
|
+
places: List of existing Place objects for proximity matching (optional)
|
185
|
+
country_code: Country code for proximity matching (optional)
|
186
|
+
max_distance_km: Maximum distance for proximity matching (default: 100km)
|
187
|
+
max_results: Maximum number of results to return (default: 5)
|
188
|
+
confidence_threshold: Minimum confidence threshold (default: 0.5)
|
189
|
+
text_weight: Weight for text search (default: 0.5) If 0.0, only proximity will be used. If 1.0, only text will be used.
|
190
|
+
|
191
|
+
Rules:
|
192
|
+
- Provide at most one of {place_id, places}. (query may accompany either.)
|
193
|
+
- If nothing but query is given, search for places from the query.
|
194
|
+
- If place_id is given:
|
195
|
+
* Fetch the place.
|
196
|
+
* If no explicit query, derive it from the place's display name.
|
197
|
+
- If places is given:
|
198
|
+
* If len(places) == 0 and no query, error.
|
199
|
+
* If len(places) == 1 and no query, derive query from that place's display name.
|
200
|
+
* If len(places) > 1 and no query, error (need query to disambiguate).
|
201
|
+
- If nothing is provided, error.
|
202
|
+
- If max_distance_km is provided, it must be > 0.
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
list[models.ResolvedAirport]
|
206
|
+
Raises:
|
207
|
+
ValueError on invalid inputs.
|
208
|
+
BookalimoError if underlying API requests fail.
|
209
|
+
"""
|
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
|
239
214
|
)
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
215
|
+
)
|
216
|
+
|
217
|
+
# Handle the calls that do depend on sync/async
|
218
|
+
effective_places: list[models.Place]
|
219
|
+
if place_id is not None:
|
220
|
+
place = await self.get(place_id=place_id)
|
221
|
+
if place is None:
|
222
|
+
raise ValueError(f"Place with id {place_id!r} was not found.")
|
223
|
+
effective_places = [place]
|
224
|
+
elif needs_call and preprocessed_places == []:
|
225
|
+
# Need to perform search
|
226
|
+
effective_places = await self.search(
|
227
|
+
request=create_search_text_request(
|
228
|
+
query=str(preprocessed_query).strip(),
|
229
|
+
region_code=country_code,
|
230
|
+
)
|
248
231
|
)
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
232
|
+
else:
|
233
|
+
effective_places = preprocessed_places
|
234
|
+
|
235
|
+
# Handle postprocessing
|
236
|
+
return handle_resolve_airport_postprocessing(
|
237
|
+
preprocessed_query,
|
238
|
+
effective_places,
|
239
|
+
max_distance_km,
|
240
|
+
max_results,
|
241
|
+
confidence_threshold,
|
242
|
+
text_weight,
|
243
|
+
)
|