Source code for operators.google_place.place_detail

import time
import typing as T
import requests
from airflow.models import BaseOperator
from airflow.utils.context import Context

PLACES_BASE = "https://places.googleapis.com/v1"


[docs] class PlaceDetailsBatchOperator(BaseOperator): """ Given a list of place ids, fetch selected details for each: name, lat, lon, address, url, phone. """ template_fields = ("place_ids", "batch_size") def __init__( self, *, api_key: str, place_ids: ( list[str] | None ) = None, # If None, will pull from upstream XCom (task_id must be set in op_kwargs at DAG use) batch_size: int = 20, per_call_sleep: float = 0.0, timeout: int = 30, session: requests.Session | None = None, **kwargs, ): super().__init__(**kwargs) self.api_key = api_key self.place_ids = place_ids self.batch_size = batch_size self.per_call_sleep = per_call_sleep self.timeout = timeout self._session = session or requests.Session() def _fetch_one(self, pid: str) -> dict: headers = { "X-Goog-Api-Key": self.api_key, # Request exactly what we need "X-Goog-FieldMask": ( "id,displayName,formattedAddress,location,googleMapsUri,internationalPhoneNumber" ), } url = f"{PLACES_BASE}/places/{pid}" r = self._session.get(url, headers=headers, timeout=self.timeout) r.raise_for_status() js = r.json() name = (js.get("displayName") or {}).get("text") loc = js.get("location") or {} return { "place_id": js.get("id"), "name": name, "lat": loc.get("latitude"), "lon": loc.get("longitude"), "address": js.get("formattedAddress"), "url": js.get("googleMapsUri"), "phone": js.get("internationalPhoneNumber"), }
[docs] def execute(self, context: Context) -> list[dict]: # Allow pulling IDs from upstream via XCom if not provided at init place_ids = self.place_ids if place_ids is None: # Expect upstream task_id in op_kwargs at DAG usage (see example) raise ValueError( "place_ids is None. Pass a list at init or pull from XCom in DAG code." ) results: list[dict] = [] for i, pid in enumerate(place_ids, start=1): try: results.append(self._fetch_one(pid)) except requests.HTTPError as e: self.log.error("Failed to fetch details for %s: %s", pid, e) if self.per_call_sleep: time.sleep(self.per_call_sleep) if (i % self.batch_size) == 0: self.log.info("Fetched %d/%d places...", i, len(place_ids)) self.log.info( "Fetched details for %d places (requested %d).", len(results), len(place_ids), ) return results