fix(config): report computed fields as read-only on update (#1454)

PUT /v1/config/{path} answered "does not exist in model" when the
target was a computed field such as eos_device_instruction_entity_ids.
Raise an explicit read-only error instead, so API clients can tell
read-only fields from unknown ones.

Closes #913
This commit is contained in:
YTM-21
2026-10-26 07:16:16 +01:00
committed by GitHub
parent 58b968562e
commit 8f4fa6fe94
4 changed files with 35 additions and 1 deletions
+3
View File
@@ -106,6 +106,9 @@ compatibility changes and a short test procedure. These changes are not yet a ta
- GENETIC reads its horizon, interval and prediction settings once at the start of a run. A
configuration change during a run (for example a client adapting the control horizon) no
longer fails the run with an out-of-bounds error; it applies to the next run.
- Updating a read-only computed configuration field, e.g. `adapter.homeassistant.eos_solution_entity_ids`
via `PUT /v1/config/{path}`, now reports a read-only error instead of claiming the field does
not exist ([#913](https://github.com/Akkudoktor-EOS/EOS/issues/913)).
## 0.3.0 (2026-03-17)
+5
View File
@@ -569,6 +569,11 @@ class PydanticModelNestedValueMixin:
raise TypeError(f"Model '{model}' is not of class type.")
if key not in model.model_fields:
if key in model.model_computed_fields:
raise TypeError(
f"Field '{key}' of model '{model.__name__}' is a read-only computed "
"field and cannot be updated."
)
raise TypeError(f"Field '{key}' does not exist in model '{model.__name__}'.")
field_annotation = model.model_fields[key].annotation
+12
View File
@@ -486,6 +486,18 @@ def test_set_nested_key(path, value, expected, exception, config_eos):
f"Expected exception {exception}, but got {type(e)}: {e}"
)
def test_set_nested_key_read_only_computed_field(config_eos):
"""Setting a computed field is reported as read-only, not as missing."""
path = "adapter/homeassistant/eos_device_instruction_entity_ids"
with pytest.raises(TypeError, match="read-only computed field"):
config_eos.set_nested_value(path, ["sensor.eos_battery1"])
def test_set_nested_key_unknown_field_still_reports_missing(config_eos):
"""A field that does not exist keeps the original 'does not exist' message."""
with pytest.raises(TypeError, match="does not exist in model"):
config_eos.set_nested_value("adapter/homeassistant/no_such_field", 1)
@pytest.mark.parametrize(
"path, expected_value, exception",
+15 -1
View File
@@ -3,7 +3,7 @@ from typing import Any, Optional
import pandas as pd
import pendulum
import pytest
from pydantic import Field, ValidationError
from pydantic import BaseModel, Field, ValidationError, computed_field
from akkudoktoreos.core.pydantic import (
PydanticBaseModel,
@@ -199,6 +199,20 @@ class TestPydanticModelNestedValueMixin:
with pytest.raises(TypeError):
PydanticModelNestedValueMixin._get_key_types(User, "unknown_field")
def test_get_key_types_for_computed_field(self):
"""Test _get_key_types reports a computed field as read-only."""
class WithComputed(BaseModel):
value: int = 1
@computed_field # type: ignore[prop-decorator]
@property
def doubled(self) -> int:
return self.value * 2
with pytest.raises(TypeError, match="read-only computed field"):
PydanticModelNestedValueMixin._get_key_types(WithComputed, "doubled")
def test_get_key_types_for_instance_raises(self, user_instance):
"""Test _get_key_types raises an error for an instance."""
with pytest.raises(TypeError):