Prefilter more logbook events in sql (#36958)

* Prefilter more logbook events in sql

Prefilter sensor events in _keep_event before humanify

Cache static attribute lookup

Reduces logbook execution time by ~35%

* fix mocking in benchmark

* Update tests for logbook users
This commit is contained in:
J. Nick Koston 2020-06-21 12:50:58 -05:00 committed by GitHub
parent 29f128eaad
commit 59e43ab6e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 275 additions and 108 deletions

View File

@ -18,11 +18,13 @@ from homeassistant.components.recorder.util import (
session_scope, session_scope,
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_DOMAIN, ATTR_DOMAIN,
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_FRIENDLY_NAME, ATTR_FRIENDLY_NAME,
ATTR_HIDDEN, ATTR_HIDDEN,
ATTR_NAME, ATTR_NAME,
ATTR_UNIT_OF_MEASUREMENT,
CONF_EXCLUDE, CONF_EXCLUDE,
CONF_INCLUDE, CONF_INCLUDE,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_START,
@ -52,6 +54,8 @@ DOMAIN = "logbook"
GROUP_BY_MINUTES = 15 GROUP_BY_MINUTES = 15
EMPTY_JSON_OBJECT = "{}"
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: vol.Schema( DOMAIN: vol.Schema(
@ -194,7 +198,7 @@ class LogbookView(HomeAssistantView):
return await hass.async_add_job(json_events) return await hass.async_add_job(json_events)
def humanify(hass, events, prev_states=None): def humanify(hass, events, entity_attr_cache, prev_states=None):
"""Generate a converted list of events into Entry objects. """Generate a converted list of events into Entry objects.
Will try to group events if possible: Will try to group events if possible:
@ -257,24 +261,22 @@ def humanify(hass, events, prev_states=None):
prev_states[entity_id] = event.state prev_states[entity_id] = event.state
domain = event.domain domain = event.domain
if domain in CONTINUOUS_DOMAINS: if (
domain in CONTINUOUS_DOMAINS
and event != last_sensor_event[entity_id]
):
# Skip all but the last sensor state # Skip all but the last sensor state
if event != last_sensor_event[entity_id]: continue
continue
# Don't show continuous sensor value changes in the logbook name = entity_attr_cache.get(
if _get_attribute(hass, entity_id, event, "unit_of_measurement"): entity_id, ATTR_FRIENDLY_NAME, event
continue
name = _get_attribute(
hass, entity_id, event, ATTR_FRIENDLY_NAME
) or split_entity_id(entity_id)[1].replace("_", " ") ) or split_entity_id(entity_id)[1].replace("_", " ")
yield { yield {
"when": event.time_fired, "when": event.time_fired,
"name": name, "name": name,
"message": _entry_message_from_event( "message": _entry_message_from_event(
hass, entity_id, domain, event hass, entity_id, domain, event, entity_attr_cache
), ),
"domain": domain, "domain": domain,
"entity_id": entity_id, "entity_id": entity_id,
@ -375,12 +377,13 @@ def _generate_filter_from_config(config):
def _get_events(hass, config, start_day, end_day, entity_id=None): def _get_events(hass, config, start_day, end_day, entity_id=None):
"""Get events for a period of time.""" """Get events for a period of time."""
entities_filter = _generate_filter_from_config(config) entities_filter = _generate_filter_from_config(config)
entity_attr_cache = EntityAttributeCache(hass)
def yield_events(query): def yield_events(query):
"""Yield Events that are not filtered away.""" """Yield Events that are not filtered away."""
for row in query.yield_per(1000): for row in query.yield_per(1000):
event = LazyEventPartialState(row) event = LazyEventPartialState(row)
if _keep_event(hass, event, entities_filter): if _keep_event(hass, event, entities_filter, entity_attr_cache):
yield event yield event
with session_scope(hass=hass) as session: with session_scope(hass=hass) as session:
@ -409,6 +412,24 @@ def _get_events(hass, config, start_day, end_day, entity_id=None):
.order_by(Events.time_fired) .order_by(Events.time_fired)
.outerjoin(States, (Events.event_id == States.event_id)) .outerjoin(States, (Events.event_id == States.event_id))
.outerjoin(old_state, (States.old_state_id == old_state.state_id)) .outerjoin(old_state, (States.old_state_id == old_state.state_id))
# The below filter, removes state change events that do not have
# and old_state, new_state, or the old and
# new state are the same for v8 schema or later.
#
# If the events/states were stored before v8 schema, we relay on the
# prev_states dict to remove them.
#
# When all data is schema v8 or later, the check for EMPTY_JSON_OBJECT
# can be removed.
.filter(
(Events.event_type != EVENT_STATE_CHANGED)
| (Events.event_data != EMPTY_JSON_OBJECT)
| (
(States.state_id.isnot(None))
& (old_state.state_id.isnot(None))
& (States.state != old_state.state)
)
)
.filter( .filter(
Events.event_type.in_(ALL_EVENT_TYPES + list(hass.data.get(DOMAIN, {}))) Events.event_type.in_(ALL_EVENT_TYPES + list(hass.data.get(DOMAIN, {})))
) )
@ -429,18 +450,12 @@ def _get_events(hass, config, start_day, end_day, entity_id=None):
| (States.state_id.is_(None)) | (States.state_id.is_(None))
) )
# When all data is schema v8 or later, prev_states can be removed
prev_states = {} prev_states = {}
return list(humanify(hass, yield_events(query), prev_states)) return list(humanify(hass, yield_events(query), entity_attr_cache, prev_states))
def _get_attribute(hass, entity_id, event, attribute): def _keep_event(hass, event, entities_filter, entity_attr_cache):
current_state = hass.states.get(entity_id)
if not current_state:
return event.attributes.get(attribute)
return current_state.attributes.get(attribute, None)
def _keep_event(hass, event, entities_filter):
if event.event_type == EVENT_STATE_CHANGED: if event.event_type == EVENT_STATE_CHANGED:
entity_id = event.entity_id entity_id = event.entity_id
@ -456,6 +471,11 @@ def _keep_event(hass, event, entities_filter):
if event.hidden: if event.hidden:
return False return False
if event.domain in CONTINUOUS_DOMAINS and entity_attr_cache.get(
entity_id, ATTR_UNIT_OF_MEASUREMENT, event
):
# Don't show continuous sensor value changes in the logbook
return False
elif event.event_type == EVENT_LOGBOOK_ENTRY: elif event.event_type == EVENT_LOGBOOK_ENTRY:
event_data = event.data event_data = event.data
domain = event_data.get(ATTR_DOMAIN) domain = event_data.get(ATTR_DOMAIN)
@ -478,7 +498,7 @@ def _keep_event(hass, event, entities_filter):
return not entity_id or entities_filter(entity_id) return not entity_id or entities_filter(entity_id)
def _entry_message_from_event(hass, entity_id, domain, event): def _entry_message_from_event(hass, entity_id, domain, event, entity_attr_cache):
"""Convert a state to a message for the logbook.""" """Convert a state to a message for the logbook."""
# We pass domain in so we don't have to split entity_id again # We pass domain in so we don't have to split entity_id again
state_state = event.state state_state = event.state
@ -494,7 +514,7 @@ def _entry_message_from_event(hass, entity_id, domain, event):
return "has set" return "has set"
if domain == "binary_sensor": if domain == "binary_sensor":
device_class = _get_attribute(hass, entity_id, event, "device_class") device_class = entity_attr_cache.get(entity_id, ATTR_DEVICE_CLASS, event)
if device_class == "battery": if device_class == "battery":
if state_state == STATE_ON: if state_state == STATE_ON:
return "is low" return "is low"
@ -600,7 +620,10 @@ class LazyEventPartialState:
def attributes(self): def attributes(self):
"""State attributes.""" """State attributes."""
if not self._attributes: if not self._attributes:
if self._row.attributes is None or self._row.attributes == "{}": if (
self._row.attributes is None
or self._row.attributes == EMPTY_JSON_OBJECT
):
self._attributes = {} self._attributes = {}
else: else:
self._attributes = json.loads(self._row.attributes) self._attributes = json.loads(self._row.attributes)
@ -611,7 +634,7 @@ class LazyEventPartialState:
"""Event data.""" """Event data."""
if not self._event_data: if not self._event_data:
if self._row.event_data == "{}": if self._row.event_data == EMPTY_JSON_OBJECT:
self._event_data = {} self._event_data = {}
else: else:
self._event_data = json.loads(self._row.event_data) self._event_data = json.loads(self._row.event_data)
@ -634,9 +657,15 @@ class LazyEventPartialState:
@property @property
def has_old_and_new_state(self): def has_old_and_new_state(self):
"""Check the json data to see if new_state and old_state is present without decoding.""" """Check the json data to see if new_state and old_state is present without decoding."""
if self._row.event_data == "{}":
# Delete this check once all states are saved in the v8 schema
# format or later (they have the old_state_id column).
# New events in v8 schema format
if self._row.event_data == EMPTY_JSON_OBJECT:
return self._row.state_id is not None and self._row.old_state_id is not None return self._row.state_id is not None and self._row.old_state_id is not None
# Old events not in v8 schema format
return ( return (
'"old_state": {' in self._row.event_data '"old_state": {' in self._row.event_data
and '"new_state": {' in self._row.event_data and '"new_state": {' in self._row.event_data
@ -648,3 +677,38 @@ class LazyEventPartialState:
if '"hidden":' in self._row.attributes: if '"hidden":' in self._row.attributes:
return self.attributes.get(ATTR_HIDDEN, False) return self.attributes.get(ATTR_HIDDEN, False)
return False return False
class EntityAttributeCache:
"""A cache to lookup static entity_id attribute.
This class should not be used to lookup attributes
that are expected to change state.
"""
def __init__(self, hass):
"""Init the cache."""
self._hass = hass
self._cache = {}
def get(self, entity_id, attribute, event):
"""Lookup an attribute for an entity or get it from the cache."""
if entity_id in self._cache:
if attribute in self._cache[entity_id]:
return self._cache[entity_id][attribute]
else:
self._cache[entity_id] = {}
current_state = self._hass.states.get(entity_id)
if current_state:
# Try the current state as its faster than decoding the
# attributes
self._cache[entity_id][attribute] = current_state.attributes.get(
attribute, None
)
else:
# If the entity has been removed, decode the attributes
# instead
self._cache[entity_id][attribute] = event.attributes.get(attribute)
return self._cache[entity_id][attribute]

View File

@ -1,8 +1,10 @@
"""Script to run benchmarks.""" """Script to run benchmarks."""
import argparse import argparse
import asyncio import asyncio
import collections
from contextlib import suppress from contextlib import suppress
from datetime import datetime from datetime import datetime
import json
import logging import logging
from timeit import default_timer as timer from timeit import default_timer as timer
from typing import Callable, Dict, TypeVar from typing import Callable, Dict, TypeVar
@ -10,6 +12,7 @@ from typing import Callable, Dict, TypeVar
from homeassistant import core from homeassistant import core
from homeassistant.components.websocket_api.const import JSON_DUMP from homeassistant.components.websocket_api.const import JSON_DUMP
from homeassistant.const import ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED from homeassistant.const import ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED
from homeassistant.helpers.json import JSONEncoder
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
# mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs
@ -169,21 +172,22 @@ async def _logbook_filtering(hass, last_changed, last_updated):
"last_changed": last_changed, "last_changed": last_changed,
} }
event = core.Event( event = _create_state_changed_event_from_old_new(
EVENT_STATE_CHANGED, entity_id, dt_util.utcnow(), old_state, new_state
{"entity_id": entity_id, "old_state": old_state, "new_state": new_state},
) )
entity_attr_cache = logbook.EntityAttributeCache(hass)
def yield_events(event): def yield_events(event):
# pylint: disable=protected-access # pylint: disable=protected-access
entities_filter = logbook._generate_filter_from_config({}) entities_filter = logbook._generate_filter_from_config({})
for _ in range(10 ** 5): for _ in range(10 ** 5):
if logbook._keep_event(hass, event, entities_filter): if logbook._keep_event(hass, event, entities_filter, entity_attr_cache):
yield event yield event
start = timer() start = timer()
list(logbook.humanify(hass, yield_events(event))) list(logbook.humanify(hass, yield_events(event), entity_attr_cache))
return timer() - start return timer() - start
@ -208,3 +212,48 @@ async def json_serialize_states(hass):
start = timer() start = timer()
JSON_DUMP(states) JSON_DUMP(states)
return timer() - start return timer() - start
def _create_state_changed_event_from_old_new(
entity_id, event_time_fired, old_state, new_state
):
"""Create a state changed event from a old and new state."""
attributes = {}
if new_state is not None:
attributes = new_state.get("attributes")
attributes_json = json.dumps(attributes, cls=JSONEncoder)
if attributes_json == "null":
attributes_json = "{}"
row = collections.namedtuple(
"Row",
[
"event_type"
"event_data"
"time_fired"
"context_id"
"context_user_id"
"state"
"entity_id"
"domain"
"attributes"
"state_id",
"old_state_id",
],
)
row.event_type = EVENT_STATE_CHANGED
row.event_data = "{}"
row.attributes = attributes_json
row.time_fired = event_time_fired
row.state = new_state and new_state.get("state")
row.entity_id = entity_id
row.domain = entity_id and core.split_entity_id(entity_id)[0]
row.context_id = None
row.context_user_id = None
row.old_state_id = old_state and 1
row.state_id = new_state and 1
# pylint: disable=import-outside-toplevel
from homeassistant.components import logbook
return logbook.LazyEventPartialState(row)

View File

@ -10,6 +10,7 @@ async def test_humanify_alexa_event(hass):
"""Test humanifying Alexa event.""" """Test humanifying Alexa event."""
await async_setup_component(hass, "alexa", {}) await async_setup_component(hass, "alexa", {})
hass.states.async_set("light.kitchen", "on", {"friendly_name": "Kitchen Light"}) hass.states.async_set("light.kitchen", "on", {"friendly_name": "Kitchen Light"})
entity_attr_cache = logbook.EntityAttributeCache(hass)
results = list( results = list(
logbook.humanify( logbook.humanify(
@ -40,6 +41,7 @@ async def test_humanify_alexa_event(hass):
}, },
), ),
], ],
entity_attr_cache,
) )
) )

View File

@ -1040,6 +1040,7 @@ async def test_extraction_functions(hass):
async def test_logbook_humanify_automation_triggered_event(hass): async def test_logbook_humanify_automation_triggered_event(hass):
"""Test humanifying Automation Trigger event.""" """Test humanifying Automation Trigger event."""
await async_setup_component(hass, automation.DOMAIN, {}) await async_setup_component(hass, automation.DOMAIN, {})
entity_attr_cache = logbook.EntityAttributeCache(hass)
event1, event2 = list( event1, event2 = list(
logbook.humanify( logbook.humanify(
@ -1054,6 +1055,7 @@ async def test_logbook_humanify_automation_triggered_event(hass):
{ATTR_ENTITY_ID: "automation.bye", ATTR_NAME: "Bye Automation"}, {ATTR_ENTITY_ID: "automation.bye", ATTR_NAME: "Bye Automation"},
), ),
], ],
entity_attr_cache,
) )
) )

View File

@ -17,6 +17,7 @@ async def test_humanify_homekit_changed_event(hass, hk_driver):
"""Test humanifying HomeKit changed event.""" """Test humanifying HomeKit changed event."""
with patch("homeassistant.components.homekit.HomeKit"): with patch("homeassistant.components.homekit.HomeKit"):
assert await async_setup_component(hass, "homekit", {"homekit": {}}) assert await async_setup_component(hass, "homekit", {"homekit": {}})
entity_attr_cache = logbook.EntityAttributeCache(hass)
event1, event2 = list( event1, event2 = list(
logbook.humanify( logbook.humanify(
@ -40,6 +41,7 @@ async def test_humanify_homekit_changed_event(hass, hk_driver):
}, },
), ),
], ],
entity_attr_cache,
) )
) )

View File

@ -121,12 +121,15 @@ class TestComponentLogbook(unittest.TestCase):
pointA = dt_util.utcnow().replace(minute=2) pointA = dt_util.utcnow().replace(minute=2)
pointB = pointA.replace(minute=5) pointB = pointA.replace(minute=5)
pointC = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) pointC = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
eventA = self.create_state_changed_event(pointA, entity_id, 10) eventA = self.create_state_changed_event(pointA, entity_id, 10)
eventB = self.create_state_changed_event(pointB, entity_id, 20) eventB = self.create_state_changed_event(pointB, entity_id, 20)
eventC = self.create_state_changed_event(pointC, entity_id, 30) eventC = self.create_state_changed_event(pointC, entity_id, 30)
entries = list(logbook.humanify(self.hass, (eventA, eventB, eventC))) entries = list(
logbook.humanify(self.hass, (eventA, eventB, eventC), entity_attr_cache)
)
assert len(entries) == 2 assert len(entries) == 2
self.assert_entry( self.assert_entry(
@ -141,12 +144,15 @@ class TestComponentLogbook(unittest.TestCase):
"""Test remove continuous sensor events from logbook.""" """Test remove continuous sensor events from logbook."""
entity_id = "sensor.bla" entity_id = "sensor.bla"
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
attributes = {"unit_of_measurement": "foo"} attributes = {"unit_of_measurement": "foo"}
eventA = self.create_state_changed_event(pointA, entity_id, 10, attributes) eventA = self.create_state_changed_event(pointA, entity_id, 10, attributes)
entries = list(logbook.humanify(self.hass, (eventA,))) entities_filter = logbook._generate_filter_from_config({})
assert (
assert len(entries) == 0 logbook._keep_event(self.hass, eventA, entities_filter, entity_attr_cache)
is False
)
def test_exclude_new_entities(self): def test_exclude_new_entities(self):
"""Test if events are excluded on first update.""" """Test if events are excluded on first update."""
@ -154,6 +160,7 @@ class TestComponentLogbook(unittest.TestCase):
entity_id2 = "sensor.blu" entity_id2 = "sensor.blu"
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
state_on = ha.State( state_on = ha.State(
entity_id, "on", {"brightness": 200}, pointA, pointA entity_id, "on", {"brightness": 200}, pointA, pointA
@ -172,9 +179,9 @@ class TestComponentLogbook(unittest.TestCase):
eventA, eventA,
eventB, eventB,
) )
if logbook._keep_event(self.hass, e, entities_filter) if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache)
] ]
entries = list(logbook.humanify(self.hass, events)) entries = list(logbook.humanify(self.hass, events, entity_attr_cache))
assert len(entries) == 2 assert len(entries) == 2
self.assert_entry( self.assert_entry(
@ -190,6 +197,7 @@ class TestComponentLogbook(unittest.TestCase):
entity_id2 = "sensor.blu" entity_id2 = "sensor.blu"
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
state_on = ha.State( state_on = ha.State(
entity_id, "on", {"brightness": 200}, pointA, pointA entity_id, "on", {"brightness": 200}, pointA, pointA
@ -207,9 +215,9 @@ class TestComponentLogbook(unittest.TestCase):
eventA, eventA,
eventB, eventB,
) )
if logbook._keep_event(self.hass, e, entities_filter) if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache)
] ]
entries = list(logbook.humanify(self.hass, events)) entries = list(logbook.humanify(self.hass, events, entity_attr_cache))
assert len(entries) == 2 assert len(entries) == 2
self.assert_entry( self.assert_entry(
@ -225,6 +233,7 @@ class TestComponentLogbook(unittest.TestCase):
entity_id2 = "sensor.blu" entity_id2 = "sensor.blu"
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, entity_id, 10, {ATTR_HIDDEN: "true"} pointA, entity_id, 10, {ATTR_HIDDEN: "true"}
@ -239,9 +248,9 @@ class TestComponentLogbook(unittest.TestCase):
eventA, eventA,
eventB, eventB,
) )
if logbook._keep_event(self.hass, e, entities_filter) if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache)
] ]
entries = list(logbook.humanify(self.hass, events)) entries = list(logbook.humanify(self.hass, events, entity_attr_cache))
assert len(entries) == 2 assert len(entries) == 2
self.assert_entry( self.assert_entry(
@ -257,6 +266,7 @@ class TestComponentLogbook(unittest.TestCase):
entity_id2 = "sensor.blu" entity_id2 = "sensor.blu"
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
eventA = self.create_state_changed_event(pointA, entity_id, 10) eventA = self.create_state_changed_event(pointA, entity_id, 10)
eventB = self.create_state_changed_event(pointB, entity_id2, 20) eventB = self.create_state_changed_event(pointB, entity_id2, 20)
@ -277,9 +287,9 @@ class TestComponentLogbook(unittest.TestCase):
eventA, eventA,
eventB, eventB,
) )
if logbook._keep_event(self.hass, e, entities_filter) if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache)
] ]
entries = list(logbook.humanify(self.hass, events)) entries = list(logbook.humanify(self.hass, events, entity_attr_cache))
assert len(entries) == 2 assert len(entries) == 2
self.assert_entry( self.assert_entry(
@ -295,6 +305,7 @@ class TestComponentLogbook(unittest.TestCase):
entity_id2 = "sensor.blu" entity_id2 = "sensor.blu"
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
eventA = self.create_state_changed_event(pointA, entity_id, 10) eventA = self.create_state_changed_event(pointA, entity_id, 10)
eventB = self.create_state_changed_event(pointB, entity_id2, 20) eventB = self.create_state_changed_event(pointB, entity_id2, 20)
@ -316,9 +327,9 @@ class TestComponentLogbook(unittest.TestCase):
eventA, eventA,
eventB, eventB,
) )
if logbook._keep_event(self.hass, e, entities_filter) if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache)
] ]
entries = list(logbook.humanify(self.hass, events)) entries = list(logbook.humanify(self.hass, events, entity_attr_cache))
assert len(entries) == 2 assert len(entries) == 2
self.assert_entry( self.assert_entry(
@ -334,6 +345,7 @@ class TestComponentLogbook(unittest.TestCase):
entity_id2 = "sensor.blu" entity_id2 = "sensor.blu"
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
eventA = self.create_state_changed_event(pointA, entity_id, 10) eventA = self.create_state_changed_event(pointA, entity_id, 10)
eventB = self.create_state_changed_event(pointB, entity_id2, 20) eventB = self.create_state_changed_event(pointB, entity_id2, 20)
@ -354,9 +366,9 @@ class TestComponentLogbook(unittest.TestCase):
eventA, eventA,
eventB, eventB,
) )
if logbook._keep_event(self.hass, e, entities_filter) if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache)
] ]
entries = list(logbook.humanify(self.hass, events)) entries = list(logbook.humanify(self.hass, events, entity_attr_cache))
assert len(entries) == 2 assert len(entries) == 2
self.assert_entry( self.assert_entry(
@ -373,6 +385,7 @@ class TestComponentLogbook(unittest.TestCase):
entity_id2 = "sensor.blu" entity_id2 = "sensor.blu"
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
event_alexa = MockLazyEventPartialState( event_alexa = MockLazyEventPartialState(
EVENT_ALEXA_SMART_HOME, EVENT_ALEXA_SMART_HOME,
@ -399,9 +412,9 @@ class TestComponentLogbook(unittest.TestCase):
eventA, eventA,
eventB, eventB,
) )
if logbook._keep_event(self.hass, e, entities_filter) if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache)
] ]
entries = list(logbook.humanify(self.hass, events)) entries = list(logbook.humanify(self.hass, events, entity_attr_cache))
assert len(entries) == 3 assert len(entries) == 3
self.assert_entry( self.assert_entry(
@ -419,6 +432,7 @@ class TestComponentLogbook(unittest.TestCase):
entity_id3 = "sensor.bli" entity_id3 = "sensor.bli"
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES)
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
eventA1 = self.create_state_changed_event(pointA, entity_id, 10) eventA1 = self.create_state_changed_event(pointA, entity_id, 10)
eventA2 = self.create_state_changed_event(pointA, entity_id2, 10) eventA2 = self.create_state_changed_event(pointA, entity_id2, 10)
@ -452,9 +466,9 @@ class TestComponentLogbook(unittest.TestCase):
eventB1, eventB1,
eventB2, eventB2,
) )
if logbook._keep_event(self.hass, e, entities_filter) if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache)
] ]
entries = list(logbook.humanify(self.hass, events)) entries = list(logbook.humanify(self.hass, events, entity_attr_cache))
assert len(entries) == 5 assert len(entries) == 5
self.assert_entry( self.assert_entry(
@ -478,6 +492,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
pointB = pointA + timedelta(minutes=1) pointB = pointA + timedelta(minutes=1)
pointC = pointB + timedelta(minutes=1) pointC = pointB + timedelta(minutes=1)
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
state_off = ha.State("light.kitchen", "off", {}, pointA, pointA).as_dict() state_off = ha.State("light.kitchen", "off", {}, pointA, pointA).as_dict()
state_100 = ha.State( state_100 = ha.State(
@ -498,9 +513,9 @@ class TestComponentLogbook(unittest.TestCase):
events = [ events = [
e e
for e in (eventA, eventB) for e in (eventA, eventB)
if logbook._keep_event(self.hass, e, entities_filter) if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache)
] ]
entries = list(logbook.humanify(self.hass, events)) entries = list(logbook.humanify(self.hass, events, entity_attr_cache))
assert len(entries) == 1 assert len(entries) == 1
self.assert_entry( self.assert_entry(
@ -512,6 +527,7 @@ class TestComponentLogbook(unittest.TestCase):
Events that are occurring in the same minute. Events that are occurring in the same minute.
""" """
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
entries = list( entries = list(
logbook.humanify( logbook.humanify(
self.hass, self.hass,
@ -519,7 +535,8 @@ class TestComponentLogbook(unittest.TestCase):
MockLazyEventPartialState(EVENT_HOMEASSISTANT_STOP), MockLazyEventPartialState(EVENT_HOMEASSISTANT_STOP),
MockLazyEventPartialState(EVENT_HOMEASSISTANT_START), MockLazyEventPartialState(EVENT_HOMEASSISTANT_START),
), ),
) entity_attr_cache,
),
) )
assert len(entries) == 1 assert len(entries) == 1
@ -531,6 +548,7 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if HA start is not filtered or converted into a restart.""" """Test if HA start is not filtered or converted into a restart."""
entity_id = "switch.bla" entity_id = "switch.bla"
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
entries = list( entries = list(
logbook.humanify( logbook.humanify(
@ -539,6 +557,7 @@ class TestComponentLogbook(unittest.TestCase):
MockLazyEventPartialState(EVENT_HOMEASSISTANT_START), MockLazyEventPartialState(EVENT_HOMEASSISTANT_START),
self.create_state_changed_event(pointA, entity_id, 10), self.create_state_changed_event(pointA, entity_id, 10),
), ),
entity_attr_cache,
) )
) )
@ -556,76 +575,79 @@ class TestComponentLogbook(unittest.TestCase):
Especially test if the special handling for turn on/off events is done. Especially test if the special handling for turn on/off events is done.
""" """
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a device state change # message for a device state change
eventA = self.create_state_changed_event(pointA, "switch.bla", 10) eventA = self.create_state_changed_event(pointA, "switch.bla", 10)
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "changed to 10" assert message == "changed to 10"
# message for a switch turned on # message for a switch turned on
eventA = self.create_state_changed_event(pointA, "switch.bla", STATE_ON) eventA = self.create_state_changed_event(pointA, "switch.bla", STATE_ON)
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "turned on" assert message == "turned on"
# message for a switch turned off # message for a switch turned off
eventA = self.create_state_changed_event(pointA, "switch.bla", STATE_OFF) eventA = self.create_state_changed_event(pointA, "switch.bla", STATE_OFF)
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "turned off" assert message == "turned off"
def test_entry_message_from_event_device_tracker(self): def test_entry_message_from_event_device_tracker(self):
"""Test if logbook message is correctly created for device tracker.""" """Test if logbook message is correctly created for device tracker."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a device tracker "not home" state # message for a device tracker "not home" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "device_tracker.john", STATE_NOT_HOME pointA, "device_tracker.john", STATE_NOT_HOME
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is away" assert message == "is away"
# message for a device tracker "home" state # message for a device tracker "home" state
eventA = self.create_state_changed_event(pointA, "device_tracker.john", "work") eventA = self.create_state_changed_event(pointA, "device_tracker.john", "work")
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is at work" assert message == "is at work"
def test_entry_message_from_event_person(self): def test_entry_message_from_event_person(self):
"""Test if logbook message is correctly created for a person.""" """Test if logbook message is correctly created for a person."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a device tracker "not home" state # message for a device tracker "not home" state
eventA = self.create_state_changed_event(pointA, "person.john", STATE_NOT_HOME) eventA = self.create_state_changed_event(pointA, "person.john", STATE_NOT_HOME)
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is away" assert message == "is away"
# message for a device tracker "home" state # message for a device tracker "home" state
eventA = self.create_state_changed_event(pointA, "person.john", "work") eventA = self.create_state_changed_event(pointA, "person.john", "work")
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is at work" assert message == "is at work"
def test_entry_message_from_event_sun(self): def test_entry_message_from_event_sun(self):
"""Test if logbook message is correctly created for sun.""" """Test if logbook message is correctly created for sun."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a sun rise # message for a sun rise
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "sun.sun", sun.STATE_ABOVE_HORIZON pointA, "sun.sun", sun.STATE_ABOVE_HORIZON
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "has risen" assert message == "has risen"
@ -634,7 +656,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "sun.sun", sun.STATE_BELOW_HORIZON pointA, "sun.sun", sun.STATE_BELOW_HORIZON
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "has set" assert message == "has set"
@ -642,13 +664,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "battery"} attributes = {"device_class": "battery"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor battery "low" state # message for a binary_sensor battery "low" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.battery", STATE_ON, attributes pointA, "binary_sensor.battery", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is low" assert message == "is low"
@ -657,7 +680,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.battery", STATE_OFF, attributes pointA, "binary_sensor.battery", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is normal" assert message == "is normal"
@ -665,13 +688,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "connectivity"} attributes = {"device_class": "connectivity"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor connectivity "connected" state # message for a binary_sensor connectivity "connected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.connectivity", STATE_ON, attributes pointA, "binary_sensor.connectivity", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is connected" assert message == "is connected"
@ -680,7 +704,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.connectivity", STATE_OFF, attributes pointA, "binary_sensor.connectivity", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is disconnected" assert message == "is disconnected"
@ -688,13 +712,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "door"} attributes = {"device_class": "door"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor door "open" state # message for a binary_sensor door "open" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.door", STATE_ON, attributes pointA, "binary_sensor.door", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is opened" assert message == "is opened"
@ -703,7 +728,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.door", STATE_OFF, attributes pointA, "binary_sensor.door", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is closed" assert message == "is closed"
@ -711,13 +736,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "garage_door"} attributes = {"device_class": "garage_door"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor garage_door "open" state # message for a binary_sensor garage_door "open" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.garage_door", STATE_ON, attributes pointA, "binary_sensor.garage_door", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is opened" assert message == "is opened"
@ -726,7 +752,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.garage_door", STATE_OFF, attributes pointA, "binary_sensor.garage_door", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is closed" assert message == "is closed"
@ -734,13 +760,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "opening"} attributes = {"device_class": "opening"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor opening "open" state # message for a binary_sensor opening "open" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.opening", STATE_ON, attributes pointA, "binary_sensor.opening", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is opened" assert message == "is opened"
@ -749,7 +776,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.opening", STATE_OFF, attributes pointA, "binary_sensor.opening", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is closed" assert message == "is closed"
@ -757,13 +784,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "window"} attributes = {"device_class": "window"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor window "open" state # message for a binary_sensor window "open" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.window", STATE_ON, attributes pointA, "binary_sensor.window", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is opened" assert message == "is opened"
@ -772,7 +800,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.window", STATE_OFF, attributes pointA, "binary_sensor.window", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is closed" assert message == "is closed"
@ -780,13 +808,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "lock"} attributes = {"device_class": "lock"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor lock "unlocked" state # message for a binary_sensor lock "unlocked" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.lock", STATE_ON, attributes pointA, "binary_sensor.lock", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is unlocked" assert message == "is unlocked"
@ -795,7 +824,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.lock", STATE_OFF, attributes pointA, "binary_sensor.lock", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is locked" assert message == "is locked"
@ -803,13 +832,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "plug"} attributes = {"device_class": "plug"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor plug "unpluged" state # message for a binary_sensor plug "unpluged" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.plug", STATE_ON, attributes pointA, "binary_sensor.plug", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is plugged in" assert message == "is plugged in"
@ -818,7 +848,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.plug", STATE_OFF, attributes pointA, "binary_sensor.plug", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is unplugged" assert message == "is unplugged"
@ -826,13 +856,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "presence"} attributes = {"device_class": "presence"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor presence "home" state # message for a binary_sensor presence "home" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.presence", STATE_ON, attributes pointA, "binary_sensor.presence", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is at home" assert message == "is at home"
@ -841,7 +872,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.presence", STATE_OFF, attributes pointA, "binary_sensor.presence", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is away" assert message == "is away"
@ -849,13 +880,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "safety"} attributes = {"device_class": "safety"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor safety "unsafe" state # message for a binary_sensor safety "unsafe" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.safety", STATE_ON, attributes pointA, "binary_sensor.safety", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is unsafe" assert message == "is unsafe"
@ -864,7 +896,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.safety", STATE_OFF, attributes pointA, "binary_sensor.safety", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "is safe" assert message == "is safe"
@ -872,13 +904,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "cold"} attributes = {"device_class": "cold"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor cold "detected" state # message for a binary_sensor cold "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.cold", STATE_ON, attributes pointA, "binary_sensor.cold", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected cold" assert message == "detected cold"
@ -887,7 +920,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.cold", STATE_OFF, attributes pointA, "binary_sensor.cold", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no cold detected)" assert message == "cleared (no cold detected)"
@ -895,13 +928,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "gas"} attributes = {"device_class": "gas"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor gas "detected" state # message for a binary_sensor gas "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.gas", STATE_ON, attributes pointA, "binary_sensor.gas", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected gas" assert message == "detected gas"
@ -910,7 +944,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.gas", STATE_OFF, attributes pointA, "binary_sensor.gas", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no gas detected)" assert message == "cleared (no gas detected)"
@ -918,13 +952,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "heat"} attributes = {"device_class": "heat"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor heat "detected" state # message for a binary_sensor heat "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.heat", STATE_ON, attributes pointA, "binary_sensor.heat", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected heat" assert message == "detected heat"
@ -933,7 +968,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.heat", STATE_OFF, attributes pointA, "binary_sensor.heat", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no heat detected)" assert message == "cleared (no heat detected)"
@ -941,13 +976,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "light"} attributes = {"device_class": "light"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor light "detected" state # message for a binary_sensor light "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.light", STATE_ON, attributes pointA, "binary_sensor.light", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected light" assert message == "detected light"
@ -956,7 +992,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.light", STATE_OFF, attributes pointA, "binary_sensor.light", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no light detected)" assert message == "cleared (no light detected)"
@ -964,13 +1000,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "moisture"} attributes = {"device_class": "moisture"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor moisture "detected" state # message for a binary_sensor moisture "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.moisture", STATE_ON, attributes pointA, "binary_sensor.moisture", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected moisture" assert message == "detected moisture"
@ -979,7 +1016,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.moisture", STATE_OFF, attributes pointA, "binary_sensor.moisture", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no moisture detected)" assert message == "cleared (no moisture detected)"
@ -987,13 +1024,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "motion"} attributes = {"device_class": "motion"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor motion "detected" state # message for a binary_sensor motion "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.motion", STATE_ON, attributes pointA, "binary_sensor.motion", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected motion" assert message == "detected motion"
@ -1002,7 +1040,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.motion", STATE_OFF, attributes pointA, "binary_sensor.motion", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no motion detected)" assert message == "cleared (no motion detected)"
@ -1010,13 +1048,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "occupancy"} attributes = {"device_class": "occupancy"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor occupancy "detected" state # message for a binary_sensor occupancy "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.occupancy", STATE_ON, attributes pointA, "binary_sensor.occupancy", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected occupancy" assert message == "detected occupancy"
@ -1025,7 +1064,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.occupancy", STATE_OFF, attributes pointA, "binary_sensor.occupancy", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no occupancy detected)" assert message == "cleared (no occupancy detected)"
@ -1033,13 +1072,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "power"} attributes = {"device_class": "power"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor power "detected" state # message for a binary_sensor power "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.power", STATE_ON, attributes pointA, "binary_sensor.power", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected power" assert message == "detected power"
@ -1048,7 +1088,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.power", STATE_OFF, attributes pointA, "binary_sensor.power", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no power detected)" assert message == "cleared (no power detected)"
@ -1056,13 +1096,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "problem"} attributes = {"device_class": "problem"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor problem "detected" state # message for a binary_sensor problem "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.problem", STATE_ON, attributes pointA, "binary_sensor.problem", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected problem" assert message == "detected problem"
@ -1071,7 +1112,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.problem", STATE_OFF, attributes pointA, "binary_sensor.problem", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no problem detected)" assert message == "cleared (no problem detected)"
@ -1079,13 +1120,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "smoke"} attributes = {"device_class": "smoke"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor smoke "detected" state # message for a binary_sensor smoke "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.smoke", STATE_ON, attributes pointA, "binary_sensor.smoke", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected smoke" assert message == "detected smoke"
@ -1094,7 +1136,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.smoke", STATE_OFF, attributes pointA, "binary_sensor.smoke", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no smoke detected)" assert message == "cleared (no smoke detected)"
@ -1102,13 +1144,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "sound"} attributes = {"device_class": "sound"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor sound "detected" state # message for a binary_sensor sound "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.sound", STATE_ON, attributes pointA, "binary_sensor.sound", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected sound" assert message == "detected sound"
@ -1117,7 +1160,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.sound", STATE_OFF, attributes pointA, "binary_sensor.sound", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no sound detected)" assert message == "cleared (no sound detected)"
@ -1125,13 +1168,14 @@ class TestComponentLogbook(unittest.TestCase):
"""Test if logbook message is correctly created for a binary_sensor.""" """Test if logbook message is correctly created for a binary_sensor."""
pointA = dt_util.utcnow() pointA = dt_util.utcnow()
attributes = {"device_class": "vibration"} attributes = {"device_class": "vibration"}
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
# message for a binary_sensor vibration "detected" state # message for a binary_sensor vibration "detected" state
eventA = self.create_state_changed_event( eventA = self.create_state_changed_event(
pointA, "binary_sensor.vibration", STATE_ON, attributes pointA, "binary_sensor.vibration", STATE_ON, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "detected vibration" assert message == "detected vibration"
@ -1140,7 +1184,7 @@ class TestComponentLogbook(unittest.TestCase):
pointA, "binary_sensor.vibration", STATE_OFF, attributes pointA, "binary_sensor.vibration", STATE_OFF, attributes
) )
message = logbook._entry_message_from_event( message = logbook._entry_message_from_event(
self.hass, eventA.entity_id, eventA.domain, eventA self.hass, eventA.entity_id, eventA.domain, eventA, entity_attr_cache
) )
assert message == "cleared (no vibration detected)" assert message == "cleared (no vibration detected)"
@ -1149,6 +1193,7 @@ class TestComponentLogbook(unittest.TestCase):
name = "Nice name" name = "Nice name"
message = "has a custom entry" message = "has a custom entry"
entity_id = "sun.sun" entity_id = "sun.sun"
entity_attr_cache = logbook.EntityAttributeCache(self.hass)
entries = list( entries = list(
logbook.humanify( logbook.humanify(
@ -1163,6 +1208,7 @@ class TestComponentLogbook(unittest.TestCase):
}, },
), ),
), ),
entity_attr_cache,
) )
) )

View File

@ -473,6 +473,7 @@ async def test_config(hass):
async def test_logbook_humanify_script_started_event(hass): async def test_logbook_humanify_script_started_event(hass):
"""Test humanifying script started event.""" """Test humanifying script started event."""
await async_setup_component(hass, DOMAIN, {}) await async_setup_component(hass, DOMAIN, {})
entity_attr_cache = logbook.EntityAttributeCache(hass)
event1, event2 = list( event1, event2 = list(
logbook.humanify( logbook.humanify(
@ -487,6 +488,7 @@ async def test_logbook_humanify_script_started_event(hass):
{ATTR_ENTITY_ID: "script.bye", ATTR_NAME: "Bye Script"}, {ATTR_ENTITY_ID: "script.bye", ATTR_NAME: "Bye Script"},
), ),
], ],
entity_attr_cache,
) )
) )