Files
mywhoosh2garmin/app/config.py
Bastian Wagner 49aba8efb4 fix: address final review findings for foundation plan
- C1: drop module-level app singleton in app/main.py so importing the
  package no longer validates Settings or creates DATA_DIR; run uvicorn
  with --factory in the Dockerfile. pytest now collects and passes with
  no ambient env vars.
- I2: add missing app/auth, app/security, app/web __init__.py so
  setuptools discovers all five packages.
- I3: resolve the Jinja2 template directory relative to __file__ instead
  of the process CWD.
- I4: add .gitignore covering .env, data/, .venv/, caches and build
  artifacts so example deployment secrets cannot be committed.
- I5: assert UserRepository.list_enabled() excludes disabled users.
- M6: encode both operands before hmac.compare_digest in validate_csrf so
  a non-ASCII token yields 403 instead of an unhandled 500.
- M9: remove unused relationship / HealthState imports.
- M11: make session cookie https_only configurable via SESSION_HTTPS_ONLY
  (default unchanged: false).
- M13: dispose SQLAlchemy engines in the db_session and client fixtures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 10:14:26 +02:00

38 lines
1.3 KiB
Python

from functools import lru_cache
from pathlib import Path
from pydantic import Field, PositiveInt, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", extra="ignore", case_sensitive=False)
admin_password: str = Field(alias="ADMIN_PASSWORD", min_length=1)
secret_key: str = Field(alias="SECRET_KEY", min_length=16)
credential_encryption_key: str = Field(alias="CREDENTIAL_ENCRYPTION_KEY", min_length=1)
data_dir: Path = Field(default=Path("/data"), alias="DATA_DIR")
database_url: str | None = Field(default=None, alias="DATABASE_URL")
sync_interval_minutes: PositiveInt = Field(default=5, alias="SYNC_INTERVAL_MINUTES")
session_https_only: bool = Field(default=False, alias="SESSION_HTTPS_ONLY")
@model_validator(mode="after")
def derive_paths(self) -> "Settings":
self.data_dir = self.data_dir.expanduser().resolve()
if self.database_url is None:
self.database_url = f"sqlite:///{self.data_dir / 'app.db'}"
return self
@property
def tokens_dir(self) -> Path:
return self.data_dir / "tokens"
@property
def activities_dir(self) -> Path:
return self.data_dir / "activities"
@lru_cache
def get_settings() -> Settings:
return Settings()