bookmyservice-ams / app /core /config.py
MukeshKapoor25's picture
refactor(config): improve jwt config flexibility with fallback values
274a33b
import os
from typing import Optional, Dict
from urllib.parse import quote_plus
from dotenv import load_dotenv
# Load environment variables early so URI builder sees them
load_dotenv()
def build_database_uri(env: Dict[str, str] = os.environ) -> str:
"""
Assemble a SQLAlchemy/asyncpg URI from separate env vars.
Supports:
- URL-encoding for user/password
- Default schema via ?options=-csearch_path=<schema>
- sslmode=require (e.g., Neon)
Honors DATABASE_URI if set (escape hatch/override).
"""
# If already provided as a single secret, honor it and return immediately.
env = env or os.environ
direct_uri = env.get("DATABASE_URI") or env.get("DATABASE_URL")
if direct_uri:
return direct_uri
protocol = env.get("DB_PROTOCOL") or "postgresql+asyncpg"
user = env.get("DB_USER")
password = env.get("DB_PASSWORD")
host = env.get("DB_HOST") or "localhost"
port = env.get("DB_PORT") or "5432"
dbname = env.get("DB_NAME")
missing = [k for k, v in {"DB_USER": user, "DB_PASSWORD": password, "DB_NAME": dbname}.items() if not v]
if missing:
raise ValueError(f"Missing required environment variables: {', '.join(missing)}")
# Optional extras
schema = env.get("DB_SCHEMA") # e.g., "trans"
# sslmode is not supported in asyncpg URI; SSL must be set programmatically if needed
# Build query params if schema provided
q = f"?options=-csearch_path={schema}" if schema else ""
# URL-encode credentials so special characters don't break the URI
user_enc = quote_plus(user)
password_enc = quote_plus(password)
return f"{protocol}://{user_enc}:{password_enc}@{host}:{port}/{dbname}{q}"
# Single source of truth exported both ways for compatibility
DATABASE_URI: str = build_database_uri()
DATABASE_URL: str = DATABASE_URI
class Settings:
"""Application configuration settings"""
# Database settings
DATABASE_URL: str = DATABASE_URL
# Redis/Cache settings
CACHE_URI: str = os.getenv("CACHE_URI", "redis://localhost:6379")
CACHE_K: str = os.getenv("CACHE_K", "")
# Cache key prefixes (configurable via environment variables)
CART_KEY_PREFIX: str = os.getenv("CART_KEY_PREFIX", "cart")
ORDER_KEY_PREFIX: str = os.getenv("ORDER_KEY_PREFIX", "order")
APPOINTMENTS_KEY_PREFIX: str = os.getenv("APPOINTMENTS_KEY_PREFIX", "appointments")
# JWT settings
# Prefer AMS-specific envs; gracefully fall back to UMS-compatible names
JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY") or os.getenv("SECRET_KEY")
JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM") or os.getenv("ALGORITHM", "HS256")
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = int(
os.getenv("JWT_ACCESS_TOKEN_EXPIRE_MINUTES", os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "30"))
)
# Security settings
# Include Hugging Face Spaces by default to avoid 400s behind their proxy.
# Override via ALLOWED_HOSTS env var in production as needed.
ALLOWED_HOSTS: list = os.getenv(
"ALLOWED_HOSTS",
"localhost,127.0.0.1,*.hf.space,bmsadmin-bookmyservice-ams.hf.space"
).split(",")
CORS_ORIGINS: list = os.getenv(
"CORS_ORIGINS",
"http://localhost:3000,http://127.0.0.1:3000"
).split(",")
# Rate limiting
RATE_LIMIT_CALLS: int = int(os.getenv("RATE_LIMIT_CALLS", "100"))
RATE_LIMIT_PERIOD: int = int(os.getenv("RATE_LIMIT_PERIOD", "60"))
@classmethod
def get_cache_key(cls, key_type: str, *args) -> str:
"""
Generate cache keys using configurable prefixes
Args:
key_type: Type of cache key (cart, order, appointments)
*args: Additional arguments to include in the key
Returns:
str: Formatted cache key
"""
if key_type == "cart":
return f"{cls.CART_KEY_PREFIX}:{':'.join(map(str, args))}"
elif key_type == "order":
return f"{cls.ORDER_KEY_PREFIX}:{':'.join(map(str, args))}"
elif key_type == "appointments":
return f"{cls.APPOINTMENTS_KEY_PREFIX}:{':'.join(map(str, args))}"
else:
raise ValueError(f"Unknown cache key type: {key_type}")
def build_database_uri(env: Dict[str, str] = os.environ) -> str:
"""
Assemble a SQLAlchemy/asyncpg URI from separate env vars.
Supports:
- URL-encoding for user/password
- Default schema via ?options=-csearch_path=<schema>
- sslmode=require (e.g., Neon)
Honors DATABASE_URI if set (escape hatch/override).
"""
# If already provided as a single secret, honor it and return immediately.
env = env or os.environ
direct_uri = env.get("DATABASE_URI") or env.get("DATABASE_URL")
if direct_uri:
return direct_uri
protocol = env.get("DB_PROTOCOL") or "postgresql+asyncpg"
user = env.get("DB_USER")
password = env.get("DB_PASSWORD")
host = env.get("DB_HOST") or "localhost"
port = env.get("DB_PORT") or "5432"
dbname = env.get("DB_NAME")
missing = [k for k, v in {"DB_USER": user, "DB_PASSWORD": password, "DB_NAME": dbname}.items() if not v]
if missing:
raise ValueError(f"Missing required environment variables: {', '.join(missing)}")
# Optional extras
schema = env.get("DB_SCHEMA") # e.g., "trans"
# sslmode is not supported in asyncpg URI; SSL must be set programmatically if needed
# Build query params if schema provided
q = f"?options=-csearch_path={schema}" if schema else ""
# URL-encode credentials so special characters don't break the URI
user_enc = quote_plus(user)
password_enc = quote_plus(password)
return f"{protocol}://{user_enc}:{password_enc}@{host}:{port}/{dbname}{q}"
# Global settings instance
settings = Settings()