diff --git a/src/transcription/__main__.py b/src/transcription/__main__.py new file mode 100644 index 0000000..d8fe112 --- /dev/null +++ b/src/transcription/__main__.py @@ -0,0 +1,20 @@ +import uvicorn + +from .config import LOGGING_CONFIG +from .config import get_settings + + +def main() -> None: + settings = get_settings() + uvicorn.run( + "transcription.app:create_app", + factory=True, + host=settings.server.host, + port=settings.server.port, + log_level=LOGGING_CONFIG.get("root", {}).get("level", "info").lower(), + reload=settings.server.reload, + ) + + +if __name__ == "__main__": + main() diff --git a/src/transcription/app.py b/src/transcription/app.py index 7f59d1f..9aeafe2 100644 --- a/src/transcription/app.py +++ b/src/transcription/app.py @@ -16,6 +16,7 @@ from fastapi.staticfiles import StaticFiles from .api.errors import register_error_handlers from .api.health import router as health_router +from .config import Settings from .config import configure_logging from .config import get_settings from .db import create_all @@ -73,14 +74,14 @@ async def _recover_stale_processing_jobs(app: FastAPI) -> None: logger.warning("Recovered %s stale processing job(s) at startup", recovered) -def create_app() -> FastAPI: +def create_app(settings: Settings | None = None) -> FastAPI: """Create and configure the FastAPI application.""" app = FastAPI(title="Transcription", lifespan=_lifespan) - settings = get_settings() - app.state.settings = settings + active_settings = settings or get_settings() + app.state.settings = active_settings app.mount( "/uploads", - StaticFiles(directory=settings.upload_dir, check_dir=False), + StaticFiles(directory=active_settings.upload_dir, check_dir=False), name="uploads", ) @@ -92,6 +93,10 @@ def create_app() -> FastAPI: async def ui_redirect() -> RedirectResponse: return RedirectResponse(url="/ui/upload", status_code=status.HTTP_307_TEMPORARY_REDIRECT) + @app.get("/healthz") + def health() -> dict[str, str]: + return {"status": "ok"} + register_error_handlers(app) register_pages(app) app.include_router(health_router) diff --git a/src/transcription/config.py b/src/transcription/config.py index 5657ed4..b6706be 100644 --- a/src/transcription/config.py +++ b/src/transcription/config.py @@ -6,11 +6,13 @@ are resolved by the provider adapters, not here. """ import logging.config -from contextvars import ContextVar from enum import StrEnum +from functools import cache from pathlib import Path from typing import Literal +from pydantic import BaseModel +from pydantic import ConfigDict from pydantic import Field from pydantic_settings import BaseSettings from pydantic_settings import SettingsConfigDict @@ -22,13 +24,26 @@ class Provider(StrEnum): OPENROUTER = "openrouter" +class ServerSettings(BaseModel): + model_config = ConfigDict(frozen=True) + + host: str = "0.0.0.0" + port: int = 8000 + log_level: Literal["critical", "error", "warning", "info", "debug", "trace"] = "info" + reload: bool = False + + class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", extra="ignore", + cli_parse_args=True, ) + # --- NiceGUI Server --- + server: ServerSettings = Field(default_factory=ServerSettings) + # --- AI provider --- provider: Provider = Provider.OPENROUTER openrouter_api_key: str @@ -64,15 +79,9 @@ class Settings(BaseSettings): return self.environment in {"development", "test"} -_settings: ContextVar[Settings | None] = ContextVar("settings", default=None) - - +@cache def get_settings(**kwargs) -> Settings: - settings = _settings.get() - if settings is None: - settings = Settings(**kwargs) # pyright: ignore[reportCallIssue] - _settings.set(settings) - return settings + return Settings(**kwargs) LOGGING_CONFIG: dict[str, object] = {