diff --git a/homeassistant/components/amcrest/manifest.json b/homeassistant/components/amcrest/manifest.json index acd93c4e2ed..725ff96b3ad 100644 --- a/homeassistant/components/amcrest/manifest.json +++ b/homeassistant/components/amcrest/manifest.json @@ -2,7 +2,7 @@ "domain": "amcrest", "name": "Amcrest", "documentation": "https://www.home-assistant.io/integrations/amcrest", - "requirements": ["amcrest==1.8.0"], + "requirements": ["amcrest==1.8.1"], "dependencies": ["ffmpeg"], "codeowners": ["@flacjacket"], "iot_class": "local_polling" diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json index fa9313eb9a1..c8e9c29e4e7 100644 --- a/homeassistant/components/myq/manifest.json +++ b/homeassistant/components/myq/manifest.json @@ -2,7 +2,7 @@ "domain": "myq", "name": "MyQ", "documentation": "https://www.home-assistant.io/integrations/myq", - "requirements": ["pymyq==3.1.3"], + "requirements": ["pymyq==3.1.4"], "codeowners": ["@bdraco","@ehendrix23"], "config_flow": true, "homekit": { diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index fafaabbd217..413d9d2152f 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -209,6 +209,9 @@ class SensorEntity(Entity): and not self._last_reset_reported ): self._last_reset_reported = True + if self.platform and self.platform.platform_name == "energy": + return {ATTR_LAST_RESET: last_reset.isoformat()} + report_issue = self._suggest_report_issue() _LOGGER.warning( "Entity %s (%s) with state_class %s has set last_reset. Setting " diff --git a/homeassistant/components/sensor/recorder.py b/homeassistant/components/sensor/recorder.py index 4ba33d7a902..e94cf0f6e46 100644 --- a/homeassistant/components/sensor/recorder.py +++ b/homeassistant/components/sensor/recorder.py @@ -441,8 +441,8 @@ def compile_statistics( # noqa: C901 _LOGGER.info( "Detected new cycle for %s, value dropped from %s to %s", entity_id, - fstate, new_state, + fstate, ) if reset: diff --git a/homeassistant/components/yamaha_musiccast/manifest.json b/homeassistant/components/yamaha_musiccast/manifest.json index bd614e368dc..be52b8a4558 100644 --- a/homeassistant/components/yamaha_musiccast/manifest.json +++ b/homeassistant/components/yamaha_musiccast/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/yamaha_musiccast", "requirements": [ - "aiomusiccast==0.9.1" + "aiomusiccast==0.9.2" ], "ssdp": [ { diff --git a/homeassistant/const.py b/homeassistant/const.py index ee903de1abd..110c49fbc01 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -5,7 +5,7 @@ from typing import Final MAJOR_VERSION: Final = 2021 MINOR_VERSION: Final = 9 -PATCH_VERSION: Final = "5" +PATCH_VERSION: Final = "6" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0) diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index da4d2bacf15..f1e74e26908 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -6,16 +6,10 @@ from datetime import datetime, timedelta import logging from typing import Any, cast -from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP -from homeassistant.core import ( - CoreState, - HomeAssistant, - State, - callback, - valid_entity_id, -) +from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import HomeAssistant, State, callback, valid_entity_id from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry, start from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.json import JSONEncoder @@ -63,42 +57,36 @@ class StoredState: class RestoreStateData: """Helper class for managing the helper saved data.""" - @classmethod - async def async_get_instance(cls, hass: HomeAssistant) -> RestoreStateData: + @staticmethod + @singleton(DATA_RESTORE_STATE_TASK) + async def async_get_instance(hass: HomeAssistant) -> RestoreStateData: """Get the singleton instance of this data helper.""" + data = RestoreStateData(hass) - @singleton(DATA_RESTORE_STATE_TASK) - async def load_instance(hass: HomeAssistant) -> RestoreStateData: - """Get the singleton instance of this data helper.""" - data = cls(hass) + try: + stored_states = await data.store.async_load() + except HomeAssistantError as exc: + _LOGGER.error("Error loading last states", exc_info=exc) + stored_states = None - try: - stored_states = await data.store.async_load() - except HomeAssistantError as exc: - _LOGGER.error("Error loading last states", exc_info=exc) - stored_states = None + if stored_states is None: + _LOGGER.debug("Not creating cache - no saved states found") + data.last_states = {} + else: + data.last_states = { + item["state"]["entity_id"]: StoredState.from_dict(item) + for item in stored_states + if valid_entity_id(item["state"]["entity_id"]) + } + _LOGGER.debug("Created cache with %s", list(data.last_states)) - if stored_states is None: - _LOGGER.debug("Not creating cache - no saved states found") - data.last_states = {} - else: - data.last_states = { - item["state"]["entity_id"]: StoredState.from_dict(item) - for item in stored_states - if valid_entity_id(item["state"]["entity_id"]) - } - _LOGGER.debug("Created cache with %s", list(data.last_states)) + async def hass_start(hass: HomeAssistant) -> None: + """Start the restore state task.""" + data.async_setup_dump() - if hass.state == CoreState.running: - data.async_setup_dump() - else: - hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, data.async_setup_dump - ) + start.async_at_start(hass, hass_start) - return data - - return cast(RestoreStateData, await load_instance(hass)) + return data @classmethod async def async_save_persistent_states(cls, hass: HomeAssistant) -> None: @@ -269,7 +257,9 @@ class RestoreEntity(Entity): # Return None if this entity isn't added to hass yet _LOGGER.warning("Cannot get last state. Entity not added to hass") # type: ignore[unreachable] return None - data = await RestoreStateData.async_get_instance(self.hass) + data = cast( + RestoreStateData, await RestoreStateData.async_get_instance(self.hass) + ) if self.entity_id not in data.last_states: return None return data.last_states[self.entity_id].state diff --git a/homeassistant/helpers/singleton.py b/homeassistant/helpers/singleton.py index a48ea5d64f0..a3cde0b2f27 100644 --- a/homeassistant/helpers/singleton.py +++ b/homeassistant/helpers/singleton.py @@ -26,31 +26,27 @@ def singleton(data_key: str) -> Callable[[FUNC], FUNC]: @bind_hass @functools.wraps(func) def wrapped(hass: HomeAssistant) -> T: - obj: T | None = hass.data.get(data_key) - if obj is None: - obj = hass.data[data_key] = func(hass) - return obj + if data_key not in hass.data: + hass.data[data_key] = func(hass) + return cast(T, hass.data[data_key]) return wrapped @bind_hass @functools.wraps(func) async def async_wrapped(hass: HomeAssistant) -> T: - obj_or_evt = hass.data.get(data_key) - - if not obj_or_evt: + if data_key not in hass.data: evt = hass.data[data_key] = asyncio.Event() - result = await func(hass) - hass.data[data_key] = result evt.set() return cast(T, result) + obj_or_evt = hass.data[data_key] + if isinstance(obj_or_evt, asyncio.Event): - evt = obj_or_evt - await evt.wait() - return cast(T, hass.data.get(data_key)) + await obj_or_evt.wait() + return cast(T, hass.data[data_key]) return cast(T, obj_or_evt) diff --git a/requirements_all.txt b/requirements_all.txt index 53ab6ff1197..1ebca9da385 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -216,7 +216,7 @@ aiolyric==1.0.7 aiomodernforms==0.1.8 # homeassistant.components.yamaha_musiccast -aiomusiccast==0.9.1 +aiomusiccast==0.9.2 # homeassistant.components.keyboard_remote aionotify==0.2.0 @@ -276,7 +276,7 @@ ambee==0.3.0 ambiclimate==0.2.1 # homeassistant.components.amcrest -amcrest==1.8.0 +amcrest==1.8.1 # homeassistant.components.androidtv androidtv[async]==0.0.60 @@ -1629,7 +1629,7 @@ pymonoprice==0.3 pymsteams==0.1.12 # homeassistant.components.myq -pymyq==3.1.3 +pymyq==3.1.4 # homeassistant.components.mysensors pymysensors==0.21.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 859bdbf2d8c..ba9d9bf0128 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -140,7 +140,7 @@ aiolyric==1.0.7 aiomodernforms==0.1.8 # homeassistant.components.yamaha_musiccast -aiomusiccast==0.9.1 +aiomusiccast==0.9.2 # homeassistant.components.notion aionotion==3.0.2 @@ -942,7 +942,7 @@ pymodbus==2.5.3rc1 pymonoprice==0.3 # homeassistant.components.myq -pymyq==3.1.3 +pymyq==3.1.4 # homeassistant.components.mysensors pymysensors==0.21.0 diff --git a/tests/helpers/test_restore_state.py b/tests/helpers/test_restore_state.py index d138a5381da..79719b75326 100644 --- a/tests/helpers/test_restore_state.py +++ b/tests/helpers/test_restore_state.py @@ -32,7 +32,7 @@ async def test_caching_data(hass): await data.store.async_save([state.as_dict() for state in stored_states]) # Emulate a fresh load - hass.data[DATA_RESTORE_STATE_TASK] = None + hass.data.pop(DATA_RESTORE_STATE_TASK) entity = RestoreEntity() entity.hass = hass @@ -59,7 +59,7 @@ async def test_periodic_write(hass): await data.store.async_save([]) # Emulate a fresh load - hass.data[DATA_RESTORE_STATE_TASK] = None + hass.data.pop(DATA_RESTORE_STATE_TASK) entity = RestoreEntity() entity.hass = hass @@ -105,7 +105,7 @@ async def test_save_persistent_states(hass): await data.store.async_save([]) # Emulate a fresh load - hass.data[DATA_RESTORE_STATE_TASK] = None + hass.data.pop(DATA_RESTORE_STATE_TASK) entity = RestoreEntity() entity.hass = hass @@ -170,7 +170,8 @@ async def test_hass_starting(hass): await data.store.async_save([state.as_dict() for state in stored_states]) # Emulate a fresh load - hass.data[DATA_RESTORE_STATE_TASK] = None + hass.state = CoreState.not_running + hass.data.pop(DATA_RESTORE_STATE_TASK) entity = RestoreEntity() entity.hass = hass diff --git a/tests/helpers/test_singleton.py b/tests/helpers/test_singleton.py index c695efd94a8..1d4f496a794 100644 --- a/tests/helpers/test_singleton.py +++ b/tests/helpers/test_singleton.py @@ -12,29 +12,33 @@ def mock_hass(): return Mock(data={}) -async def test_singleton_async(mock_hass): +@pytest.mark.parametrize("result", (object(), {}, [])) +async def test_singleton_async(mock_hass, result): """Test singleton with async function.""" @singleton.singleton("test_key") async def something(hass): - return object() + return result result1 = await something(mock_hass) result2 = await something(mock_hass) + assert result1 is result assert result1 is result2 assert "test_key" in mock_hass.data assert mock_hass.data["test_key"] is result1 -def test_singleton(mock_hass): +@pytest.mark.parametrize("result", (object(), {}, [])) +def test_singleton(mock_hass, result): """Test singleton with function.""" @singleton.singleton("test_key") def something(hass): - return object() + return result result1 = something(mock_hass) result2 = something(mock_hass) + assert result1 is result assert result1 is result2 assert "test_key" in mock_hass.data assert mock_hass.data["test_key"] is result1