fix: add __post_init__ validation to Settings dataclass (#65)
Reject invalid configuration at startup: negative timeouts, zero concurrency, bad table mode. Reports all errors at once.
This commit is contained in:
parent
24cfd567f2
commit
2c254382c8
2 changed files with 72 additions and 0 deletions
|
|
@ -25,6 +25,27 @@ class Settings:
|
|||
default_factory=lambda: ["http://localhost:3000", "http://localhost:5173"]
|
||||
)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
errors: list[str] = []
|
||||
if self.document_timeout <= 0:
|
||||
errors.append(f"document_timeout must be > 0 (got {self.document_timeout})")
|
||||
if self.conversion_timeout <= 0:
|
||||
errors.append(f"conversion_timeout must be > 0 (got {self.conversion_timeout})")
|
||||
if self.max_concurrent_analyses < 1:
|
||||
errors.append(
|
||||
f"max_concurrent_analyses must be >= 1 (got {self.max_concurrent_analyses})"
|
||||
)
|
||||
if self.max_page_count < 0:
|
||||
errors.append(f"max_page_count must be >= 0 (got {self.max_page_count})")
|
||||
if self.max_file_size < 0:
|
||||
errors.append(f"max_file_size must be >= 0 (got {self.max_file_size})")
|
||||
if self.default_table_mode not in ("accurate", "fast"):
|
||||
errors.append(
|
||||
f"default_table_mode must be 'accurate' or 'fast' (got '{self.default_table_mode}')"
|
||||
)
|
||||
if errors:
|
||||
raise ValueError("Invalid settings:\n " + "\n ".join(errors))
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> Settings:
|
||||
"""Build a Settings instance from environment variables."""
|
||||
|
|
|
|||
|
|
@ -29,6 +29,57 @@ class TestSettingsDefaults:
|
|||
s.upload_dir = "/other" # type: ignore[misc]
|
||||
|
||||
|
||||
class TestSettingsValidation:
|
||||
def test_negative_document_timeout_rejected(self):
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValueError, match="document_timeout must be > 0"):
|
||||
Settings(document_timeout=-1.0)
|
||||
|
||||
def test_zero_document_timeout_rejected(self):
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValueError, match="document_timeout must be > 0"):
|
||||
Settings(document_timeout=0)
|
||||
|
||||
def test_negative_conversion_timeout_rejected(self):
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValueError, match="conversion_timeout must be > 0"):
|
||||
Settings(conversion_timeout=-1)
|
||||
|
||||
def test_zero_max_concurrent_rejected(self):
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValueError, match="max_concurrent_analyses must be >= 1"):
|
||||
Settings(max_concurrent_analyses=0)
|
||||
|
||||
def test_negative_max_page_count_rejected(self):
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValueError, match="max_page_count must be >= 0"):
|
||||
Settings(max_page_count=-1)
|
||||
|
||||
def test_negative_max_file_size_rejected(self):
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValueError, match="max_file_size must be >= 0"):
|
||||
Settings(max_file_size=-1)
|
||||
|
||||
def test_invalid_table_mode_rejected(self):
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValueError, match="default_table_mode must be"):
|
||||
Settings(default_table_mode="turbo")
|
||||
|
||||
def test_multiple_errors_reported(self):
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValueError, match="document_timeout") as exc_info:
|
||||
Settings(document_timeout=-1, conversion_timeout=-1)
|
||||
assert "conversion_timeout" in str(exc_info.value)
|
||||
|
||||
|
||||
class TestSettingsFromEnv:
|
||||
def test_reads_env_vars(self, monkeypatch):
|
||||
monkeypatch.setenv("APP_VERSION", "1.2.3")
|
||||
|
|
|
|||
Loading…
Reference in a new issue