Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged

2021.9.6 #56116

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
2 changes: 1 addition & 1 deletion homeassistant/components/amcrest/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/myq/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ def state_attributes(self) -> dict[str, Any] | None:
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 "
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/sensor/recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/yamaha_musiccast/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

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)
Expand Down
78 changes: 34 additions & 44 deletions homeassistant/helpers/restore_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,10 @@
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
Expand Down Expand Up @@ -63,42 +57,36 @@ def from_dict(cls, json_dict: dict) -> 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)

try:
stored_states = await data.store.async_load()
except HomeAssistantError as exc:
_LOGGER.error("Error loading last states", exc_info=exc)
stored_states = None

@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

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 hass.state == CoreState.running:
data.async_setup_dump()
else:
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, data.async_setup_dump
)

return data

return cast(RestoreStateData, await load_instance(hass))
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()

start.async_at_start(hass, hass_start)

return data

@classmethod
async def async_save_persistent_states(cls, hass: HomeAssistant) -> None:
Expand Down Expand Up @@ -269,7 +257,9 @@ async def async_get_last_state(self) -> State | None:
# 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
20 changes: 8 additions & 12 deletions homeassistant/helpers/singleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,27 @@ def wrapper(func: 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)

Expand Down
6 changes: 3 additions & 3 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions tests/helpers/test_restore_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions tests/helpers/test_singleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -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