Files
2026-09-15 15:24:03 -04:00

58 lines
2.7 KiB
Python

"""Runtime configuration, all from the environment."""
from __future__ import annotations
import os
from dataclasses import dataclass, field
def _int(name: str, default: int) -> int:
try:
return int(os.getenv(name, "") or default)
except ValueError:
return default
def _opt(name: str) -> str | None:
# Compose passes unset optionals through as empty strings rather than
# omitting them, so "" has to mean "absent" here.
return (os.getenv(name) or "").strip() or None
@dataclass(frozen=True)
class Settings:
auth_token: str = field(default_factory=lambda: os.environ["OWUITOOLS_AUTH_TOKEN"])
egress_proxy: str = field(default_factory=lambda: os.getenv("OWUITOOLS_EGRESS_PROXY", "http://owuitools-egress:3128"))
searxng_url: str = field(default_factory=lambda: os.getenv("OWUITOOLS_SEARXNG_URL", "http://owuitools-searxng:8080").rstrip("/"))
valkey_url: str = field(default_factory=lambda: os.getenv("OWUITOOLS_VALKEY_URL", "redis://owuitools-valkey:6379/0"))
contact: str = field(default_factory=lambda: os.getenv("OWUITOOLS_CONTACT", "owuitools@localhost"))
# Optional. Goes in the User-Agent so a site that wants to know who is
# fetching can look you up. Leave it unset if this is not reachable
# from outside, which is the normal case.
public_url: str | None = field(default_factory=lambda: _opt("OWUITOOLS_PUBLIC_URL"))
max_response_chars: int = field(default_factory=lambda: _int("OWUITOOLS_MAX_RESPONSE_CHARS", 12000))
probe_rate_per_hour: int = field(default_factory=lambda: _int("OWUITOOLS_PROBE_RATE_PER_HOUR", 30))
log_level: str = field(default_factory=lambda: os.getenv("OWUITOOLS_LOG_LEVEL", "INFO").upper())
github_token: str | None = field(default_factory=lambda: _opt("OWUITOOLS_GITHUB_TOKEN"))
stackexchange_key: str | None = field(default_factory=lambda: _opt("OWUITOOLS_STACKEXCHANGE_KEY"))
nvd_key: str | None = field(default_factory=lambda: _opt("OWUITOOLS_NVD_KEY"))
@property
def user_agent(self) -> str:
# Wikimedia hard-blocks requests with no User-Agent (verified: 403)
# and its policy explicitly disallows generic ones like "curl/8.5.0",
# which pass today and may not tomorrow. OpenAlex and Crossref read
# the same string to decide whether you get the polite pool.
#
# The URL is included only if there is one to give. It used to be
# one deployment's address, hardcoded - which every other
# deployment would then have been announcing as its own to every
# site it fetched from.
site = f"+{self.public_url}; " if self.public_url else ""
return f"owuitools/1.0 ({site}{self.contact})"
settings = Settings()