import logging
import os
from datetime import datetime, timedelta
from typing import Optional
from tamr_toolbox.enrichment.address_mapping import AddressValidationMapping
from tamr_toolbox.models.data_type import JsonDict
# Building our documentation requires access to all dependencies, including optional ones
# This environments variable is set automatically when `invoke docs` is used
BUILDING_DOCS = os.environ.get("TAMR_TOOLBOX_DOCS") == "1"
if BUILDING_DOCS:
# Import relevant optional dependencies
import googlemaps
LOGGER = logging.getLogger(__name__)
_ADDRESSVALIDATION_BASE_URL = "https://addressvalidation.googleapis.com"
[docs]def get_maps_client(googlemaps_api_key: str) -> "googlemaps.Client":
"""Get GoogleMaps client.
Args:
googlemaps_api_key: API key for the Google Maps address validation API
Returns:
API client linekd to specified key
"""
import googlemaps
return googlemaps.Client(key=googlemaps_api_key)
[docs]def validate(
*,
address_to_validate: str,
client: "googlemaps.Client",
locality: Optional[str] = None,
region_code: Optional[str] = None,
enable_usps_cass: bool = False,
) -> Optional[AddressValidationMapping]:
"""Validate an address using google's address validation API.
Args:
address_to_validate: address to validate
client: client for Google Maps API
region_code: region to use for validation, optional
locality: locality to use for validation, optional
enable_usps_cass: whether to use USPS validation
Returns:
toolbox address validation mapping, or None if API call fails
Raises:
RuntimeError: if API key is invalid
"""
params = {"address": {"addressLines": [address_to_validate]}}
if region_code:
params["address"]["regionCode"] = region_code
if locality:
params["address"]["locality"] = locality
if enable_usps_cass:
params["enableUspsCass"] = True
json_resp: JsonDict = client._request(
"/v1:validateAddress",
{}, # No GET params
base_url=_ADDRESSVALIDATION_BASE_URL,
extract_body=lambda x: x.json(),
post_json=params,
)
if "api key not valid" in json_resp.get("error", dict()).get("message", "").lower():
message = "Invalid API key supplied to Google Maps address validation"
LOGGER.error(message)
raise RuntimeError(message)
if not json_resp.get("result"):
message = f"Got no result for {address_to_validate}: API returned {json_resp}."
LOGGER.error(message)
raise RuntimeError(message)
# Parse the response to extract the desired fields
json_resp = json_resp["result"]
# Get USPS address components
usps_address: JsonDict = json_resp.get("uspsData", dict()).get("standardizedAddress", dict())
usps_zipcode = usps_address.get("zipCode")
if usps_zipcode and usps_address.get("zipCodeExtension"):
usps_zipcode += "-" + usps_address.get("zipCodeExtension")
# Get postal address components
postal_address = json_resp.get("address", dict()).get("postalAddress", dict())
return AddressValidationMapping(
input_address=address_to_validate,
validated_formatted_address=json_resp["address"]["formattedAddress"],
expiration=str(datetime.now() + timedelta(days=30)),
region_code=postal_address.get("regionCode"),
postal_code=postal_address.get("postalCode"),
admin_area=postal_address.get("administrativeArea"),
locality=postal_address.get("locality"),
address_lines=postal_address.get("addressLines"),
usps_first_address_line=usps_address.get("firstAddressLine"),
usps_city_state_zip_line=usps_address.get("cityStateZipAddressLine"),
usps_city=usps_address.get("city"),
usps_state=usps_address.get("state"),
usps_zip_code=usps_zipcode,
latitude=json_resp.get("geocode", dict()).get("location", dict()).get("latitude"),
longitude=json_resp.get("geocode", dict()).get("location", dict()).get("longitude"),
place_id=json_resp.get("geocode", dict()).get("placeId"),
input_granularity=json_resp.get("verdict", dict()).get(
"inputGranularity", "GRANULARITY_UNSPECIFIED"
),
validation_granularity=json_resp.get("verdict", dict()).get(
"validationGranularity", "GRANULARITY_UNSPECIFIED"
),
geocode_granularity=json_resp.get("verdict", dict()).get(
"geocodeGranularity", "GRANULARITY_UNSPECIFIED"
),
has_inferred=json_resp.get("verdict", dict()).get("hasInferredComponents", False),
has_unconfirmed=json_resp.get("verdict", dict()).get("hasUnconfirmedComponents", False),
has_replaced=json_resp.get("verdict", dict()).get("hasReplacedComponents", False),
address_complete=json_resp.get("verdict", dict()).get("addressComplete", False),
)