"""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"))