92 lines
3.1 KiB
Python
92 lines
3.1 KiB
Python
"""Google Ads - reporting via GAQL.
|
|
|
|
GoogleAdsService search / searchStream accept a GAQL query and stream rows back;
|
|
this covers virtually all Google Ads reporting. Request/response bodies are
|
|
forwarded as-is.
|
|
|
|
Auth differs from the other Google services: besides the OAuth Bearer token it
|
|
needs a **developer token** (``X-GAds-Developer-Token`` -> ``developer-token``)
|
|
and, for manager (MCC) access, an optional ``X-GAds-Login-Customer-Id``
|
|
(-> ``login-customer-id``). The API version is configurable via
|
|
``GOOGLE_ADS_API_VERSION`` because Google deprecates versions yearly.
|
|
|
|
Credentials: see ``app.credentials.get_google_ads_credentials``.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Body, Depends, Path
|
|
|
|
from .. import config
|
|
from ..clients.google import GoogleApiClient
|
|
from ..credentials import GoogleAdsCredentials, get_google_ads_credentials
|
|
|
|
router = APIRouter(prefix="/googleads", tags=["google-ads"])
|
|
|
|
_SEARCH_EXAMPLE = {
|
|
"query": (
|
|
"SELECT campaign.id, campaign.name, metrics.impressions, metrics.clicks, "
|
|
"metrics.cost_micros FROM campaign WHERE segments.date DURING LAST_7_DAYS"
|
|
)
|
|
}
|
|
|
|
|
|
def _client(creds: GoogleAdsCredentials) -> GoogleApiClient:
|
|
headers = {"developer-token": creds.developer_token}
|
|
if creds.login_customer_id:
|
|
headers["login-customer-id"] = creds.login_customer_id
|
|
return GoogleApiClient(
|
|
creds.google, extra_headers=headers, service_name="Google Ads"
|
|
)
|
|
|
|
|
|
def _customer(customer_id: str) -> str:
|
|
# Customer ids are digits only (callers may include dashes for readability).
|
|
return customer_id.strip().replace("-", "")
|
|
|
|
|
|
def _base(customer_id: str) -> str:
|
|
return (
|
|
f"{config.GOOGLE_ADS_BASE_URL}/{config.GOOGLE_ADS_API_VERSION}"
|
|
f"/customers/{_customer(customer_id)}/googleAds"
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/customers/{customer_id}/search",
|
|
summary="Run a GAQL query (paginated)",
|
|
)
|
|
async def search(
|
|
customer_id: str = Path(..., description="Google Ads customer id (digits)"),
|
|
body: dict[str, Any] = Body(..., examples=[_SEARCH_EXAMPLE]),
|
|
creds: GoogleAdsCredentials = Depends(get_google_ads_credentials),
|
|
) -> Any:
|
|
return await _client(creds).post(f"{_base(customer_id)}:search", body)
|
|
|
|
|
|
@router.post(
|
|
"/customers/{customer_id}/searchStream",
|
|
summary="Run a GAQL query (streamed, whole result set in one response)",
|
|
)
|
|
async def search_stream(
|
|
customer_id: str = Path(..., description="Google Ads customer id (digits)"),
|
|
body: dict[str, Any] = Body(..., examples=[_SEARCH_EXAMPLE]),
|
|
creds: GoogleAdsCredentials = Depends(get_google_ads_credentials),
|
|
) -> Any:
|
|
return await _client(creds).post(f"{_base(customer_id)}:searchStream", body)
|
|
|
|
|
|
@router.get(
|
|
"/customers:listAccessibleCustomers",
|
|
summary="List customer ids the credentials can access",
|
|
)
|
|
async def list_accessible_customers(
|
|
creds: GoogleAdsCredentials = Depends(get_google_ads_credentials),
|
|
) -> Any:
|
|
url = (
|
|
f"{config.GOOGLE_ADS_BASE_URL}/{config.GOOGLE_ADS_API_VERSION}"
|
|
"/customers:listAccessibleCustomers"
|
|
)
|
|
return await _client(creds).get(url)
|