Protect state.as_dict from mutation (#65693)
This commit is contained in:
parent
0d3bbfc9a7
commit
5da923c341
14 changed files with 114 additions and 45 deletions
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue