fix: migrate Places and Routes to new GCP APIs
Switch from legacy Places textsearch and Directions APIs (disabled on this project) to Places API (New) and Routes API (New). Both places.py and the assistant's _places_search helper updated. Also fixes uid() recursive self-call in trips page and adds Places API response logging.
This commit is contained in:
@@ -5,8 +5,9 @@ from app.internal.logger import logger
|
||||
|
||||
router = APIRouter(prefix="/places", tags=["places"])
|
||||
|
||||
PLACES_SEARCH_URL = "https://maps.googleapis.com/maps/api/place/textsearch/json"
|
||||
DIRECTIONS_URL = "https://maps.googleapis.com/maps/api/directions/json"
|
||||
_PLACES_SEARCH_URL = "https://places.googleapis.com/v1/places:searchText"
|
||||
_ROUTES_URL = "https://routes.googleapis.com/directions/v2:computeRoutes"
|
||||
_PLACES_FIELDS = "places.id,places.displayName,places.formattedAddress,places.rating,places.googleMapsUri,places.location"
|
||||
|
||||
|
||||
@router.get("/search")
|
||||
@@ -17,9 +18,13 @@ async def search_places(query: str = Query(...), near: str = Query("")):
|
||||
full_query = f"{query} {near}".strip()
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
r = await client.get(
|
||||
PLACES_SEARCH_URL,
|
||||
params={"query": full_query, "key": settings.google_maps_api_key},
|
||||
r = await client.post(
|
||||
_PLACES_SEARCH_URL,
|
||||
json={"textQuery": full_query},
|
||||
headers={
|
||||
"X-Goog-Api-Key": settings.google_maps_api_key,
|
||||
"X-Goog-FieldMask": _PLACES_FIELDS,
|
||||
},
|
||||
)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
@@ -29,15 +34,15 @@ async def search_places(query: str = Query(...), near: str = Query("")):
|
||||
|
||||
return [
|
||||
{
|
||||
"name": p.get("name"),
|
||||
"address": p.get("formatted_address"),
|
||||
"place_id": p.get("place_id"),
|
||||
"lat": p.get("geometry", {}).get("location", {}).get("lat"),
|
||||
"lng": p.get("geometry", {}).get("location", {}).get("lng"),
|
||||
"maps_link": f"https://www.google.com/maps/place/?q=place_id:{p.get('place_id')}",
|
||||
"name": p.get("displayName", {}).get("text"),
|
||||
"address": p.get("formattedAddress"),
|
||||
"place_id": p.get("id"),
|
||||
"lat": p.get("location", {}).get("latitude"),
|
||||
"lng": p.get("location", {}).get("longitude"),
|
||||
"maps_link": p.get("googleMapsUri"),
|
||||
"rating": p.get("rating"),
|
||||
}
|
||||
for p in data.get("results", [])[:6]
|
||||
for p in data.get("places", [])[:6]
|
||||
]
|
||||
|
||||
|
||||
@@ -51,13 +56,16 @@ async def get_directions(
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
r = await client.get(
|
||||
DIRECTIONS_URL,
|
||||
params={
|
||||
"origin": origin,
|
||||
"destination": destination,
|
||||
"mode": "driving",
|
||||
"key": settings.google_maps_api_key,
|
||||
r = await client.post(
|
||||
_ROUTES_URL,
|
||||
json={
|
||||
"origin": {"address": origin},
|
||||
"destination": {"address": destination},
|
||||
"travelMode": "DRIVE",
|
||||
},
|
||||
headers={
|
||||
"X-Goog-Api-Key": settings.google_maps_api_key,
|
||||
"X-Goog-FieldMask": "routes.duration,routes.distanceMeters",
|
||||
},
|
||||
)
|
||||
r.raise_for_status()
|
||||
@@ -70,9 +78,23 @@ async def get_directions(
|
||||
if not routes:
|
||||
return {"duration_text": None, "duration_seconds": None, "distance_text": None}
|
||||
|
||||
leg = routes[0]["legs"][0]
|
||||
route = routes[0]
|
||||
duration_seconds = int(route.get("duration", "0s").rstrip("s") or 0)
|
||||
distance_m = route.get("distanceMeters", 0)
|
||||
|
||||
# Format human-readable strings
|
||||
hours, rem = divmod(duration_seconds, 3600)
|
||||
mins = rem // 60
|
||||
if hours:
|
||||
duration_text = f"{hours} hr {mins} min" if mins else f"{hours} hr"
|
||||
else:
|
||||
duration_text = f"{mins} min"
|
||||
|
||||
miles = distance_m / 1609.34
|
||||
distance_text = f"{miles:.1f} mi"
|
||||
|
||||
return {
|
||||
"duration_text": leg["duration"]["text"],
|
||||
"duration_seconds": leg["duration"]["value"],
|
||||
"distance_text": leg["distance"]["text"],
|
||||
"duration_text": duration_text,
|
||||
"duration_seconds": duration_seconds,
|
||||
"distance_text": distance_text,
|
||||
}
|
||||
|
||||
@@ -74,30 +74,38 @@ _TOOLS = [
|
||||
]
|
||||
|
||||
|
||||
_PLACES_SEARCH_URL = "https://places.googleapis.com/v1/places:searchText"
|
||||
_PLACES_FIELDS = "places.id,places.displayName,places.formattedAddress,places.rating,places.googleMapsUri"
|
||||
|
||||
|
||||
async def _places_search(query: str, near: str) -> list[dict]:
|
||||
if not settings.google_maps_api_key:
|
||||
return []
|
||||
full_query = f"{query} {near}".strip()
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=8) as client:
|
||||
r = await client.get(
|
||||
"https://maps.googleapis.com/maps/api/place/textsearch/json",
|
||||
params={"query": f"{query} {near}".strip(), "key": settings.google_maps_api_key},
|
||||
r = await client.post(
|
||||
_PLACES_SEARCH_URL,
|
||||
json={"textQuery": full_query},
|
||||
headers={
|
||||
"X-Goog-Api-Key": settings.google_maps_api_key,
|
||||
"X-Goog-FieldMask": _PLACES_FIELDS,
|
||||
},
|
||||
)
|
||||
data = r.json()
|
||||
status = data.get("status")
|
||||
results = data.get("results", [])
|
||||
logger.info(f"Places search '{query} {near}': status={status}, count={len(results)}")
|
||||
if status not in ("OK", "ZERO_RESULTS"):
|
||||
logger.warning(f"Places API error: {status} — {data.get('error_message', '')}")
|
||||
places = data.get("places", [])
|
||||
logger.info(f"Places search '{full_query}': count={len(places)}")
|
||||
if not places and "error" in data:
|
||||
logger.warning(f"Places API error: {data['error'].get('message', '')}")
|
||||
return [
|
||||
{
|
||||
"name": p.get("name"),
|
||||
"address": p.get("formatted_address"),
|
||||
"place_id": p.get("place_id"),
|
||||
"maps_link": f"https://www.google.com/maps/place/?q=place_id:{p.get('place_id')}",
|
||||
"name": p.get("displayName", {}).get("text"),
|
||||
"address": p.get("formattedAddress"),
|
||||
"place_id": p.get("id"),
|
||||
"maps_link": p.get("googleMapsUri"),
|
||||
"rating": p.get("rating"),
|
||||
}
|
||||
for p in results[:5]
|
||||
for p in places[:5]
|
||||
]
|
||||
except Exception as e:
|
||||
logger.error(f"Places search in assistant failed: {e}")
|
||||
|
||||
Reference in New Issue
Block a user