Summary
Complete the v2 States accessor ergonomics pass (started in #2108) by adding typed value getters to the States container, before the 2.0 freeze.
States.get_value() / first_value() return the untyped union:
StateType = str | int | float | bool | dict[str, Any] | list[Any] | None
The typed accessors (value_as_float, value_as_int, …) exist only on the State object. So every consumer that needs a concrete type is pushed into one of two verbose shapes:
- Cast the union:
cast(float, device.states.get_value(OverkizState.CORE_TARGET_DWH_TEMPERATURE))
get() + None-guard + value_as_*:
state = device.states.get(name)
if state is None:
return None
position = state.value_as_int
In the Home Assistant overkiz integration's pyOverkiz 2.0 migration this accounts for 52 cast(...) call sites across 23 files (~26 float, ~22 str, ~3 dict, ~1 int), plus the two-step guard pattern in cover position fallbacks and every water-heater / Hitachi temperature property.
Proposed addition
Thin pass-throughs delegating to the already-existing, already-tested State.value_as_* properties:
# on States (backs both device.states and device.attributes)
def get_value_as_float(self, name: StateName) -> float | None:
state = self._index.get(name)
return state.value_as_float if state is not None else None
# ... get_value_as_int / _str / _bool / _dict / _list
# ... first_value_as_float(names) / ... for fallback chains
Usage collapses to:
temp = device.states.get_value_as_float(OverkizState.CORE_TARGET_DWH_TEMPERATURE)
position = device.states.get_value_as_int(state_name) # None if missing
Why before the 2.0 freeze
The addition is non-breaking, but it is freeze-sensitive:
- The new public method names are locked at freeze — renaming them post-2.0 would itself be a breaking change.
- Consumers that pin an exact version (e.g. Home Assistant) need the methods in the
2.0.0 final to adopt them in a single migration pass rather than re-touching every file again in 2.1.
Design decisions to lock
- Naming:
get_value_as_float (mirrors State.value_as_float) vs a shorter get_float. Whatever is chosen is frozen.
- Fallback variant: include
first_value_as_* — serves fallback chains (e.g. cover "My position" / "Unknown position", towel-dryer setpoint selection).
- Mismatch semantics:
State.value_as_* already raises TypeError on a genuine type mismatch (fail-fast). Keep that propagating; only a missing state returns None.
Scope
pyoverkiz/models.py: add the getters to States + the first_value_as_* variants.
tests/test_models.py: cover hit / miss / type-mismatch / enum-key cases.
docs/device-control.md: document the new getters alongside get_value.
Follow-up (separate, in home-assistant-core): adopt the getters and drop the 52 casts.
Relates to #2108.
Summary
Complete the v2
Statesaccessor ergonomics pass (started in #2108) by adding typed value getters to theStatescontainer, before the 2.0 freeze.States.get_value()/first_value()return the untyped union:The typed accessors (
value_as_float,value_as_int, …) exist only on theStateobject. So every consumer that needs a concrete type is pushed into one of two verbose shapes:get()+ None-guard +value_as_*:In the Home Assistant
overkizintegration's pyOverkiz 2.0 migration this accounts for 52cast(...)call sites across 23 files (~26float, ~22str, ~3dict, ~1int), plus the two-step guard pattern in cover position fallbacks and every water-heater / Hitachi temperature property.Proposed addition
Thin pass-throughs delegating to the already-existing, already-tested
State.value_as_*properties:Usage collapses to:
Why before the 2.0 freeze
The addition is non-breaking, but it is freeze-sensitive:
2.0.0final to adopt them in a single migration pass rather than re-touching every file again in 2.1.Design decisions to lock
get_value_as_float(mirrorsState.value_as_float) vs a shorterget_float. Whatever is chosen is frozen.first_value_as_*— serves fallback chains (e.g. cover "My position" / "Unknown position", towel-dryer setpoint selection).State.value_as_*already raisesTypeErroron a genuine type mismatch (fail-fast). Keep that propagating; only a missing state returnsNone.Scope
pyoverkiz/models.py: add the getters toStates+ thefirst_value_as_*variants.tests/test_models.py: cover hit / miss / type-mismatch / enum-key cases.docs/device-control.md: document the new getters alongsideget_value.Follow-up (separate, in home-assistant-core): adopt the getters and drop the 52 casts.
Relates to #2108.