Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions custom_components/luxtronik2/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
DEFAULT_MAX_DATA_LENGTH,
DEFAULT_PORT,
DEFAULT_TIMEOUT,
DEFAULT_UPDATE_INTERVAL,
DEFAULT_UPDATE_INTERVAL_OPTION,
DOMAIN,
LOGGER,
)
Expand Down Expand Up @@ -512,15 +512,15 @@ async def async_step_user(
new_options[CONF_HA_SENSOR_INDOOR_TEMPERATURE] = None

update_interval = user_input.get(
CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL
CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL_OPTION
)
new_options[CONF_UPDATE_INTERVAL] = update_interval

return self.async_create_entry(title="", data=new_options)

current_indoor_temp = self._get_value(CONF_HA_SENSOR_INDOOR_TEMPERATURE)
current_interval = self._get_value(
CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL
CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL_OPTION
)

return self.async_show_form(
Expand Down
9 changes: 9 additions & 0 deletions custom_components/luxtronik2/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@
"1 minute (default)": timedelta(seconds=60),
"5 minutes": timedelta(minutes=5),
}
# The UPDATE_INTERVAL_OPTIONS key matching DEFAULT_UPDATE_INTERVAL. The options
# flow's CONF_UPDATE_INTERVAL field is a SelectSelector whose default/suggested
# value must be one of these string keys - DEFAULT_UPDATE_INTERVAL itself is a
# timedelta and isn't JSON-serializable when used there.
DEFAULT_UPDATE_INTERVAL_OPTION: Final = next(
key
for key, value in UPDATE_INTERVAL_OPTIONS.items()
if value == DEFAULT_UPDATE_INTERVAL
)

UPDATE_INTERVAL_FAST: Final = timedelta(seconds=10)
UPDATE_INTERVAL_NORMAL: Final = timedelta(minutes=1)
Expand Down
7 changes: 4 additions & 3 deletions custom_components/luxtronik2/schema_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
DEFAULT_MAX_DATA_LENGTH,
DEFAULT_PORT,
DEFAULT_TIMEOUT,
DEFAULT_UPDATE_INTERVAL,
DEFAULT_UPDATE_INTERVAL_OPTION,
UPDATE_INTERVAL_OPTIONS,
)

Expand Down Expand Up @@ -53,9 +53,10 @@ def build_options_schema(
),
vol.Optional(
CONF_UPDATE_INTERVAL,
default=current_interval or DEFAULT_UPDATE_INTERVAL,
default=current_interval or DEFAULT_UPDATE_INTERVAL_OPTION,
description={
"suggested_value": current_interval or DEFAULT_UPDATE_INTERVAL
"suggested_value": current_interval
or DEFAULT_UPDATE_INTERVAL_OPTION
},
): selector.SelectSelector(
selector.SelectSelectorConfig(
Expand Down
14 changes: 14 additions & 0 deletions tests/test_const.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
DEFAULT_PORT,
DEFAULT_TIMEOUT,
DEFAULT_UPDATE_INTERVAL,
DEFAULT_UPDATE_INTERVAL_OPTION,
DOMAIN,
PLATFORMS,
UPDATE_INTERVAL_OPTIONS,
Expand Down Expand Up @@ -198,6 +199,19 @@ def test_update_interval_options_keys_and_timedeltas(self):
assert UPDATE_INTERVAL_OPTIONS["1 minute (default)"].total_seconds() == 60
assert UPDATE_INTERVAL_OPTIONS["5 minutes"].total_seconds() == 300

def test_default_update_interval_option_is_a_valid_key(self):
"""DEFAULT_UPDATE_INTERVAL_OPTION must be a string key of
UPDATE_INTERVAL_OPTIONS, not the DEFAULT_UPDATE_INTERVAL timedelta
itself - the options flow's SelectSelector default must match one of
its own option values or the schema fails to serialize (see #656).
"""
assert isinstance(DEFAULT_UPDATE_INTERVAL_OPTION, str)
assert DEFAULT_UPDATE_INTERVAL_OPTION in UPDATE_INTERVAL_OPTIONS
assert (
UPDATE_INTERVAL_OPTIONS[DEFAULT_UPDATE_INTERVAL_OPTION]
== DEFAULT_UPDATE_INTERVAL
)

def test_conf_update_interval_constant(self):
assert CONF_UPDATE_INTERVAL == "update_interval"

Expand Down
18 changes: 18 additions & 0 deletions tests/test_schema_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

from __future__ import annotations

import json
from typing import Any, cast

from homeassistant.const import CONF_HOST, CONF_PORT
import homeassistant.helpers.config_validation as cv
import pytest
import voluptuous as vol
import voluptuous_serialize

from custom_components.luxtronik2.const import (
DEFAULT_PORT,
Expand Down Expand Up @@ -92,3 +95,18 @@ def test_with_update_interval(self):
current_interval="1 minute",
)
assert isinstance(schema, vol.Schema)

def test_default_schema_is_json_serializable(self):
"""Regression test for issue #656.

A config entry that has never saved options before (current_interval=None,
the common case) must not fall back to the raw DEFAULT_UPDATE_INTERVAL
timedelta - the frontend serializes this schema to JSON to render the
"Configure" form, and a timedelta default/suggested_value crashes that
with a 500 (TypeError: Object of type timedelta is not JSON serializable).
"""
schema = build_options_schema()
converted = voluptuous_serialize.convert(
schema, custom_serializer=cv.custom_serializer
)
json.dumps(converted) # must not raise
Loading