fix: Fehlerzaehlung, Upload-Fehler und ocr.timeout scharf (v0.4.0)
- [ocr].timeout wird als tesseract_timeout (pro Seite) an ocrmypdf durchgereicht; Default 1800 -> 300, 0 = ocrmypdf-Default - Exceptions nach dem OCR zaehlen als Fehler, Datei wird nach error/ gerettet - Fehlgeschlagene Uploads zaehlen als Fehler und loesen Fehler-Mail aus - name_mode wird im Preflight geprueft, nicht erst pro Datei - Fehlende [paths]-Sektion -> ConfigError mit klarer Meldung statt KeyError - Stabilitaets-Timeout zaehlt als Fehler (--once liefert Exit 1) - upload_folder nutzt shutil.copyfile statt read_bytes/write_bytes - OcrConfig.pdfa_level Default "2" -> "" (Ghostscript-Bug, Issue #3) - 35 neue Tests (92 gesamt), pytest.ini - AI_AGENT_BRIEFING.md auf Stand 0.4.0 gebracht Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,10 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
class ConfigError(RuntimeError):
|
||||
"""Konfigurationsdatei ist unvollständig oder fehlerhaft."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Paths:
|
||||
incoming: Path
|
||||
@@ -21,11 +25,14 @@ class OcrConfig:
|
||||
jobs: int = 4
|
||||
skip_text: bool = True
|
||||
oversample: int = 300
|
||||
pdfa_level: str = "2"
|
||||
# Default bewusst leer: pdfa_level + skip_text zerschießt OCR mit
|
||||
# Ghostscript 10.0.0-10.02.0 (Debian-12-Default), siehe Issue #3
|
||||
pdfa_level: str = ""
|
||||
deskew: bool = True
|
||||
clean: bool = False
|
||||
max_workers: int = 2
|
||||
timeout: int = 1800
|
||||
# Max. Sekunden, die Tesseract pro Seite laufen darf (0 = kein eigenes Limit)
|
||||
timeout: int = 300
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -107,17 +114,40 @@ def _section(data: dict[str, Any], *keys: str) -> dict[str, Any]:
|
||||
return cur if isinstance(cur, dict) else {}
|
||||
|
||||
|
||||
def _require_path(p: dict[str, Any], key: str, cfg_path: Path) -> Path:
|
||||
"""Holt einen Pflicht-Pfad aus der [paths]-Sektion.
|
||||
|
||||
Wirft ConfigError mit klarer Meldung statt eines nackten KeyError.
|
||||
"""
|
||||
value = p.get(key)
|
||||
if value is None or (isinstance(value, str) and not value.strip()):
|
||||
raise ConfigError(
|
||||
f"{cfg_path}: In der Sektion [paths] fehlt der Eintrag '{key}' "
|
||||
f"(oder er ist leer). Bitte ergänzen, z.B. "
|
||||
f'{key} = "/var/lib/pdf-ocr-hotfolder/{key}" '
|
||||
f"— siehe config.example.toml."
|
||||
)
|
||||
return Path(str(value))
|
||||
|
||||
|
||||
def load_config(path: str | Path) -> Config:
|
||||
path = Path(path)
|
||||
with path.open("rb") as f:
|
||||
data = tomllib.load(f)
|
||||
|
||||
if not isinstance(data.get("paths"), dict):
|
||||
raise ConfigError(
|
||||
f"{path}: Die Sektion [paths] fehlt (oder ist keine Tabelle). "
|
||||
"Sie muss die Einträge incoming, outgoing, working und error "
|
||||
"enthalten — siehe config.example.toml."
|
||||
)
|
||||
|
||||
p = _section(data, "paths")
|
||||
paths = Paths(
|
||||
incoming=Path(p["incoming"]),
|
||||
outgoing=Path(p["outgoing"]),
|
||||
working=Path(p["working"]),
|
||||
error=Path(p["error"]),
|
||||
incoming=_require_path(p, "incoming", path),
|
||||
outgoing=_require_path(p, "outgoing", path),
|
||||
working=_require_path(p, "working", path),
|
||||
error=_require_path(p, "error", path),
|
||||
)
|
||||
|
||||
ocr = OcrConfig(**{k: v for k, v in _section(data, "ocr").items()
|
||||
|
||||
Reference in New Issue
Block a user