Files
analytics/app/routers/ga_data.py
T
2026-06-18 12:43:42 +02:00

127 lines
4.1 KiB
Python

"""Google Analytics 4 - Data API (reporting).
Thin passthrough: request bodies are the GA4 Data API request objects and
responses are returned as-is. See
https://developers.google.com/analytics/devguides/reporting/data/v1/rest
Credentials: X-GA-Access-Token (preferred) or X-GA-Credentials. See
``app.credentials.get_ga_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 GoogleCredentials, get_ga_credentials
router = APIRouter(prefix="/ga/data", tags=["google-analytics: data"])
# Reused OpenAPI example for report request bodies.
_RUN_REPORT_EXAMPLE = {
"dateRanges": [{"startDate": "7daysAgo", "endDate": "today"}],
"dimensions": [{"name": "country"}],
"metrics": [{"name": "activeUsers"}],
}
def _property(property_id: str) -> str:
# Accept both "123456789" and "properties/123456789".
pid = property_id.strip()
return pid if pid.startswith("properties/") else f"properties/{pid}"
def _client(creds: GoogleCredentials) -> GoogleApiClient:
return GoogleApiClient(creds, service_name="Google Analytics")
def _url(property_id: str, suffix: str) -> str:
return f"{config.GA_DATA_BASE_URL}/{_property(property_id)}{suffix}"
@router.post(
"/properties/{property_id}/runReport",
summary="Run a GA4 report",
)
async def run_report(
property_id: str = Path(..., description="GA4 property id, e.g. 123456789"),
body: dict[str, Any] = Body(..., examples=[_RUN_REPORT_EXAMPLE]),
creds: GoogleCredentials = Depends(get_ga_credentials),
) -> Any:
return await _client(creds).post(_url(property_id, ":runReport"), body)
@router.post(
"/properties/{property_id}/runPivotReport",
summary="Run a GA4 pivot report",
)
async def run_pivot_report(
property_id: str = Path(..., description="GA4 property id"),
body: dict[str, Any] = Body(...),
creds: GoogleCredentials = Depends(get_ga_credentials),
) -> Any:
return await _client(creds).post(_url(property_id, ":runPivotReport"), body)
@router.post(
"/properties/{property_id}/batchRunReports",
summary="Run up to 5 GA4 reports in one call",
)
async def batch_run_reports(
property_id: str = Path(..., description="GA4 property id"),
body: dict[str, Any] = Body(...),
creds: GoogleCredentials = Depends(get_ga_credentials),
) -> Any:
return await _client(creds).post(_url(property_id, ":batchRunReports"), body)
@router.post(
"/properties/{property_id}/batchRunPivotReports",
summary="Run up to 5 GA4 pivot reports in one call",
)
async def batch_run_pivot_reports(
property_id: str = Path(..., description="GA4 property id"),
body: dict[str, Any] = Body(...),
creds: GoogleCredentials = Depends(get_ga_credentials),
) -> Any:
return await _client(creds).post(
_url(property_id, ":batchRunPivotReports"), body
)
@router.post(
"/properties/{property_id}/runRealtimeReport",
summary="Run a GA4 realtime report",
)
async def run_realtime_report(
property_id: str = Path(..., description="GA4 property id"),
body: dict[str, Any] = Body(...),
creds: GoogleCredentials = Depends(get_ga_credentials),
) -> Any:
return await _client(creds).post(_url(property_id, ":runRealtimeReport"), body)
@router.post(
"/properties/{property_id}/checkCompatibility",
summary="Check dimension/metric compatibility for a GA4 report",
)
async def check_compatibility(
property_id: str = Path(..., description="GA4 property id"),
body: dict[str, Any] = Body(...),
creds: GoogleCredentials = Depends(get_ga_credentials),
) -> Any:
return await _client(creds).post(_url(property_id, ":checkCompatibility"), body)
@router.get(
"/properties/{property_id}/metadata",
summary="List available GA4 dimensions and metrics for a property",
)
async def get_metadata(
property_id: str = Path(..., description="GA4 property id"),
creds: GoogleCredentials = Depends(get_ga_credentials),
) -> Any:
return await _client(creds).get(_url(property_id, "/metadata"))