bookalimo 1.0.0__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.
- bookalimo/config.py +1 -1
- bookalimo/integrations/google_places/client_async.py +139 -108
- bookalimo/integrations/google_places/client_sync.py +139 -109
- bookalimo/integrations/google_places/common.py +186 -200
- bookalimo/integrations/google_places/resolve_airport.py +397 -0
- bookalimo/integrations/google_places/transports.py +98 -0
- bookalimo/schemas/__init__.py +6 -0
- bookalimo/schemas/places/__init__.py +25 -0
- bookalimo/schemas/places/common.py +155 -2
- bookalimo/schemas/places/field_mask.py +221 -0
- bookalimo/schemas/places/google.py +293 -6
- bookalimo/schemas/places/place.py +25 -28
- {bookalimo-1.0.0.dist-info → bookalimo-1.0.1.dist-info}/METADATA +132 -69
- {bookalimo-1.0.0.dist-info → bookalimo-1.0.1.dist-info}/RECORD +17 -14
- bookalimo-1.0.1.dist-info/licenses/LICENSE +21 -0
- bookalimo-1.0.0.dist-info/licenses/LICENSE +0 -0
- {bookalimo-1.0.0.dist-info → bookalimo-1.0.1.dist-info}/WHEEL +0 -0
- {bookalimo-1.0.0.dist-info → bookalimo-1.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,397 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import csv
|
4
|
+
import math
|
5
|
+
import os
|
6
|
+
import re
|
7
|
+
import unicodedata
|
8
|
+
from functools import lru_cache
|
9
|
+
from importlib.resources import files
|
10
|
+
from typing import Any, Optional, cast
|
11
|
+
|
12
|
+
import numpy as np
|
13
|
+
from numpy.typing import NDArray
|
14
|
+
from rapidfuzz import fuzz, process
|
15
|
+
|
16
|
+
from bookalimo.schemas.places import GooglePlace, ResolvedAirport
|
17
|
+
|
18
|
+
# ---------- Config ----------
|
19
|
+
CSV_PATH = os.environ.get(
|
20
|
+
"AIRPORTS_CSV", str(files("airportsdata").joinpath("airports.csv"))
|
21
|
+
)
|
22
|
+
DEFAULT_MAX_RESULTS = 20 # number of airports to return
|
23
|
+
DIST_KM_SCALE = 200.0 # distance scale for proximity confidence
|
24
|
+
|
25
|
+
# Google types that clearly indicate “airport-ish” places
|
26
|
+
AIRPORTY_TYPES = {
|
27
|
+
"airport",
|
28
|
+
"international_airport",
|
29
|
+
"airstrip",
|
30
|
+
"heliport",
|
31
|
+
}
|
32
|
+
|
33
|
+
# Small bonus when a candidate airport’s IATA/ICAO matches codes hinted by Places
|
34
|
+
CODE_BONUS_QUERY = 15.0 # user typed a code (strong)
|
35
|
+
CODE_BONUS_PLACES = 8.0 # code inferred from Places strings (softer)
|
36
|
+
|
37
|
+
|
38
|
+
# ---------- Helpers ----------
|
39
|
+
def _norm(s: Optional[str]) -> str:
|
40
|
+
if not s:
|
41
|
+
return ""
|
42
|
+
s = unicodedata.normalize("NFKD", s)
|
43
|
+
s = "".join(ch for ch in s if not unicodedata.combining(ch))
|
44
|
+
s = s.lower()
|
45
|
+
s = re.sub(r"[^a-z0-9]+", " ", s).strip()
|
46
|
+
return s
|
47
|
+
|
48
|
+
|
49
|
+
def _haversine_km_scalar_to_many(
|
50
|
+
lat1_rad: float,
|
51
|
+
lon1_rad: float,
|
52
|
+
lat2_rad: NDArray[np.float64],
|
53
|
+
lon2_rad: NDArray[np.float64],
|
54
|
+
) -> NDArray[np.float64]:
|
55
|
+
dlat = lat2_rad - lat1_rad
|
56
|
+
dlon = lon2_rad - lon1_rad
|
57
|
+
a = (
|
58
|
+
np.sin(dlat / 2.0) ** 2
|
59
|
+
+ np.cos(lat1_rad) * np.cos(lat2_rad) * np.sin(dlon / 2.0) ** 2
|
60
|
+
)
|
61
|
+
c: NDArray[np.float64] = 2.0 * np.arcsin(np.sqrt(a))
|
62
|
+
return cast(NDArray[np.float64], 6371.0088 * c) # mean Earth radius (km)
|
63
|
+
|
64
|
+
|
65
|
+
def _looks_like_code(q: str) -> tuple[Optional[str], Optional[str]]:
|
66
|
+
q = q.strip().upper()
|
67
|
+
if re.fullmatch(r"[A-Z0-9]{3}", q):
|
68
|
+
return (q, None) # likely IATA
|
69
|
+
if re.fullmatch(r"[A-Z0-9]{4}", q):
|
70
|
+
return (None, q) # likely ICAO
|
71
|
+
return (None, None)
|
72
|
+
|
73
|
+
|
74
|
+
def _extract_codes_from_text(s: str) -> tuple[set[str], set[str]]:
|
75
|
+
"""
|
76
|
+
Pull 3- or 4-char uppercase tokens that *could* be codes.
|
77
|
+
We'll only use these with a small bonus and only if the place looks airport-ish.
|
78
|
+
"""
|
79
|
+
tokens = set(re.findall(r"\b[A-Z0-9]{3,4}\b", s.upper()))
|
80
|
+
iata = {t for t in tokens if re.fullmatch(r"[A-Z]{3}", t)}
|
81
|
+
icao = {t for t in tokens if re.fullmatch(r"[A-Z0-9]{4}", t)}
|
82
|
+
return iata, icao
|
83
|
+
|
84
|
+
|
85
|
+
def _place_points(places: list[GooglePlace]) -> list[tuple[float, float]]:
|
86
|
+
"""
|
87
|
+
Extract (lat, lon) from Places responses. Prefers 'location', then viewport center,
|
88
|
+
then Plus Code (if the openlocationcode lib is available).
|
89
|
+
"""
|
90
|
+
pts: list[tuple[float, float]] = []
|
91
|
+
for p in places or []:
|
92
|
+
# p.location (LatLng)
|
93
|
+
loc = getattr(p, "location", None)
|
94
|
+
if loc is not None and hasattr(loc, "latitude") and hasattr(loc, "longitude"):
|
95
|
+
pts.append((float(loc.latitude), float(loc.longitude)))
|
96
|
+
continue
|
97
|
+
|
98
|
+
# p.viewport (Viewport -> center)
|
99
|
+
vp = getattr(p, "viewport", None)
|
100
|
+
if vp is not None and hasattr(vp, "high") and hasattr(vp, "low"):
|
101
|
+
try:
|
102
|
+
lat = (float(vp.high.latitude) + float(vp.low.latitude)) / 2.0
|
103
|
+
lon = (float(vp.high.longitude) + float(vp.low.longitude)) / 2.0
|
104
|
+
pts.append((lat, lon))
|
105
|
+
continue
|
106
|
+
except Exception:
|
107
|
+
pass
|
108
|
+
return pts
|
109
|
+
|
110
|
+
|
111
|
+
def _place_hints_and_codes(
|
112
|
+
places: list[GooglePlace],
|
113
|
+
) -> tuple[list[str], set[str], set[str]]:
|
114
|
+
"""
|
115
|
+
Collect a few high-utility strings from Places to augment text matching,
|
116
|
+
plus soft code candidates (IATA/ICAO) extracted from those strings.
|
117
|
+
We prioritize places whose types include airport-ish categories.
|
118
|
+
"""
|
119
|
+
hints_prioritized: list[str] = []
|
120
|
+
hints_general: list[str] = []
|
121
|
+
iata_cand: set[str] = set()
|
122
|
+
icao_cand: set[str] = set()
|
123
|
+
|
124
|
+
for p in places or []:
|
125
|
+
types = set(getattr(p, "types", []) or [])
|
126
|
+
primary = getattr(p, "primary_type", None) or ""
|
127
|
+
airporty = bool(types & AIRPORTY_TYPES) or (primary in AIRPORTY_TYPES)
|
128
|
+
|
129
|
+
# Display name & address
|
130
|
+
disp = getattr(p, "display_name", None)
|
131
|
+
disp_txt = getattr(disp, "text", None) if disp is not None else None
|
132
|
+
addr = getattr(p, "formatted_address", None)
|
133
|
+
|
134
|
+
# A few relational names (areas/landmarks)
|
135
|
+
adesc = getattr(p, "address_descriptor", None)
|
136
|
+
area_names = []
|
137
|
+
lm_names = []
|
138
|
+
if adesc is not None:
|
139
|
+
for a in (getattr(adesc, "areas", []) or [])[:2]:
|
140
|
+
dn = getattr(a, "display_name", None)
|
141
|
+
if dn and getattr(dn, "text", None):
|
142
|
+
area_names.append(dn.text)
|
143
|
+
for lm in (getattr(adesc, "landmarks", []) or [])[:2]:
|
144
|
+
dn = getattr(lm, "display_name", None)
|
145
|
+
if dn and getattr(dn, "text", None):
|
146
|
+
lm_names.append(dn.text)
|
147
|
+
|
148
|
+
# Gather hint candidates
|
149
|
+
candidates = [disp_txt, addr, *area_names, *lm_names]
|
150
|
+
candidates = [c for c in candidates if c]
|
151
|
+
if not candidates:
|
152
|
+
continue
|
153
|
+
|
154
|
+
# Extract soft code candidates from the most descriptive strings
|
155
|
+
for s in candidates[:2]:
|
156
|
+
i3, i4 = _extract_codes_from_text(s)
|
157
|
+
if airporty:
|
158
|
+
iata_cand |= i3
|
159
|
+
icao_cand |= i4
|
160
|
+
|
161
|
+
# Prioritize hints if the place is airport-ish
|
162
|
+
(hints_prioritized if airporty else hints_general).extend(candidates[:2])
|
163
|
+
|
164
|
+
# De-dup (by normalized form) and cap to keep RF calls small
|
165
|
+
seen = set()
|
166
|
+
|
167
|
+
def dedup_cap(items: list[str], cap: int) -> list[str]:
|
168
|
+
out = []
|
169
|
+
for s in items:
|
170
|
+
k = _norm(s)
|
171
|
+
if not k or k in seen:
|
172
|
+
continue
|
173
|
+
out.append(s)
|
174
|
+
seen.add(k)
|
175
|
+
if len(out) >= cap:
|
176
|
+
break
|
177
|
+
return out
|
178
|
+
|
179
|
+
hints = dedup_cap(hints_prioritized, cap=3) + dedup_cap(hints_general, cap=2)
|
180
|
+
return hints, iata_cand, icao_cand
|
181
|
+
|
182
|
+
|
183
|
+
def _parse_coord(s: Optional[str]) -> float:
|
184
|
+
"""Return float value or NaN for None/blank/invalid strings."""
|
185
|
+
if s is None:
|
186
|
+
return float("nan")
|
187
|
+
s = s.strip()
|
188
|
+
if s == "":
|
189
|
+
return float("nan")
|
190
|
+
try:
|
191
|
+
return float(s)
|
192
|
+
except ValueError:
|
193
|
+
return float("nan")
|
194
|
+
|
195
|
+
|
196
|
+
@lru_cache(maxsize=1)
|
197
|
+
def _load_data() -> dict[str, Any]:
|
198
|
+
"""
|
199
|
+
Loads and caches airport rows and vectorized fields.
|
200
|
+
Expects CSV columns: icao,iata,name,city,subd,country,elevation,lat,lon,tz,lid
|
201
|
+
"""
|
202
|
+
rows: list[dict[str, Any]] = []
|
203
|
+
lat_rad: list[float] = []
|
204
|
+
lon_rad: list[float] = []
|
205
|
+
keys: list[str] = [] # normalized text used for fuzzy matching
|
206
|
+
codes: list[tuple[str, str]] = [] # (iata, icao)
|
207
|
+
has_coords: list[bool] = []
|
208
|
+
|
209
|
+
with open(CSV_PATH, newline="", encoding="utf-8") as f:
|
210
|
+
reader = csv.DictReader(f)
|
211
|
+
for r in reader:
|
212
|
+
name = (r.get("name") or "").strip()
|
213
|
+
city = (r.get("city") or "").strip()
|
214
|
+
iata = (r.get("iata") or "").strip() or None
|
215
|
+
icao = (r.get("icao") or "").strip() or None
|
216
|
+
|
217
|
+
# Robust coords: keep NaN if missing/invalid
|
218
|
+
lat_s = cast(Optional[str], r.get("lat"))
|
219
|
+
lon_s = cast(Optional[str], r.get("lon"))
|
220
|
+
lat = _parse_coord(lat_s)
|
221
|
+
lon = _parse_coord(lon_s)
|
222
|
+
|
223
|
+
valid = not (math.isnan(lat) or math.isnan(lon))
|
224
|
+
|
225
|
+
rows.append(
|
226
|
+
{
|
227
|
+
"name": name,
|
228
|
+
"city": city,
|
229
|
+
"iata": iata,
|
230
|
+
"icao": icao,
|
231
|
+
"lat": lat,
|
232
|
+
"lon": lon,
|
233
|
+
}
|
234
|
+
)
|
235
|
+
lat_rad.append(math.radians(lat) if valid else float("nan"))
|
236
|
+
lon_rad.append(math.radians(lon) if valid else float("nan"))
|
237
|
+
has_coords.append(valid)
|
238
|
+
|
239
|
+
code_bits = (
|
240
|
+
" ".join([c for c in (iata, icao) if c]) if (iata or icao) else ""
|
241
|
+
)
|
242
|
+
keys.append(_norm(f"{name} {city} {code_bits}"))
|
243
|
+
codes.append((iata or "", icao or ""))
|
244
|
+
|
245
|
+
return {
|
246
|
+
"rows": rows,
|
247
|
+
"lat_rad": np.array(lat_rad, dtype=float),
|
248
|
+
"lon_rad": np.array(lon_rad, dtype=float),
|
249
|
+
"keys": np.array(keys, dtype=object),
|
250
|
+
"codes": codes,
|
251
|
+
"has_coords": np.array(has_coords, dtype=bool),
|
252
|
+
}
|
253
|
+
|
254
|
+
|
255
|
+
# ---------- Main ----------
|
256
|
+
def resolve_airport(
|
257
|
+
query: str,
|
258
|
+
places_response: list[GooglePlace],
|
259
|
+
max_distance_km: Optional[float] = 200,
|
260
|
+
max_results: Optional[int] = 5,
|
261
|
+
confidence_threshold: Optional[float] = 0.5,
|
262
|
+
text_weight: float = 0.5,
|
263
|
+
) -> list[ResolvedAirport]:
|
264
|
+
"""
|
265
|
+
Resolve airport candidates given a query and a list of Places responses.
|
266
|
+
Args:
|
267
|
+
query: The text query to resolve an airport from.
|
268
|
+
places_response: The list of Places responses to resolve an airport from.
|
269
|
+
max_distance_km: The maximum distance in kilometers to any of the places to consider for proximity.
|
270
|
+
max_results: The maximum number of results to return.
|
271
|
+
confidence_threshold: The confidence threshold to consider for the results. Default is 0.5.
|
272
|
+
text_weight: The weight for the text confidence.
|
273
|
+
Returns:
|
274
|
+
The list of resolved airports ordered by confidence.
|
275
|
+
"""
|
276
|
+
|
277
|
+
data = _load_data()
|
278
|
+
rows: list[dict[str, Any]] = data["rows"]
|
279
|
+
n = len(rows)
|
280
|
+
if n == 0:
|
281
|
+
return []
|
282
|
+
|
283
|
+
# ---- Proximity anchors from Places ----
|
284
|
+
anchors = _place_points(places_response)
|
285
|
+
min_dist = np.full(n, np.inf, dtype=float)
|
286
|
+
if anchors:
|
287
|
+
for lat, lon in anchors:
|
288
|
+
lat1 = math.radians(lat)
|
289
|
+
lon1 = math.radians(lon)
|
290
|
+
d = _haversine_km_scalar_to_many(
|
291
|
+
lat1, lon1, data["lat_rad"], data["lon_rad"]
|
292
|
+
)
|
293
|
+
np.nan_to_num(d, copy=False, nan=np.inf) # NaN coords -> ∞
|
294
|
+
np.minimum(min_dist, d, out=min_dist)
|
295
|
+
|
296
|
+
prox = np.zeros(n, dtype=float)
|
297
|
+
if anchors:
|
298
|
+
prox = 100.0 * np.exp(-min_dist / float(DIST_KM_SCALE))
|
299
|
+
|
300
|
+
# ---- Text score: best across augmented queries ----
|
301
|
+
hints, iata_from_places, icao_from_places = _place_hints_and_codes(places_response)
|
302
|
+
q_variants = [_norm(query)] + [_norm(f"{query} {h}") for h in hints]
|
303
|
+
# Single cdist call over up to 1+5 variants keeps things fast
|
304
|
+
scores_matrix = process.cdist(q_variants, data["keys"], scorer=fuzz.token_set_ratio)
|
305
|
+
text_scores = np.array(scores_matrix.max(axis=0), dtype=float)
|
306
|
+
|
307
|
+
# ---- Code bonuses ----
|
308
|
+
# 1) If the *user* typed a code, stronger bonus
|
309
|
+
iata_q, icao_q = _looks_like_code(query)
|
310
|
+
if iata_q or icao_q:
|
311
|
+
if iata_q:
|
312
|
+
text_scores += (
|
313
|
+
np.fromiter(
|
314
|
+
((1.0 if iata_q == iata else 0.0) for iata, _ in data["codes"]),
|
315
|
+
float,
|
316
|
+
count=n,
|
317
|
+
)
|
318
|
+
* CODE_BONUS_QUERY
|
319
|
+
)
|
320
|
+
if icao_q:
|
321
|
+
text_scores += (
|
322
|
+
np.fromiter(
|
323
|
+
((1.0 if icao_q == icao else 0.0) for _, icao in data["codes"]),
|
324
|
+
float,
|
325
|
+
count=n,
|
326
|
+
)
|
327
|
+
* CODE_BONUS_QUERY
|
328
|
+
)
|
329
|
+
|
330
|
+
# 2) If Places hints include codes (e.g., “JFK Terminal 4”), soft bonus
|
331
|
+
if iata_from_places:
|
332
|
+
text_scores += (
|
333
|
+
np.fromiter(
|
334
|
+
(
|
335
|
+
(1.0 if (iata in iata_from_places) else 0.0)
|
336
|
+
for iata, _ in data["codes"]
|
337
|
+
),
|
338
|
+
float,
|
339
|
+
count=n,
|
340
|
+
)
|
341
|
+
* CODE_BONUS_PLACES
|
342
|
+
)
|
343
|
+
if icao_from_places:
|
344
|
+
text_scores += (
|
345
|
+
np.fromiter(
|
346
|
+
(
|
347
|
+
(1.0 if (icao in icao_from_places) else 0.0)
|
348
|
+
for _, icao in data["codes"]
|
349
|
+
),
|
350
|
+
float,
|
351
|
+
count=n,
|
352
|
+
)
|
353
|
+
* CODE_BONUS_PLACES
|
354
|
+
)
|
355
|
+
|
356
|
+
# Cap to 0..100
|
357
|
+
text_scores = np.clip(text_scores, 0.0, 100.0)
|
358
|
+
|
359
|
+
# ---- Blend + optional radius mask ----
|
360
|
+
final = text_weight * text_scores + (1.0 - text_weight) * prox
|
361
|
+
|
362
|
+
# Apply mask only if we have anchors and caller asked for one.
|
363
|
+
# IMPORTANT: rows without coords are still included (mask keeps them).
|
364
|
+
if anchors and max_distance_km is not None:
|
365
|
+
mask = (~data["has_coords"]) | (min_dist <= float(max_distance_km))
|
366
|
+
else:
|
367
|
+
mask = np.ones(n, dtype=bool)
|
368
|
+
|
369
|
+
final_masked = np.where(mask, final, -np.inf)
|
370
|
+
order = np.argsort(-final_masked)
|
371
|
+
top = order[: max_results or DEFAULT_MAX_RESULTS]
|
372
|
+
|
373
|
+
results: list[ResolvedAirport] = []
|
374
|
+
for idx in top:
|
375
|
+
if final_masked[idx] == -np.inf:
|
376
|
+
break
|
377
|
+
r = rows[idx]
|
378
|
+
text_confidence = float(text_scores[idx] / 100.0)
|
379
|
+
proximity_confidence = float(prox[idx] / 100.0)
|
380
|
+
if (
|
381
|
+
confidence_threshold is not None
|
382
|
+
and (text_confidence + proximity_confidence) / 2.0 < confidence_threshold
|
383
|
+
):
|
384
|
+
continue
|
385
|
+
results.append(
|
386
|
+
ResolvedAirport(
|
387
|
+
name=r["name"],
|
388
|
+
city=r["city"],
|
389
|
+
iata_code=r["iata"] or None,
|
390
|
+
icao_code=r["icao"] or None,
|
391
|
+
confidence=(text_confidence + proximity_confidence) / 2.0,
|
392
|
+
)
|
393
|
+
)
|
394
|
+
|
395
|
+
results.sort(key=lambda x: x.confidence, reverse=True)
|
396
|
+
|
397
|
+
return results
|
@@ -0,0 +1,98 @@
|
|
1
|
+
"""
|
2
|
+
Transport abstractions for Google Places clients.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
from typing import Any, Optional, Protocol
|
8
|
+
|
9
|
+
from google.api_core.client_options import ClientOptions
|
10
|
+
from google.maps.places_v1 import PlacesAsyncClient, PlacesClient
|
11
|
+
|
12
|
+
|
13
|
+
class SyncPlacesTransport(Protocol):
|
14
|
+
"""Protocol for synchronous Places API transport."""
|
15
|
+
|
16
|
+
def autocomplete_places(self, *, request: dict[str, Any], **kwargs: Any) -> Any: ...
|
17
|
+
|
18
|
+
def search_text(
|
19
|
+
self, *, request: dict[str, Any], metadata: tuple[tuple[str, str], ...]
|
20
|
+
) -> Any: ...
|
21
|
+
|
22
|
+
def get_place(
|
23
|
+
self, *, request: dict[str, Any], metadata: tuple[tuple[str, str], ...]
|
24
|
+
) -> Any: ...
|
25
|
+
|
26
|
+
def close(self) -> None: ...
|
27
|
+
|
28
|
+
|
29
|
+
class AsyncPlacesTransport(Protocol):
|
30
|
+
"""Protocol for asynchronous Places API transport."""
|
31
|
+
|
32
|
+
async def autocomplete_places(
|
33
|
+
self, *, request: dict[str, Any], **kwargs: Any
|
34
|
+
) -> Any: ...
|
35
|
+
|
36
|
+
async def search_text(
|
37
|
+
self, *, request: dict[str, Any], metadata: tuple[tuple[str, str], ...]
|
38
|
+
) -> Any: ...
|
39
|
+
|
40
|
+
async def get_place(
|
41
|
+
self, *, request: dict[str, Any], metadata: tuple[tuple[str, str], ...]
|
42
|
+
) -> Any: ...
|
43
|
+
|
44
|
+
async def close(self) -> None: ...
|
45
|
+
|
46
|
+
|
47
|
+
class GoogleSyncTransport:
|
48
|
+
"""Synchronous transport implementation for Google Places API."""
|
49
|
+
|
50
|
+
def __init__(self, api_key: str, client: Optional[PlacesClient] = None) -> None:
|
51
|
+
self.client = client or PlacesClient(
|
52
|
+
client_options=ClientOptions(api_key=api_key)
|
53
|
+
)
|
54
|
+
|
55
|
+
def autocomplete_places(self, *, request: dict[str, Any], **kwargs: Any) -> Any:
|
56
|
+
return self.client.autocomplete_places(request=request, **kwargs)
|
57
|
+
|
58
|
+
def search_text(
|
59
|
+
self, *, request: dict[str, Any], metadata: tuple[tuple[str, str], ...]
|
60
|
+
) -> Any:
|
61
|
+
return self.client.search_text(request=request, metadata=metadata)
|
62
|
+
|
63
|
+
def get_place(
|
64
|
+
self, *, request: dict[str, Any], metadata: tuple[tuple[str, str], ...]
|
65
|
+
) -> Any:
|
66
|
+
return self.client.get_place(request=request, metadata=metadata)
|
67
|
+
|
68
|
+
def close(self) -> None:
|
69
|
+
self.client.transport.close()
|
70
|
+
|
71
|
+
|
72
|
+
class GoogleAsyncTransport:
|
73
|
+
"""Asynchronous transport implementation for Google Places API."""
|
74
|
+
|
75
|
+
def __init__(
|
76
|
+
self, api_key: str, client: Optional[PlacesAsyncClient] = None
|
77
|
+
) -> None:
|
78
|
+
self.client = client or PlacesAsyncClient(
|
79
|
+
client_options=ClientOptions(api_key=api_key)
|
80
|
+
)
|
81
|
+
|
82
|
+
async def autocomplete_places(
|
83
|
+
self, *, request: dict[str, Any], **kwargs: Any
|
84
|
+
) -> Any:
|
85
|
+
return await self.client.autocomplete_places(request=request, **kwargs)
|
86
|
+
|
87
|
+
async def search_text(
|
88
|
+
self, *, request: dict[str, Any], metadata: tuple[tuple[str, str], ...]
|
89
|
+
) -> Any:
|
90
|
+
return await self.client.search_text(request=request, metadata=metadata)
|
91
|
+
|
92
|
+
async def get_place(
|
93
|
+
self, *, request: dict[str, Any], metadata: tuple[tuple[str, str], ...]
|
94
|
+
) -> Any:
|
95
|
+
return await self.client.get_place(request=request, metadata=metadata)
|
96
|
+
|
97
|
+
async def close(self) -> None:
|
98
|
+
await self.client.transport.close()
|
bookalimo/schemas/__init__.py
CHANGED
@@ -35,6 +35,7 @@ from .places import (
|
|
35
35
|
AutocompletePlacesRequest,
|
36
36
|
AutocompletePlacesResponse,
|
37
37
|
Circle,
|
38
|
+
EVConnectorType,
|
38
39
|
FormattableText,
|
39
40
|
GeocodingRequest,
|
40
41
|
GetPlaceRequest,
|
@@ -44,6 +45,8 @@ from .places import (
|
|
44
45
|
PlacePrediction,
|
45
46
|
PlaceType,
|
46
47
|
QueryPrediction,
|
48
|
+
RankPreference,
|
49
|
+
SearchTextRequest,
|
47
50
|
StringRange,
|
48
51
|
StructuredFormat,
|
49
52
|
Suggestion,
|
@@ -79,6 +82,8 @@ __all__ = [
|
|
79
82
|
"GetReservationRequest",
|
80
83
|
"EditReservationResponse",
|
81
84
|
"PlaceType",
|
85
|
+
"RankPreference",
|
86
|
+
"EVConnectorType",
|
82
87
|
"StringRange",
|
83
88
|
"FormattableText",
|
84
89
|
"StructuredFormat",
|
@@ -93,5 +98,6 @@ __all__ = [
|
|
93
98
|
"GetPlaceRequest",
|
94
99
|
"AutocompletePlacesRequest",
|
95
100
|
"GeocodingRequest",
|
101
|
+
"SearchTextRequest",
|
96
102
|
"Credentials",
|
97
103
|
]
|
@@ -1,9 +1,13 @@
|
|
1
1
|
"""Google Places API schemas."""
|
2
2
|
|
3
|
+
from .common import AddressDescriptor
|
4
|
+
from .field_mask import FieldMaskInput, FieldPath, compile_field_mask
|
3
5
|
from .google import (
|
4
6
|
AutocompletePlacesRequest,
|
5
7
|
AutocompletePlacesResponse,
|
6
8
|
Circle,
|
9
|
+
EVConnectorType,
|
10
|
+
EVOptions,
|
7
11
|
FormattableText,
|
8
12
|
GeocodingRequest,
|
9
13
|
GetPlaceRequest,
|
@@ -13,20 +17,31 @@ from .google import (
|
|
13
17
|
PlacePrediction,
|
14
18
|
PlaceType,
|
15
19
|
QueryPrediction,
|
20
|
+
RankPreference,
|
21
|
+
ResolvedAirport,
|
22
|
+
RoutingParameters,
|
23
|
+
SearchTextLocationRestriction,
|
24
|
+
SearchTextRequest,
|
25
|
+
SearchTextResponse,
|
16
26
|
StringRange,
|
17
27
|
StructuredFormat,
|
18
28
|
Suggestion,
|
19
29
|
)
|
30
|
+
from .place import AddressComponent, GooglePlace
|
20
31
|
|
21
32
|
__all__ = [
|
22
33
|
"PlaceType",
|
34
|
+
"RankPreference",
|
35
|
+
"EVConnectorType",
|
23
36
|
"StringRange",
|
24
37
|
"FormattableText",
|
25
38
|
"StructuredFormat",
|
26
39
|
"Circle",
|
27
40
|
"LocationBias",
|
28
41
|
"LocationRestriction",
|
42
|
+
"SearchTextLocationRestriction",
|
29
43
|
"Place",
|
44
|
+
"GooglePlace",
|
30
45
|
"AutocompletePlacesResponse",
|
31
46
|
"PlacePrediction",
|
32
47
|
"QueryPrediction",
|
@@ -34,4 +49,14 @@ __all__ = [
|
|
34
49
|
"GetPlaceRequest",
|
35
50
|
"AutocompletePlacesRequest",
|
36
51
|
"GeocodingRequest",
|
52
|
+
"SearchTextRequest",
|
53
|
+
"ResolvedAirport",
|
54
|
+
"AddressDescriptor",
|
55
|
+
"AddressComponent",
|
56
|
+
"FieldMaskInput",
|
57
|
+
"FieldPath",
|
58
|
+
"compile_field_mask",
|
59
|
+
"EVOptions",
|
60
|
+
"RoutingParameters",
|
61
|
+
"SearchTextResponse",
|
37
62
|
]
|