feat(config): add Pydantic config models, tests, docs and infra notes
This commit is contained in:
parent
9096afbace
commit
4aa7adba3a
@ -45,13 +45,6 @@ The tasks should be completed in the following order to ensure proper dependenci
|
|||||||
|
|
||||||
### 3. Configuration Management
|
### 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 configuration service
|
||||||
|
|
||||||
- []Create `src/server/services/config_service.py`
|
- []Create `src/server/services/config_service.py`
|
||||||
|
|||||||
66
src/server/models/config.py
Normal file
66
src/server/models/config.py
Normal 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)
|
||||||
49
tests/unit/test_config_models.py
Normal file
49
tests/unit/test_config_models.py
Normal 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)
|
||||||
Loading…
x
Reference in New Issue
Block a user