Spaces:
Sleeping
Sleeping
| 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")) | |
| 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() |