chore: scaffold Python service

This commit is contained in:
Lucas Winther committed 2026-09-18 18:02:33 +02:00
1 parent c5660a89dd
commit bcf1e9a9a9
14 files changed
+854 -10

No files matched your search

+1
View File
@@ -0,0 +1 @@
"""recoll-modern service package."""
+12
View File
@@ -0,0 +1,12 @@
"""Console entry point for recoll-modern."""
import uvicorn
from recoll_modern.app import create_app
from recoll_modern.config import Settings
def main() -> None:
"""Load process configuration and start the HTTP server."""
settings = Settings.from_env()
uvicorn.run(create_app(settings), host=settings.host, port=settings.port)
+16
View File
@@ -0,0 +1,16 @@
"""FastAPI application factory."""
from fastapi import FastAPI
from recoll_modern.config import Settings
def create_app(settings: Settings) -> FastAPI:
"""Create the HTTP application with its configured mount prefix."""
app = FastAPI(root_path=settings.root_path)
@app.get("/health")
def health() -> dict[str, str]:
return {"status": "starting"}
return app
+67
View File
@@ -0,0 +1,67 @@
"""Configuration loaded at the application's process boundary."""
import ipaddress
import os
from collections.abc import Mapping
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True, slots=True)
class Settings:
"""Immutable runtime configuration for the service."""
document_root: Path
state_root: Path
recoll_config_dir: Path
host: str
port: int
root_path: str
trusted_proxies: tuple[ipaddress.IPv4Network | ipaddress.IPv6Network, ...]
inline_text_limit: int
inline_text_limit_max: int
@property
def database_path(self) -> Path:
"""Return the SQLite database location inside the configured state root."""
return self.state_root / "recoll-modern.sqlite3"
@property
def membership_map_path(self) -> Path:
"""Return the generated Recoll membership-map location."""
return self.state_root / "membership.map"
@classmethod
def from_env(cls, environ: Mapping[str, str] = os.environ) -> "Settings":
"""Build settings from an environment mapping."""
root_path = environ.get("RECOLL_MODERN_ROOT_PATH", "/")
if not root_path.startswith("/") or (root_path != "/" and root_path.endswith("/")):
raise ValueError("mount prefix must start with / and have no trailing slash")
text_max = int(environ.get("RECOLL_MODERN_INLINE_TEXT_LIMIT_MAX", "100000"))
text_default = int(environ.get("RECOLL_MODERN_INLINE_TEXT_LIMIT", "20000"))
if not 0 < text_default <= text_max:
raise ValueError("inline text limit must be positive and no greater than its maximum")
proxy_text = environ.get("RECOLL_MODERN_TRUSTED_PROXIES", "")
proxies = tuple(
ipaddress.ip_network(value.strip(), strict=False)
for value in proxy_text.split(",")
if value.strip()
)
return cls(
document_root=Path(environ.get("RECOLL_MODERN_DOCUMENT_ROOT", "/documents")).resolve(),
state_root=Path(
environ.get("RECOLL_MODERN_STATE_ROOT", "/var/lib/recoll-modern")
).resolve(),
recoll_config_dir=Path(
environ.get("RECOLL_MODERN_RECOLL_CONFIG_DIR", "/etc/recoll-modern")
).resolve(),
host=environ.get("RECOLL_MODERN_HOST", "0.0.0.0"),
port=int(environ.get("RECOLL_MODERN_PORT", "8000")),
root_path=root_path,
trusted_proxies=proxies,
inline_text_limit=text_default,
inline_text_limit_max=text_max,
)