From 3707f349807cedab9de25551b32c6367502a4921 Mon Sep 17 00:00:00 2001 From: David K Date: Wed, 19 Jun 2024 14:50:45 +0000 Subject: [PATCH] fix: add save way to parse float and int (#46) --- custom_components/phoniebox/media_player.py | 27 +++---- custom_components/phoniebox/utils.py | 18 +++++ tests/test_media_player.py | 18 +++++ tests/test_utils.py | 88 +++++++++++++++++++++ 4 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 tests/test_utils.py diff --git a/custom_components/phoniebox/media_player.py b/custom_components/phoniebox/media_player.py index 115f4d2..c5db9d9 100644 --- a/custom_components/phoniebox/media_player.py +++ b/custom_components/phoniebox/media_player.py @@ -76,7 +76,7 @@ from .data_coordinator import DataCoordinator from .entity import PhonieboxEntity from .services import async_register_custom_services -from .utils import bool_to_string, string_to_bool +from .utils import bool_to_string, parse_float_save, parse_int_save, string_to_bool async def async_setup_entry( @@ -129,7 +129,7 @@ async def async_set_attributes(self, msg: ReceiveMessage) -> None: # noqa: PLR0 new_value = str(msg.payload) if changed_attribute_name == PHONIEBOX_ATTR_VOLUME: - self._attr_volume_level = float(new_value) / 100.0 + self._attr_volume_level = parse_float_save(new_value) / 100.0 elif changed_attribute_name == PHONIEBOX_ATTR_STATE: self._attr_state = PHONIEBOX_STATE_TO_HA[new_value] elif changed_attribute_name == PHONIEBOX_ATTR_MUTE: @@ -137,19 +137,18 @@ async def async_set_attributes(self, msg: ReceiveMessage) -> None: # noqa: PLR0 elif changed_attribute_name == PHONIEBOX_ATTR_RANDOM: self._attr_shuffle = string_to_bool(new_value) elif changed_attribute_name == PHONIEBOX_ATTR_MAX_VOLUME: - self._max_volume = int(new_value) + self._max_volume = parse_int_save(new_value, 100) elif changed_attribute_name == PHONIEBOX_ATTR_DURATION: self._attr_media_duration = sum( - x * int(t) + x * parse_int_save(t) for x, t in zip([3600, 60, 1], new_value.split(":"), strict=False) ) elif changed_attribute_name == PHONIEBOX_ATTR_TRACK: track_number = new_value.split(sep="/", maxsplit=1)[0] - - self._attr_media_track = int(track_number) + self._attr_media_track = parse_int_save(track_number) elif changed_attribute_name == PHONIEBOX_ATTR_ELAPSED: self._attr_media_position = sum( - x * int(t) + x * parse_int_save(t) for x, t in zip([3600, 60, 1], new_value.split(":"), strict=False) ) elif changed_attribute_name == PHONIEBOX_ATTR_ARTIST: @@ -161,14 +160,12 @@ async def async_set_attributes(self, msg: ReceiveMessage) -> None: # noqa: PLR0 elif changed_attribute_name == PHONIEBOX_ATTR_ALBUM_ARTIST: self._attr_media_album_artist = new_value elif changed_attribute_name == PHONIEBOX_ATTR_VOLUME_STEPS: - self._vol_steps = int(new_value) + self._vol_steps = parse_int_save(new_value) elif changed_attribute_name == PHONIEBOX_ATTR_REPEAT: - if new_value == "true": - # is bad but phoniebox will only say if repeat is on or off - self._attr_repeat = REPEAT_MODE_ONE - else: - self._attr_repeat = REPEAT_MODE_OFF - + # is bad but phoniebox will only say if repeat is on or off + self._attr_repeat = ( + REPEAT_MODE_ONE if new_value == "true" else REPEAT_MODE_OFF + ) self.schedule_update_ha_state(force_refresh=True) async def update_device_state(self, msg: ReceiveMessage) -> None: @@ -264,7 +261,7 @@ async def async_turn_off(self) -> None: async def async_set_volume_level(self, volume: float) -> None: """Set volume level, range 0..1.""" await self.mqtt_client.async_publish_cmd( - PHONIEBOX_CMD_SET_VOLUME, int(volume * 100) + PHONIEBOX_CMD_SET_VOLUME, parse_int_save(volume * 100) ) async def async_set_volume_steps(self, volume_steps: int) -> None: diff --git a/custom_components/phoniebox/utils.py b/custom_components/phoniebox/utils.py index c570757..fd96788 100644 --- a/custom_components/phoniebox/utils.py +++ b/custom_components/phoniebox/utils.py @@ -1,5 +1,7 @@ """Utility functions.""" +from typing import Any + def string_to_bool(value: str) -> bool: """Boolean string to boolean converter.""" @@ -13,3 +15,19 @@ def bool_to_string(value: bool) -> str: # noqa: FBT001 if value: return "true" return "false" + + +def parse_float_save(val: Any, fallback: float = 0.0) -> float: + """Try to parse value to float or return fallback.""" + try: + return float(val) + except ValueError: + return fallback + + +def parse_int_save(val: Any, fallback: int = 0) -> int: + """Try to parse value to int or return fallback.""" + try: + return int(val) + except ValueError: + return fallback diff --git a/tests/test_media_player.py b/tests/test_media_player.py index e5f2c5e..ec8fe69 100644 --- a/tests/test_media_player.py +++ b/tests/test_media_player.py @@ -825,3 +825,21 @@ async def test_repeat( phoniebox_state = hass.states.get("media_player.phoniebox_test_box") assert phoniebox_state is not None assert phoniebox_state.attributes.get(ATTR_MEDIA_REPEAT) == REPEAT_MODE_OFF + + +async def test_bugfix_parse_value( + hass: HomeAssistant, mock_phoniebox: MockConfigEntry, config: dict +) -> None: + """ + Test parsing of values in mediaplayer. + + https://github.com/c0un7-z3r0/hass-phoniebox/issues/45 + """ + phoniebox_state = hass.states.get("media_player.phoniebox_test_box") + assert phoniebox_state is not None + + async_fire_mqtt_message(hass, "test_phoniebox/attribute/volume", "-") + await hass.async_block_till_done() + phoniebox_state = hass.states.get("media_player.phoniebox_test_box") + assert phoniebox_state is not None + assert phoniebox_state.attributes.get(ATTR_MEDIA_VOLUME_LEVEL) == 0.0 diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..79bc7b0 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,88 @@ +"""Tests for the utils.""" + +from custom_components.phoniebox.utils import ( + bool_to_string, + parse_float_save, + parse_int_save, + string_to_bool, +) + + +def test_parse_float_save() -> None: + """Test parsing of float.""" + val = parse_float_save("10") + assert val == 10.0 + assert isinstance(val, float) + val = parse_float_save(val="-10", fallback=2.3) + assert val == -10.0 + assert isinstance(val, float) + val = parse_float_save(val="1a0") + assert val == 0 + assert isinstance(val, float) + + +def test_parse_float_save_fallback() -> None: + """Test parsing of float failing.""" + val = parse_float_save("-1a0") + assert val == 0.0 + assert isinstance(val, float) + val = parse_float_save(val="-10a", fallback=2.3) + assert val == 2.3 + assert isinstance(val, float) + val = parse_float_save(val="-10a", fallback=-1.2) + assert val == -1.2 + assert isinstance(val, float) + + +def test_parse_int_save() -> None: + """Test parsing of int.""" + val = parse_int_save("10") + assert val == 10 + assert isinstance(val, int) + + val = parse_int_save(val="-10", fallback=2) + assert val == -10 + assert isinstance(val, int) + + val = parse_int_save(val="1a0") + assert val == 0 + assert isinstance(val, int) + + +def test_parse_int_save_fallback() -> None: + """Test parsing of int failing.""" + val = parse_int_save("-1a0") + assert val == 0.0 + assert isinstance(val, int) + val = parse_int_save(val="-10a", fallback=2) + assert val == 2 + assert isinstance(val, int) + val = parse_int_save(val="-10a", fallback=-1) + assert val == -1 + assert isinstance(val, int) + + +def test_bool_to_string() -> None: + """Test the util function to stringify bool.""" + val = bool_to_string(True) + assert isinstance(val, str) + assert val == "true" + + val = bool_to_string(False) + assert isinstance(val, str) + assert val == "false" + + +def test_string_to_bool() -> None: + """Test the util function to parse string to boolean.""" + val = string_to_bool("true") + assert isinstance(val, bool) + assert val is True + + val = string_to_bool("false") + assert isinstance(val, bool) + assert val is False + + val = string_to_bool("any thing else") + assert isinstance(val, bool) + assert val is False