Protect state.as_dict from mutation (#65693)

This commit is contained in:
Paulus Schoutsen 2022-02-04 14:45:25 -08:00 committed by GitHub
parent 0d3bbfc9a7
commit 5da923c341
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 114 additions and 45 deletions

View file

@ -24,7 +24,6 @@ import pathlib
import re
import threading
from time import monotonic
from types import MappingProxyType
from typing import (
TYPE_CHECKING,
Any,
@ -83,6 +82,7 @@ from .util.async_ import (
run_callback_threadsafe,
shutdown_run_callback_threadsafe,
)
from .util.read_only_dict import ReadOnlyDict
from .util.timeout import TimeoutManager
from .util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem
@ -1049,12 +1049,12 @@ class State:
self.entity_id = entity_id.lower()
self.state = state
self.attributes = MappingProxyType(attributes or {})
self.attributes = ReadOnlyDict(attributes or {})
self.last_updated = last_updated or dt_util.utcnow()
self.last_changed = last_changed or self.last_updated
self.context = context or Context()
self.domain, self.object_id = split_entity_id(self.entity_id)
self._as_dict: dict[str, Collection[Any]] | None = None
self._as_dict: ReadOnlyDict[str, Collection[Any]] | None = None
@property
def name(self) -> str:
@ -1063,7 +1063,7 @@ class State:
"_", " "
)
def as_dict(self) -> dict[str, Collection[Any]]:
def as_dict(self) -> ReadOnlyDict[str, Collection[Any]]:
"""Return a dict representation of the State.
Async friendly.
@ -1077,14 +1077,16 @@ class State:
last_updated_isoformat = last_changed_isoformat
else:
last_updated_isoformat = self.last_updated.isoformat()
self._as_dict = {
"entity_id": self.entity_id,
"state": self.state,
"attributes": dict(self.attributes),
"last_changed": last_changed_isoformat,
"last_updated": last_updated_isoformat,
"context": self.context.as_dict(),
}
self._as_dict = ReadOnlyDict(
{
"entity_id": self.entity_id,
"state": self.state,
"attributes": self.attributes,
"last_changed": last_changed_isoformat,
"last_updated": last_updated_isoformat,
"context": ReadOnlyDict(self.context.as_dict()),
}
)
return self._as_dict
@classmethod
@ -1343,7 +1345,7 @@ class StateMachine:
last_changed = None
else:
same_state = old_state.state == new_state and not force_update
same_attr = old_state.attributes == MappingProxyType(attributes)
same_attr = old_state.attributes == attributes
last_changed = old_state.last_changed if same_state else None
if same_state and same_attr:
@ -1404,7 +1406,7 @@ class ServiceCall:
"""Initialize a service call."""
self.domain = domain.lower()
self.service = service.lower()
self.data = MappingProxyType(data or {})
self.data = ReadOnlyDict(data or {})
self.context = context or Context()
def __repr__(self) -> str: