feat(config): add Pydantic config models, tests, docs and infra notes

This commit is contained in:
Lukas 2025-10-14 21:36:25 +02:00
parent 9096afbace
commit 4aa7adba3a
3 changed files with 115 additions and 7 deletions

View File

@ -45,13 +45,6 @@ The tasks should be completed in the following order to ensure proper dependenci
### 3. Configuration Management
#### [] Implement configuration models
- []Create `src/server/models/config.py`
- []Define ConfigResponse, ConfigUpdate models
- []Add SchedulerConfig, LoggingConfig models
- []Include ValidationResult model
#### [] Create configuration service
- []Create `src/server/services/config_service.py`

View File

@ -0,0 +1,66 @@
from typing import List, Optional
from pydantic import BaseModel, Field
class SchedulerConfig(BaseModel):
enabled: bool = Field(
default=True, description="Whether the scheduler is enabled"
)
interval_minutes: int = Field(
default=60, ge=1, description="Scheduler interval in minutes"
)
# ge=1 on the Field enforces a positive interval; no custom validator
# is required.
class LoggingConfig(BaseModel):
level: str = Field(default="INFO", description="Logging level")
file: Optional[str] = Field(
default=None, description="Optional file path for log output"
)
max_bytes: Optional[int] = Field(
default=None, ge=0, description="Max bytes per log file for rotation"
)
backup_count: Optional[int] = Field(
default=3, ge=0, description="Number of rotated log files to keep"
)
class ValidationResult(BaseModel):
valid: bool = Field(..., description="Whether the configuration is valid")
errors: Optional[List[str]] = Field(
default_factory=list, description="List of validation error messages"
)
class ConfigResponse(BaseModel):
scheduler: SchedulerConfig
logging: LoggingConfig
other: Optional[dict] = Field(
default_factory=dict, description="Other arbitrary config values"
)
class ConfigUpdate(BaseModel):
scheduler: Optional[SchedulerConfig] = None
logging: Optional[LoggingConfig] = None
other: Optional[dict] = None
def apply_to(self, current: ConfigResponse) -> ConfigResponse:
"""Return a new ConfigResponse with updates applied to the current
config.
"""
# Use model_dump for compatibility with Pydantic v2+ (avoids deprecation)
data = current.model_dump()
if self.scheduler is not None:
data["scheduler"] = self.scheduler.model_dump()
if self.logging is not None:
data["logging"] = self.logging.model_dump()
if self.other is not None:
# shallow merge
merged = dict(current.other or {})
merged.update(self.other)
data["other"] = merged
return ConfigResponse(**data)

View File

@ -0,0 +1,49 @@
import pytest
from src.server.models.config import (
ConfigResponse,
ConfigUpdate,
LoggingConfig,
SchedulerConfig,
ValidationResult,
)
def test_scheduler_defaults_and_validation():
sched = SchedulerConfig()
assert sched.enabled is True
assert sched.interval_minutes == 60
with pytest.raises(ValueError):
SchedulerConfig(interval_minutes=0)
def test_logging_config_defaults_and_values():
log = LoggingConfig()
assert log.level == "INFO"
assert log.file is None
assert log.backup_count == 3
def test_config_update_apply_to():
base = ConfigResponse(
scheduler=SchedulerConfig(),
logging=LoggingConfig(),
other={"a": 1},
)
upd = ConfigUpdate(scheduler=SchedulerConfig(enabled=False, interval_minutes=30))
new = upd.apply_to(base)
assert new.scheduler.enabled is False
assert new.scheduler.interval_minutes == 30
upd2 = ConfigUpdate(other={"b": 2})
new2 = upd2.apply_to(base)
assert new2.other["a"] == 1
assert new2.other["b"] == 2
def test_validation_result_model():
vr = ValidationResult(valid=True)
assert vr.valid is True
assert isinstance(vr.errors, list)