from typing import Any, Self
from fansifter_common.constants import PROD_ENVIRONMENT, QA_ENVIRONMENT
from fansifter_common.utils.functional import lazy_proxy
from pydantic import Field, ValidationError, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
environment: str
debug: bool = Field(validation_alias="APP_DEBUG", default=False)
service_name: str = "lambda-audience-songwhip-fan-consent-handler"
service_version: str = "0.1.0"
aws_region_name: str = "us-east-1"
# Redis
redis_host: str = "localhost"
redis_port: int = 6379
redis_ssl: bool = True
cache_backend: str = Field("redis", validation_alias="CACHE_BACKEND")
cache_redis_db: int = 0
cache_timeout: int = 3600
@property
def cache_key_prefix(self) -> str:
return f"{self.service_name}:"
@property
def m2m_token_secret_name(self) -> str:
return f"{self.environment}/lambda-audience-refresh-m2m-token/m2m_token"
@property
def m2m_token_expiration_secret_name(self) -> str:
return (
f"{self.environment}/lambda-audience-refresh-m2m-token/m2m_token_expiration"
)
email_sender_name: str = "Songwhip"
email_title_templates: dict[str, str] = {
"en": "Confirm your {artist_name} subscription",
"de": "Bitte bestätige deine Anmeldung zum Newsletter von {artist_name}",
"at": "Bitte bestätige deine Anmeldung zum Newsletter von {artist_name}",
"ch": "Bitte bestätige deine Anmeldung zum Newsletter von {artist_name}",
}
email_content_wording: dict[str, dict[str, str]] = {
"en": {
"title": "Confirm your subscription",
"body": "Thanks for signing up to {{ artist_name }} email list. Please click the button below "
"to confirm you want to be notified about the latest content from {{ artist_name }}.",
"button_text": "Yes, I want to subscribe",
"notification_message": "This is one-off notification email. You received it because "
"you signed-up for notifications from {{ artist_name }}",
},
"de": {
"title": "Fast geschafft!
Bitte bestätige deine Anmeldung",
"body": "Vielen Dank für dein Interesse! Damit wir dich ab sofort mit den neuesten Inhalten "
"von {{ artist_name }} versorgen können, bestätige bitte die Anmeldung zum Newsletter.",
"button_text": "Anmeldung bestätigen",
"notification_message": "Du hast dich nicht für den Newsletter angemeldet? Dann können "
"Sie diese E-Mail einfach ignorieren. Wir respektieren Sie und Ihre Daten.",
},
"at": {
"title": "Fast geschafft!
Bitte bestätige deine Anmeldung",
"body": "Vielen Dank für dein Interesse! Damit wir dich ab sofort mit den neuesten Inhalten "
"von {{ artist_name }} versorgen können, bestätige bitte die Anmeldung zum Newsletter.",
"button_text": "Anmeldung bestätigen",
"notification_message": "Du hast dich nicht für den Newsletter angemeldet? Dann können "
"Sie diese E-Mail einfach ignorieren. Wir respektieren Sie und Ihre Daten.",
},
"ch": {
"title": "Fast geschafft!
Bitte bestätige deine Anmeldung",
"body": "Vielen Dank für dein Interesse! Damit wir dich ab sofort mit den neuesten Inhalten "
"von {{ artist_name }} versorgen können, bestätige bitte die Anmeldung zum Newsletter.",
"button_text": "Anmeldung bestätigen",
"notification_message": "Du hast dich nicht für den Newsletter angemeldet? Dann können "
"Sie diese E-Mail einfach ignorieren. Wir respektieren Sie und Ihre Daten.",
},
}
songwhip_aws_account_id: str = "619719722105"
@property
def email_source_arn(self) -> str:
return f"arn:aws:ses:us-east-1:{self.songwhip_aws_account_id}:identity/songwhip.com"
@property
def email_sender(self) -> str:
if self.environment == PROD_ENVIRONMENT:
return "contact@songwhip.com"
return "qa-contact@songwhip.com"
@property
def preference_center_url(self) -> str:
if self.environment == PROD_ENVIRONMENT:
return "https://fan-preferences.sonymusic.com/en/opt-in"
return "https://qa-preferences.theorchard.io/en/opt-in"
# Logging
logging_debug: bool = Field(validation_alias="LOGGING_DEBUG", default=False)
@property
def logging_config(self) -> dict[str, Any]:
return {
"version": 1,
"disable_existing_loggers": True,
"formatters": {
"json": {
"()": "owslogger.logger.DDJsonFormatter",
"service_name": self.service_name,
"service_version": self.service_version,
"env": self.environment,
},
"console": {
"()": "fansifter_common.logging.ConsoleFormatter",
},
},
"filters": {
"require_debug_true": {
"()": "fansifter_common.logging.RequireDebugTrueFilter",
"value": self.logging_debug,
},
"require_debug_false": {
"()": "fansifter_common.logging.RequireDebugFalseFilter",
"value": self.logging_debug,
},
},
"handlers": {
"stream": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "json",
"filters": ["require_debug_false"],
},
"rich": {
"level": "INFO",
"class": "rich.logging.RichHandler",
"filters": ["require_debug_true"],
"formatter": "console",
},
},
"loggers": {
"app": {
"handlers": ["stream", "rich"],
"level": "INFO",
"propagate": False,
},
"httpx": {
"handlers": ["stream", "rich"],
"level": "WARNING",
"propagate": False,
},
},
}
# Sentry
sentry_dsn: str | None = None
@model_validator(mode="after")
def _setup_sentry_dsn(self) -> Self:
if not self.sentry_dsn and self.environment in [
QA_ENVIRONMENT,
PROD_ENVIRONMENT,
]:
raise ValueError(f"Sentry is not configured in {self.environment}.")
return self
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="allow",
)
def initialize_settings(**defaults: Any) -> Settings:
try:
return Settings(**defaults)
except ValidationError as exc:
errors = "\n".join(
[
f" * {'.'.join(map(str, error['loc']))} - {error['msg']}"
for error in exc.errors()
]
)
raise RuntimeError(f"Failed to initialize settings:\n {errors}") from exc
# Lazy proxy for settings
settings = lazy_proxy(initialize_settings)