diff --git a/pyproject.toml b/pyproject.toml index a123475e..a0d1ca8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ packages = [ {include = "**/*.py", from = "src"}, ] readme = "README.md" -version = "0.25.0" +version = "0.25.1" [tool.poetry.dependencies] # For certifi, use ">=" instead of "^" since it upgrades its "major version" every year, not really following semver diff --git a/src/groundlight/edge/config.py b/src/groundlight/edge/config.py index 3d476bb1..1b0de76d 100644 --- a/src/groundlight/edge/config.py +++ b/src/groundlight/edge/config.py @@ -9,7 +9,7 @@ class GlobalConfig(BaseModel): # pylint: disable=too-few-public-methods """Global runtime settings for edge-endpoint behavior.""" - model_config = ConfigDict(extra="forbid") + model_config = ConfigDict(extra="ignore") refresh_rate: float = Field( default=60.0, @@ -27,7 +27,7 @@ class InferenceConfig(BaseModel): # pylint: disable=too-few-public-methods """ # Keep shared presets immutable (DEFAULT/NO_CLOUD/etc.) so one mutation cannot globally change behavior. - model_config = ConfigDict(extra="forbid", frozen=True) + model_config = ConfigDict(extra="ignore", frozen=True) name: str = Field(..., exclude=True, description="A unique name for this inference config preset.") enabled: bool = Field( @@ -76,7 +76,7 @@ class DetectorConfig(BaseModel): # pylint: disable=too-few-public-methods Configuration for a specific detector. """ - model_config = ConfigDict(extra="forbid") + model_config = ConfigDict(extra="ignore") detector_id: str = Field(..., description="Detector ID") edge_inference_config: str = Field(..., description="Config for edge inference.") @@ -85,7 +85,7 @@ class DetectorConfig(BaseModel): # pylint: disable=too-few-public-methods class ConfigBase(BaseModel): """Shared detector/inference configuration behavior for edge config models.""" - model_config = ConfigDict(extra="forbid") + model_config = ConfigDict(extra="ignore") edge_inference_configs: dict[str, InferenceConfig] = Field(default_factory=dict) detectors: list[DetectorConfig] = Field(default_factory=list) diff --git a/test/unit/test_edge_config.py b/test/unit/test_edge_config.py index 6d313cc0..469e6061 100644 --- a/test/unit/test_edge_config.py +++ b/test/unit/test_edge_config.py @@ -235,10 +235,21 @@ def test_edge_endpoint_config_from_yaml_requires_exactly_one_input(): EdgeEndpointConfig.from_yaml(filename=" ") -def test_edge_endpoint_config_rejects_extra_top_level_fields(): - """Rejects unknown top-level fields to avoid silent config drift.""" - with pytest.raises(ValueError, match="Extra inputs are not permitted"): - EdgeEndpointConfig.model_validate({"global_config": {}, "unknown_field": True}) +def test_edge_endpoint_config_ignores_extra_fields_at_all_levels(): + """Unknown fields are silently ignored at every nesting level for forward compatibility.""" + config = EdgeEndpointConfig.model_validate({ + "global_config": {"refresh_rate": REFRESH_RATE_SECONDS, "unknown_global_field": "ignored"}, + "edge_inference_configs": { + "default": {"enabled": True, "unknown_inference_field": 42}, + }, + "detectors": [ + {"detector_id": "det_1", "edge_inference_config": "default", "unknown_detector_field": [1, 2]}, + ], + "unknown_top_level_field": True, + }) + assert config.global_config.refresh_rate == REFRESH_RATE_SECONDS + assert config.edge_inference_configs["default"].enabled is True + assert config.detectors[0].detector_id == "det_1" def test_model_dump_shape_for_edge_endpoint_config():