diff --git a/instructions.md b/instructions.md index 9a66cbb..c815122 100644 --- a/instructions.md +++ b/instructions.md @@ -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` diff --git a/src/server/models/config.py b/src/server/models/config.py new file mode 100644 index 0000000..f5b8368 --- /dev/null +++ b/src/server/models/config.py @@ -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) diff --git a/tests/unit/test_config_models.py b/tests/unit/test_config_models.py new file mode 100644 index 0000000..dfbd085 --- /dev/null +++ b/tests/unit/test_config_models.py @@ -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)