From ab067eaceb8052feafe1838f0d3d889bcbe1f9ac Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Fri, 20 Mar 2026 19:13:25 +0000 Subject: [PATCH 1/5] allow unknown fields --- pyproject.toml | 2 +- src/groundlight/edge/config.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) 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) From a0940acf004141e1ea52dac787754ca110f837ad Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Fri, 20 Mar 2026 19:17:08 +0000 Subject: [PATCH 2/5] updating tests --- test/unit/test_edge_config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/test_edge_config.py b/test/unit/test_edge_config.py index 6d313cc0..311bba35 100644 --- a/test/unit/test_edge_config.py +++ b/test/unit/test_edge_config.py @@ -235,10 +235,10 @@ 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_top_level_fields(): + """Unknown fields are silently ignored for forward compatibility (Postel's Law).""" + config = EdgeEndpointConfig.model_validate({"global_config": {}, "unknown_field": True}) + assert config.global_config.refresh_rate == 60.0 def test_model_dump_shape_for_edge_endpoint_config(): From 20e2c6194b2548d1aec02686f763b6d0590e7d3c Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Fri, 20 Mar 2026 19:20:01 +0000 Subject: [PATCH 3/5] fixing linter error --- test/unit/test_edge_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test_edge_config.py b/test/unit/test_edge_config.py index 311bba35..06ecd2ee 100644 --- a/test/unit/test_edge_config.py +++ b/test/unit/test_edge_config.py @@ -238,7 +238,7 @@ def test_edge_endpoint_config_from_yaml_requires_exactly_one_input(): def test_edge_endpoint_config_ignores_extra_top_level_fields(): """Unknown fields are silently ignored for forward compatibility (Postel's Law).""" config = EdgeEndpointConfig.model_validate({"global_config": {}, "unknown_field": True}) - assert config.global_config.refresh_rate == 60.0 + assert config.global_config == GlobalConfig() def test_model_dump_shape_for_edge_endpoint_config(): From 5682b867d585f50efd31bccae855f93a08f0248f Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Fri, 20 Mar 2026 20:45:25 +0000 Subject: [PATCH 4/5] responding to PR feedback --- test/unit/test_edge_config.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/test/unit/test_edge_config.py b/test/unit/test_edge_config.py index 06ecd2ee..6b5398d4 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_ignores_extra_top_level_fields(): - """Unknown fields are silently ignored for forward compatibility (Postel's Law).""" - config = EdgeEndpointConfig.model_validate({"global_config": {}, "unknown_field": True}) - assert config.global_config == GlobalConfig() +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": 30.0, "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 == 30.0 + 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(): From 858652e63abe9c2c242f09aab6d5acd2e89a18cb Mon Sep 17 00:00:00 2001 From: Tim Huff Date: Fri, 20 Mar 2026 20:48:51 +0000 Subject: [PATCH 5/5] fixing linter error --- test/unit/test_edge_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/test_edge_config.py b/test/unit/test_edge_config.py index 6b5398d4..469e6061 100644 --- a/test/unit/test_edge_config.py +++ b/test/unit/test_edge_config.py @@ -238,7 +238,7 @@ def test_edge_endpoint_config_from_yaml_requires_exactly_one_input(): 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": 30.0, "unknown_global_field": "ignored"}, + "global_config": {"refresh_rate": REFRESH_RATE_SECONDS, "unknown_global_field": "ignored"}, "edge_inference_configs": { "default": {"enabled": True, "unknown_inference_field": 42}, }, @@ -247,7 +247,7 @@ def test_edge_endpoint_config_ignores_extra_fields_at_all_levels(): ], "unknown_top_level_field": True, }) - assert config.global_config.refresh_rate == 30.0 + 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"