Files
mtgonline/backend/app/core/config.py
T
akadmin 900f51af3a Complete architecture simplification: 54+ files → 37 files
- Removed 11 duplicate router files (auth.py, users.py, decks.py, card_router.py, interactions.py, user_data.py, rooms.py, games/router.py, refresh.py, card_import.py, ws.py)
- Removed duplicate core files (logging_config.py, settings.py)
- Removed unused services (card_mirror_service.py, mtgjson_loader.py, mtgjson_uploader.py)
- Removed unused schemas (proto_messages.py, protocol_constants.py)
- Created run.py startup script for simple execution
- Created app/core/config.py for unified configuration management
- Created app/core/logging.py for logging configuration
- Consolidated routers into 6 focused files (auth_router.py, users_router.py, decks_router.py, cards_router.py, user_data_router.py, games_router.py)
- Updated all imports to use consolidated model paths
- Added backward compatibility aliases in database.py
- Created SIMPLIFICATION_SUMMARY.md documentation

Architecture improvements:
- Single unified database engine with auto-detection (SQLite/PostgreSQL)
- Consolidated models into 2 files (models.py, mtg_models.py)
- Clean router structure with one file per domain
- Simplified configuration with sensible defaults
- SQLite by default for development, PostgreSQL for production

Testing:
- All imports verified working
- App loads successfully
- Ready for development and testing
2026-08-26 15:15:36 +00:00

217 lines
7.0 KiB
Python

"""
Application configuration management.
Simple configuration with sensible defaults. SQLite by default for development,
PostgreSQL supported via DATABASE_URL environment variable.
## Usage
from app.core.config import get_config
config = get_config()
print(config.DATABASE_URL)
## Environment Variables
All configuration values can be overridden via environment variables.
The .env file in the project root is also loaded automatically.
| Variable | Default | Description |
|---|---|---|
| APP_NAME | MTG Online | Application display name |
| APP_VERSION | 0.2.0 | Application version |
| DEBUG | false | Enable debug mode |
| SECRET_KEY | change-me-in-production | Application secret key |
| JWT_SECRET_KEY | (same as SECRET_KEY) | JWT signing key |
| DATABASE_URL | sqlite:///./mtgonline.db | Database connection string |
| JWT_ALGORITHM | HS256 | JWT signing algorithm |
| JWT_ACCESS_TOKEN_EXPIRE_MINUTES | 60 | Access token lifetime |
| JWT_REFRESH_TOKEN_EXPIRE_DAYS | 7 | Refresh token lifetime |
| CORS_ORIGINS | ["http://localhost:3000", "http://localhost:8000"] | Allowed CORS origins |
| BCRYPT_ROUNDS | 12 | Bcrypt hashing rounds |
| LOG_LEVEL | INFO | Logging level |
| LOG_FORMAT | text | Log output format (text or json) |
| DATA_DIR | /app/data | Data directory for MTGJSON files |
| UPLOAD_DIR | /app/uploads | Upload directory |
"""
import os
from typing import Optional
def _load_env_file(path: str) -> None:
"""Load environment variables from a .env file if it exists.
Args:
path: Absolute or relative path to the .env file.
"""
if os.path.isfile(path):
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" not in line:
continue
key, _, value = line.partition("=")
key = key.strip()
value = value.strip().strip("\"'")
if key and value and key not in os.environ:
os.environ[key] = value
# Load .env from project root (one level up from backend/app/core/)
_project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
_env_path = os.path.join(_project_root, ".env")
_load_env_file(_env_path)
# Also try backend/.env
_backend_env = os.path.join(_project_root, "..", ".env")
_load_env_file(os.path.abspath(_backend_env))
class Config:
"""Application configuration with sensible defaults.
All values can be overridden via environment variables.
This is a simple class (not pydantic-settings) for minimal dependencies.
Attributes:
APP_NAME: Application display name.
APP_VERSION: Application version string.
DEBUG: Enable debug mode (enables SQL echo, etc.).
SECRET_KEY: Application secret key for signing.
JWT_SECRET_KEY: JWT signing key (falls back to SECRET_KEY).
DATABASE_URL: SQLAlchemy database connection string.
Defaults to SQLite for development.
JWT_ALGORITHM: JWT signing algorithm (default HS256).
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: Access token lifetime in minutes.
JWT_REFRESH_TOKEN_EXPIRE_DAYS: Refresh token lifetime in days.
CORS_ORIGINS: List of allowed CORS origin URLs.
BCRYPT_ROUNDS: Number of bcrypt hashing rounds.
LOG_LEVEL: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL).
LOG_FORMAT: Log output format (text or json).
DATA_DIR: Directory for MTGJSON data files.
UPLOAD_DIR: Directory for user uploads.
"""
# Application
APP_NAME: str = "MTG Online"
APP_VERSION: str = "0.2.0"
DEBUG: bool = False
SECRET_KEY: str = "change-me-in-production"
JWT_SECRET_KEY: str = "" # Falls back to SECRET_KEY if empty
# Database - single unified connection string
DATABASE_URL: str = "sqlite+aiosqlite:///./mtgonline.db"
# JWT Configuration
JWT_ALGORITHM: str = "HS256"
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 60
JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = 7
# CORS
CORS_ORIGINS: list[str] = ["http://localhost:3000", "http://localhost:8000"]
# Security
BCRYPT_ROUNDS: int = 12
# Logging
LOG_LEVEL: str = "INFO"
LOG_FORMAT: str = "text"
# Directories
DATA_DIR: str = "/app/data"
UPLOAD_DIR: str = "/app/uploads"
def __init__(self) -> None:
"""Initialize configuration from environment variables."""
self._apply_env_overrides()
def _apply_env_overrides(self) -> None:
"""Override configuration values from environment variables."""
env_map: dict[str, type] = {
"APP_NAME": str,
"APP_VERSION": str,
"DEBUG": bool,
"SECRET_KEY": str,
"JWT_SECRET_KEY": str,
"DATABASE_URL": str,
"JWT_ALGORITHM": str,
"JWT_ACCESS_TOKEN_EXPIRE_MINUTES": int,
"JWT_REFRESH_TOKEN_EXPIRE_DAYS": int,
"BCRYPT_ROUNDS": int,
"LOG_LEVEL": str,
"LOG_FORMAT": str,
"DATA_DIR": str,
"UPLOAD_DIR": str,
}
for attr, type_cast in env_map.items():
value = os.environ.get(attr)
if value is not None:
if type_cast == bool:
value = value.lower() in ("true", "1", "yes", "on")
else:
value = type_cast(value)
setattr(self, attr, value)
# CORS_ORIGINS: parse comma-separated from env or keep default list
cors_env = os.environ.get("CORS_ORIGINS")
if cors_env:
self.CORS_ORIGINS = [
origin.strip() for origin in cors_env.split(",") if origin.strip()
]
@property
def jwt_secret(self) -> str:
"""Get the effective JWT secret key.
Returns JWT_SECRET_KEY if set, otherwise falls back to SECRET_KEY.
Returns:
The JWT secret key string.
"""
return self.JWT_SECRET_KEY or self.SECRET_KEY
@property
def is_sqlite(self) -> bool:
"""Check if the database is SQLite.
Returns:
True if using SQLite, False otherwise.
"""
return self.DATABASE_URL.startswith("sqlite")
@property
def is_postgresql(self) -> bool:
"""Check if the database is PostgreSQL.
Returns:
True if using PostgreSQL, False otherwise.
"""
return "postgresql" in self.DATABASE_URL
# Singleton instance
_config_instance: Optional[Config] = None
def get_config() -> Config:
"""Get the application configuration singleton.
Returns a cached Config instance. Subsequent calls return the same object.
Returns:
The application configuration instance.
Example:
>>> config = get_config()
>>> print(config.DATABASE_URL)
sqlite:///./mtgonline.db
"""
global _config_instance
if _config_instance is None:
_config_instance = Config()
return _config_instance