Files
analytics/app/config.py
T
2026-07-20 08:29:23 +02:00

107 lines
4.9 KiB
Python

"""Runtime configuration read from environment variables.
AppFactory injects variables/secrets as environment variables (see AGENTS.md).
This module holds only NON-secret infrastructure configuration. Per-request
credentials are never stored here - they arrive in X- headers (see
``app.credentials``).
"""
import os
# Public app metadata
APP_NAME = os.getenv("APP_NAME", "analytics")
APP_VERSION = os.getenv("APP_VERSION", "1.0.0")
# Reverse-proxy prefix injected by AppFactory (e.g. "/apps/analytics").
# Empty when running locally at the domain root.
ROOT_PATH = os.getenv("ROOT_PATH", "")
# --- Google Analytics ---------------------------------------------------------
# Base URLs are configurable so we can point at a staging/mock endpoint, but
# default to the production Google endpoints.
GA_DATA_BASE_URL = os.getenv(
"GA_DATA_BASE_URL", "https://analyticsdata.googleapis.com/v1beta"
)
GA_ADMIN_BASE_URL = os.getenv(
"GA_ADMIN_BASE_URL", "https://analyticsadmin.googleapis.com/v1beta"
)
# OAuth scope requested when minting an access token from a service account.
# analytics.readonly is sufficient for reporting (Data API) and for listing
# accounts/properties/data streams (Admin API read operations).
GA_SCOPE = os.getenv(
"GA_SCOPE", "https://www.googleapis.com/auth/analytics.readonly"
)
# --- Google Search Console ----------------------------------------------------
# Search Analytics, Sites and Sitemaps live under the Webmasters v3 API; the
# newer URL Inspection lives under searchconsole.googleapis.com/v1.
GSC_DATA_BASE_URL = os.getenv(
"GSC_DATA_BASE_URL", "https://www.googleapis.com/webmasters/v3"
)
GSC_INSPECT_BASE_URL = os.getenv(
"GSC_INSPECT_BASE_URL", "https://searchconsole.googleapis.com/v1"
)
GSC_SCOPE = os.getenv(
"GSC_SCOPE", "https://www.googleapis.com/auth/webmasters.readonly"
)
# --- Google Ads ---------------------------------------------------------------
# Google Ads API versions are deprecated roughly yearly - keep the version in an
# env var so it can be bumped without a code change.
GOOGLE_ADS_BASE_URL = os.getenv(
"GOOGLE_ADS_BASE_URL", "https://googleads.googleapis.com"
)
GOOGLE_ADS_API_VERSION = os.getenv("GOOGLE_ADS_API_VERSION", "v19")
GOOGLE_ADS_SCOPE = os.getenv(
"GOOGLE_ADS_SCOPE", "https://www.googleapis.com/auth/adwords"
)
# --- Sklik (Seznam) -----------------------------------------------------------
# Sklik "Drak" JSON API. The method name is appended to this base URL and the
# HTTP body is a JSON array of positional arguments.
SKLIK_BASE_URL = os.getenv("SKLIK_BASE_URL", "https://api.sklik.cz/drak/json/v5")
# Page size / safety cap for the typed list endpoints (campaigns, groups, ads).
SKLIK_LIST_PAGE_LIMIT = int(os.getenv("SKLIK_LIST_PAGE_LIMIT", "100"))
SKLIK_LIST_MAX_PAGES = int(os.getenv("SKLIK_LIST_MAX_PAGES", "200"))
# --- Sklik write guard rails --------------------------------------------------
# Optional backstops for the write endpoints. All amounts are in HALERS
# (100 = 1 Kc), the unit the Sklik API itself uses.
#
# All THREE ceilings default to 0 = DISABLED, i.e. the proxy does not second-
# guess the caller's budgets out of the box. Set a non-zero value to have the
# proxy reject anything above it - useful as a safety net against a misplaced
# decimal point (Sklik works in halers, so "50000" meant as korunas is 500 Kc).
# Enforcement is opt-in on purpose: the approval flow lives on the caller's side.
SKLIK_MAX_DAY_BUDGET_HALERS = int(os.getenv("SKLIK_MAX_DAY_BUDGET_HALERS", "0"))
SKLIK_MAX_TOTAL_BUDGET_HALERS = int(os.getenv("SKLIK_MAX_TOTAL_BUDGET_HALERS", "0"))
# Ceiling for a group's default max CPC / CPT.
SKLIK_MAX_CPC_HALERS = int(os.getenv("SKLIK_MAX_CPC_HALERS", "0"))
# How long a completed write is remembered for idempotent replay (seconds).
# Idempotency itself is opt-in per request via the X-Idempotency-Key header.
SKLIK_IDEMPOTENCY_TTL_SECONDS = float(
os.getenv("SKLIK_IDEMPOTENCY_TTL_SECONDS", "86400")
)
# Creation always forces status=suspend (see app.sklik_guards). UPDATE, however,
# is full CRUD and can set status=active by default - that is how you resume a
# paused campaign. Set this to "true" to refuse activation through the API as
# well, so starting a campaign stays a manual action in the Sklik UI.
SKLIK_BLOCK_ACTIVATION = os.getenv(
"SKLIK_BLOCK_ACTIVATION", "false"
).strip().lower() in ("1", "true", "yes")
# The generic /sklik/rpc passthrough reaches any method, including mutating ones
# (campaigns.create etc.), which bypass the typed endpoints' guard rails. That
# is the long-standing behaviour and stays ALLOWED by default. Set this to
# "false" to restrict the passthrough to read methods and funnel every write
# through the typed endpoints.
SKLIK_RPC_ALLOW_MUTATIONS = os.getenv(
"SKLIK_RPC_ALLOW_MUTATIONS", "true"
).strip().lower() in ("1", "true", "yes")
# --- HTTP ---------------------------------------------------------------------
# Upstream request timeout in seconds.
HTTP_TIMEOUT_SECONDS = float(os.getenv("HTTP_TIMEOUT_SECONDS", "60"))