Files
iklim-sandbox/common/config.py
T
2026-09-09 16:21:13 +03:00

111 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Environment-backed configuration shared by every service client."""
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import urlsplit
from dotenv import load_dotenv
from .errors import ConfigurationError
DEFAULT_ENV_FILE = Path(__file__).resolve().parent.parent / ".env"
def _required(name: str) -> str:
value = os.getenv(name, "").strip()
if not value:
raise ConfigurationError(f".env içinde {name} eksik veya boş.")
if value.lower() == "change-me":
raise ConfigurationError(f".env içindeki {name} placeholder değeri değiştirilmemiş.")
return value
def _positive_float(name: str, default: str) -> float:
raw_value = os.getenv(name, default).strip()
try:
value = float(raw_value)
except ValueError as exc:
raise ConfigurationError(f"{name} sayısal bir değer olmalıdır.") from exc
if value <= 0:
raise ConfigurationError(f"{name} sıfırdan büyük olmalıdır.")
return value
def _non_negative_int(name: str, default: str) -> int:
raw_value = os.getenv(name, default).strip()
try:
value = int(raw_value)
except ValueError as exc:
raise ConfigurationError(f"{name} tam sayı olmalıdır.") from exc
if value < 0:
raise ConfigurationError(f"{name} negatif olamaz.")
return value
def env_bool(name: str, default: bool = False) -> bool:
raw_value = os.getenv(name)
if raw_value is None or not raw_value.strip():
return default
normalized = raw_value.strip().lower()
if normalized in {"1", "true", "yes", "on"}:
return True
if normalized in {"0", "false", "no", "off"}:
return False
raise ConfigurationError(f"{name} true veya false olmalıdır.")
@dataclass(frozen=True)
class Settings:
env_file: Path
base_url: str
hmac_secret: str
accept_language: str
timeout_seconds: float
token_expiry_skew_seconds: int
max_retries: int
retry_base_seconds: float
@classmethod
def from_env(cls, env_file: Path = DEFAULT_ENV_FILE) -> "Settings":
env_file = Path(env_file).resolve()
if not env_file.is_file():
raise ConfigurationError(f".env dosyası bulunamadı: {env_file}")
load_dotenv(env_file, override=True)
environment = _required("IKLIM_API_ENV").lower()
url_variables = {
"test": "IKLIM_API_TEST_URL",
"prod": "IKLIM_API_PROD_URL",
}
if environment not in url_variables:
raise ConfigurationError("IKLIM_API_ENV yalnızca test veya prod olabilir.")
base_url = _required(url_variables[environment]).rstrip("/")
parsed_url = urlsplit(base_url)
if parsed_url.scheme != "https" or not parsed_url.netloc:
raise ConfigurationError("Seçilen API adresi geçerli bir HTTPS adresi olmalıdır.")
language = os.getenv("IKLIM_ACCEPT_LANGUAGE", "en").strip().lower() or "en"
if language not in {"tr", "en"}:
raise ConfigurationError("IKLIM_ACCEPT_LANGUAGE yalnızca tr veya en olabilir.")
return cls(
env_file=env_file,
base_url=base_url,
hmac_secret=_required("IKLIM_HMAC_SECRET"),
accept_language=language,
timeout_seconds=_positive_float("IKLIM_TIMEOUT_SECONDS", "30"),
token_expiry_skew_seconds=_non_negative_int(
"IKLIM_TOKEN_EXPIRY_SKEW_SECONDS", "60"
),
max_retries=_non_negative_int("IKLIM_MAX_RETRIES", "3"),
retry_base_seconds=_positive_float("IKLIM_RETRY_BASE_SECONDS", "2"),
)
def credentials(self) -> tuple[str, str]:
return _required("IKLIM_USERNAME"), _required("IKLIM_PASSWORD")