- 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>
38 lines
1.3 KiB
Python
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()
|