def test_states_from_native_invalid_entity_id(): """Test loading a state from an invalid entity ID.""" event = States() event.entity_id = "test.invalid__id" event.attributes = "{}" state = event.to_native() assert state.entity_id == "test.invalid__id"
def test_states_from_native_invalid_entity_id(): """Test loading a state from an invalid entity ID.""" event = States() event.entity_id = "test.invalid__id" event.attributes = "{}" state = event.to_native() assert state.entity_id == 'test.invalid__id'
def _add_db_entries(hass: HomeAssistant) -> None: with recorder.session_scope(hass=hass) as session: # Add states and state_changed events that should be purged for days in range(1, 4): timestamp = dt_util.utcnow() - timedelta(days=days) for event_id in range(1000, 1020): _add_state_and_state_changed_event( session, "sensor.excluded", "purgeme", timestamp, event_id * days, ) # Add events that should be keeped timestamp = dt_util.utcnow() - timedelta(days=1) for event_id in range(200, 210): session.add( Events( event_id=event_id, event_type="EVENT_KEEP", event_data="{}", origin="LOCAL", created=timestamp, time_fired=timestamp, ) ) # Add states with linked old_state_ids that need to be handled timestamp = dt_util.utcnow() - timedelta(days=0) state_1 = States( entity_id="sensor.linked_old_state_id", domain="sensor", state="keep", attributes="{}", last_changed=timestamp, last_updated=timestamp, created=timestamp, old_state_id=1, ) timestamp = dt_util.utcnow() - timedelta(days=4) state_2 = States( entity_id="sensor.linked_old_state_id", domain="sensor", state="keep", attributes="{}", last_changed=timestamp, last_updated=timestamp, created=timestamp, old_state_id=2, ) state_3 = States( entity_id="sensor.linked_old_state_id", domain="sensor", state="keep", attributes="{}", last_changed=timestamp, last_updated=timestamp, created=timestamp, old_state_id=62, # keep ) session.add_all((state_1, state_2, state_3))
async def _add_db_entries(hass: HomeAssistant, cutoff: datetime, rows: int) -> None: timestamp_keep = cutoff timestamp_purge = cutoff - timedelta(microseconds=1) with recorder.session_scope(hass=hass) as session: session.add( Events( event_id=1000, event_type="KEEP", event_data="{}", origin="LOCAL", time_fired=timestamp_keep, )) session.add( States( entity_id="test.cutoff", state="keep", attributes="{}", last_changed=timestamp_keep, last_updated=timestamp_keep, event_id=1000, attributes_id=1000, )) session.add( StateAttributes( shared_attrs="{}", hash=1234, attributes_id=1000, )) for row in range(1, rows): session.add( Events( event_id=1000 + row, event_type="PURGE", event_data="{}", origin="LOCAL", time_fired=timestamp_purge, )) session.add( States( entity_id="test.cutoff", state="purge", attributes="{}", last_changed=timestamp_purge, last_updated=timestamp_purge, event_id=1000 + row, attributes_id=1000 + row, )) session.add( StateAttributes( shared_attrs="{}", hash=1234, attributes_id=1000 + row, ))
def test_entity_ids(self): """Test if entity ids helper method works.""" run = RecorderRuns( start=datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC), end=datetime(2016, 7, 9, 23, 0, 0, tzinfo=dt.UTC), closed_incorrect=False, created=datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC), ) self.session.add(run) self.session.commit() before_run = datetime(2016, 7, 9, 8, 0, 0, tzinfo=dt.UTC) in_run = datetime(2016, 7, 9, 13, 0, 0, tzinfo=dt.UTC) in_run2 = datetime(2016, 7, 9, 15, 0, 0, tzinfo=dt.UTC) in_run3 = datetime(2016, 7, 9, 18, 0, 0, tzinfo=dt.UTC) after_run = datetime(2016, 7, 9, 23, 30, 0, tzinfo=dt.UTC) assert run.to_native() == run assert run.entity_ids() == [] self.session.add( States( entity_id="sensor.temperature", state="20", last_changed=before_run, last_updated=before_run, )) self.session.add( States( entity_id="sensor.sound", state="10", last_changed=after_run, last_updated=after_run, )) self.session.add( States( entity_id="sensor.humidity", state="76", last_changed=in_run, last_updated=in_run, )) self.session.add( States( entity_id="sensor.lux", state="5", last_changed=in_run3, last_updated=in_run3, )) assert sorted(run.entity_ids()) == ["sensor.humidity", "sensor.lux"] assert run.entity_ids(in_run2) == ["sensor.humidity"]
def _add_test_states(self): """Add multiple states to the db for testing.""" now = datetime.now() five_days_ago = now - timedelta(days=5) eleven_days_ago = now - timedelta(days=11) attributes = {'test_attr': 5, 'test_attr_10': 'nice'} self.hass.block_till_done() self.hass.data[DATA_INSTANCE].block_till_done() with recorder.session_scope(hass=self.hass) as session: for event_id in range(6): if event_id < 2: timestamp = eleven_days_ago state = 'autopurgeme' elif event_id < 4: timestamp = five_days_ago state = 'purgeme' else: timestamp = now state = 'dontpurgeme' session.add(States( entity_id='test.recorder2', domain='sensor', state=state, attributes=json.dumps(attributes), last_changed=timestamp, last_updated=timestamp, created=timestamp, event_id=event_id + 1000 )) # if self._add_test_events was called, we added a special event # that should be protected from deletion, too protected_event_id = getattr(self, "_protected_event_id", 2000) # add a state that is old but the only state of its entity and # should be protected session.add(States( entity_id='test.rarely_updated_entity', domain='sensor', state='iamprotected', attributes=json.dumps(attributes), last_changed=eleven_days_ago, last_updated=eleven_days_ago, created=eleven_days_ago, event_id=protected_event_id ))
def _add_db_entries(hass: HomeAssistant) -> None: with recorder.session_scope(hass=hass) as session: # Add states and state_changed events that should be purged # in the legacy format timestamp = dt_util.utcnow() - timedelta(days=5) event_id = 1021 session.add( States( entity_id="sensor.old_format", state=STATE_ON, attributes=json.dumps( {"old": "not_using_state_attributes"}), last_changed=timestamp, last_updated=timestamp, event_id=event_id, state_attributes=None, )) session.add( Events( event_id=event_id, event_type=EVENT_STATE_CHANGED, event_data="{}", origin="LOCAL", time_fired=timestamp, ))
def _add_state_and_state_changed_event( session: Session, entity_id: str, state: str, timestamp: datetime, event_id: int, ) -> None: """Add state and state_changed event to database for testing.""" state_attrs = StateAttributes(hash=event_id, shared_attrs=json.dumps( {entity_id: entity_id})) session.add(state_attrs) session.add( States( entity_id=entity_id, state=state, attributes=None, last_changed=timestamp, last_updated=timestamp, event_id=event_id, state_attributes=state_attrs, )) session.add( Events( event_id=event_id, event_type=EVENT_STATE_CHANGED, event_data="{}", origin="LOCAL", time_fired=timestamp, ))
def _add_test_states(self): """Add multiple states to the db for testing.""" now = datetime.now() five_days_ago = now - timedelta(days=5) eleven_days_ago = now - timedelta(days=11) attributes = {"test_attr": 5, "test_attr_10": "nice"} self.hass.block_till_done() self.hass.data[DATA_INSTANCE].block_till_done() with recorder.session_scope(hass=self.hass) as session: for event_id in range(6): if event_id < 2: timestamp = eleven_days_ago state = "autopurgeme" elif event_id < 4: timestamp = five_days_ago state = "purgeme" else: timestamp = now state = "dontpurgeme" session.add( States( entity_id="test.recorder2", domain="sensor", state=state, attributes=json.dumps(attributes), last_changed=timestamp, last_updated=timestamp, created=timestamp, event_id=event_id + 1000, ))
async def _add_db_entries(hass: HomeAssistant, timestamp: datetime) -> None: with recorder.session_scope(hass=hass) as session: session.add( Events( event_id=1001, event_type="EVENT_TEST_PURGE", event_data="{}", origin="LOCAL", time_fired=timestamp, )) session.add( States( entity_id="test.recorder2", state="purgeme", attributes="{}", last_changed=timestamp, last_updated=timestamp, event_id=1001, attributes_id=1002, )) session.add( StateAttributes( shared_attrs="{}", hash=1234, attributes_id=1002, ))
def _add_state_and_state_changed_event( session: Session, entity_id: str, state: str, timestamp: datetime, event_id: int, ) -> None: """Add state and state_changed event to database for testing.""" session.add( States( entity_id=entity_id, domain="sensor", state=state, attributes="{}", last_changed=timestamp, last_updated=timestamp, created=timestamp, event_id=event_id, )) session.add( Events( event_id=event_id, event_type=EVENT_STATE_CHANGED, event_data="{}", origin="LOCAL", created=timestamp, time_fired=timestamp, ))
def _add_db_entries(hass: ha.HomeAssistant, point: datetime, entity_ids: list[str]) -> None: with recorder.session_scope(hass=hass) as session: for idx, entity_id in enumerate(entity_ids): session.add( Events( event_id=1001 + idx, event_type="state_changed", event_data="{}", origin="LOCAL", time_fired=point, )) session.add( States( entity_id=entity_id, state="on", attributes='{"name":"the light"}', last_changed=point, last_updated=point, event_id=1001 + idx, attributes_id=1002 + idx, )) session.add( StateAttributes( shared_attrs='{"name":"the shared light"}', hash=1234 + idx, attributes_id=1002 + idx, ))
def _add_test_states(self): """Add multiple states to the db for testing.""" now = datetime.now() five_days_ago = now - timedelta(days=5) attributes = {'test_attr': 5, 'test_attr_10': 'nice'} self.hass.block_till_done() self.hass.data[DATA_INSTANCE].block_till_done() with recorder.session_scope(hass=self.hass) as session: for event_id in range(5): if event_id < 3: timestamp = five_days_ago state = 'purgeme' else: timestamp = now state = 'dontpurgeme' session.add( States(entity_id='test.recorder2', domain='sensor', state=state, attributes=json.dumps(attributes), last_changed=timestamp, last_updated=timestamp, created=timestamp, event_id=event_id + 1000))
def test_from_event(self): """Test converting event to db state.""" state = ha.State('sensor.temperature', '18') event = ha.Event(EVENT_STATE_CHANGED, { 'entity_id': 'sensor.temperature', 'old_state': None, 'new_state': state, }) assert state == States.from_event(event).to_native()
def test_from_event(self): """Test converting event to db state.""" state = ha.State('sensor.temperature', '18') event = ha.Event(EVENT_STATE_CHANGED, { 'entity_id': 'sensor.temperature', 'old_state': None, 'new_state': state, }, context=state.context) assert state == States.from_event(event).to_native()
def run(self): """Start processing events to save.""" from homeassistant.components.recorder.models import Events, States import sqlalchemy.exc while True: try: self._setup_connection() self._setup_run() break except sqlalchemy.exc.SQLAlchemyError as e: log_error(e, retry_wait=CONNECT_RETRY_WAIT, rollback=False, message="Error during connection setup: %s") if self.purge_days is not None: def purge_ticker(event): """Rerun purge every second day.""" self._purge_old_data() track_point_in_utc_time(self.hass, purge_ticker, dt_util.utcnow() + timedelta(days=2)) track_point_in_utc_time(self.hass, purge_ticker, dt_util.utcnow() + timedelta(minutes=5)) while True: event = self.queue.get() if event == self.quit_object: self._close_run() self._close_connection() # pylint: disable=global-statement global _INSTANCE _INSTANCE = None self.queue.task_done() return if event.event_type == EVENT_TIME_CHANGED: self.queue.task_done() continue dbevent = Events.from_event(event) self._commit(dbevent) if event.event_type != EVENT_STATE_CHANGED: self.queue.task_done() continue dbstate = States.from_event(event) dbstate.event_id = dbevent.event_id self._commit(dbstate) self.queue.task_done()
def test_from_event_to_db_state(): """Test converting event to db state.""" state = ha.State("sensor.temperature", "18") event = ha.Event( EVENT_STATE_CHANGED, { "entity_id": "sensor.temperature", "old_state": None, "new_state": state }, context=state.context, ) assert state == States.from_event(event).to_native()
def test_from_event_to_delete_state(self): """Test converting deleting state event to db state.""" event = ha.Event(EVENT_STATE_CHANGED, { 'entity_id': 'sensor.temperature', 'old_state': ha.State('sensor.temperature', '18'), 'new_state': None, }) db_state = States.from_event(event) assert db_state.entity_id == 'sensor.temperature' assert db_state.domain == 'sensor' assert db_state.state == '' assert db_state.last_changed == event.time_fired assert db_state.last_updated == event.time_fired
def test_states_from_native_invalid_entity_id(): """Test loading a state from an invalid entity ID.""" state = States() state.entity_id = "test.invalid__id" state.attributes = "{}" with pytest.raises(InvalidEntityFormatError): state = state.to_native() state = state.to_native(validate_entity_id=False) assert state.entity_id == "test.invalid__id"
def test_from_event_to_delete_state(): """Test converting deleting state event to db state.""" event = ha.Event( EVENT_STATE_CHANGED, { "entity_id": "sensor.temperature", "old_state": ha.State("sensor.temperature", "18"), "new_state": None, }, ) db_state = States.from_event(event) assert db_state.entity_id == "sensor.temperature" assert db_state.state == "" assert db_state.last_changed == event.time_fired assert db_state.last_updated == event.time_fired
def test_from_event_to_db_state(): """Test converting event to db state.""" state = ha.State("sensor.temperature", "18") event = ha.Event( EVENT_STATE_CHANGED, { "entity_id": "sensor.temperature", "old_state": None, "new_state": state }, context=state.context, ) # We don't restore context unless we need it by joining the # events table on the event_id for state_changed events state.context = ha.Context(id=None) assert state == States.from_event(event).to_native()
async def _add_test_states(hass: HomeAssistantType, instance: recorder.Recorder): """Add multiple states to the db for testing.""" utcnow = dt_util.utcnow() five_days_ago = utcnow - timedelta(days=5) eleven_days_ago = utcnow - timedelta(days=11) attributes = {"test_attr": 5, "test_attr_10": "nice"} await hass.async_block_till_done() await async_wait_recording_done(hass, instance) with recorder.session_scope(hass=hass) as session: old_state_id = None for event_id in range(6): if event_id < 2: timestamp = eleven_days_ago state = "autopurgeme" elif event_id < 4: timestamp = five_days_ago state = "purgeme" else: timestamp = utcnow state = "dontpurgeme" event = Events( event_type="state_changed", event_data="{}", origin="LOCAL", created=timestamp, time_fired=timestamp, ) session.add(event) session.flush() state = States( entity_id="test.recorder2", domain="sensor", state=state, attributes=json.dumps(attributes), last_changed=timestamp, last_updated=timestamp, created=timestamp, event_id=event.event_id, old_state_id=old_state_id, ) session.add(state) session.flush() old_state_id = state.state_id
def _add_data_in_last_run(hass, entities): """Add test data in the last recorder_run.""" # pylint: disable=protected-access t_now = dt_util.utcnow() - timedelta(minutes=10) t_min_1 = t_now - timedelta(minutes=20) t_min_2 = t_now - timedelta(minutes=30) with recorder.session_scope(hass=hass) as session: session.add(RecorderRuns(start=t_min_2, end=t_now, created=t_min_2)) for entity_id, state in entities.items(): session.add( States(entity_id=entity_id, domain=split_entity_id(entity_id)[0], state=state, attributes='{}', last_changed=t_min_1, last_updated=t_min_1, created=t_min_1))
def test_entity_ids(): """Test if entity ids helper method works.""" engine = create_engine("sqlite://") Base.metadata.create_all(engine) session_factory = sessionmaker(bind=engine) session = scoped_session(session_factory) session.query(Events).delete() session.query(States).delete() session.query(RecorderRuns).delete() run = RecorderRuns( start=datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC), end=datetime(2016, 7, 9, 23, 0, 0, tzinfo=dt.UTC), closed_incorrect=False, created=datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC), ) session.add(run) session.commit() before_run = datetime(2016, 7, 9, 8, 0, 0, tzinfo=dt.UTC) in_run = datetime(2016, 7, 9, 13, 0, 0, tzinfo=dt.UTC) in_run2 = datetime(2016, 7, 9, 15, 0, 0, tzinfo=dt.UTC) in_run3 = datetime(2016, 7, 9, 18, 0, 0, tzinfo=dt.UTC) after_run = datetime(2016, 7, 9, 23, 30, 0, tzinfo=dt.UTC) assert run.to_native() == run assert run.entity_ids() == [] session.add( States( entity_id="sensor.temperature", state="20", last_changed=before_run, last_updated=before_run, )) session.add( States( entity_id="sensor.sound", state="10", last_changed=after_run, last_updated=after_run, )) session.add( States( entity_id="sensor.humidity", state="76", last_changed=in_run, last_updated=in_run, )) session.add( States( entity_id="sensor.lux", state="5", last_changed=in_run3, last_updated=in_run3, )) assert sorted(run.entity_ids()) == ["sensor.humidity", "sensor.lux"] assert run.entity_ids(in_run2) == ["sensor.humidity"]
def run(self): """Start processing events to save.""" from homeassistant.components.recorder.models import Events, States from sqlalchemy.exc import SQLAlchemyError while True: try: self._setup_connection() self._setup_run() self.db_ready.set() self.hass.loop.call_soon_threadsafe(self.async_db_ready.set) break except SQLAlchemyError as err: _LOGGER.error("Error during connection setup: %s (retrying " "in %s seconds)", err, CONNECT_RETRY_WAIT) time.sleep(CONNECT_RETRY_WAIT) if self.purge_days is not None: async_track_time_interval( self.hass, self._purge_old_data, timedelta(days=2)) while True: event = self.queue.get() if event is None: self._close_run() self._close_connection() self.queue.task_done() return if event.event_type == EVENT_TIME_CHANGED: self.queue.task_done() continue if ATTR_ENTITY_ID in event.data: entity_id = event.data[ATTR_ENTITY_ID] domain = split_entity_id(entity_id)[0] # Exclude entities OR # Exclude domains, but include specific entities if (entity_id in self.exclude) or \ (domain in self.exclude and entity_id not in self.include_e): self.queue.task_done() continue # Included domains only (excluded entities above) OR # Include entities only, but only if no excludes if (self.include_d and domain not in self.include_d) or \ (self.include_e and entity_id not in self.include_e and not self.exclude): self.queue.task_done() continue with session_scope() as session: dbevent = Events.from_event(event) self._commit(session, dbevent) if event.event_type != EVENT_STATE_CHANGED: self.queue.task_done() continue dbstate = States.from_event(event) dbstate.event_id = dbevent.event_id self._commit(session, dbstate) self.queue.task_done()
def run(self): """Start processing events to save.""" from homeassistant.components.recorder.models import Events, States import sqlalchemy.exc while True: try: self._setup_connection() self._setup_run() break except sqlalchemy.exc.SQLAlchemyError as e: log_error(e, retry_wait=CONNECT_RETRY_WAIT, rollback=False, message="Error during connection setup: %s") if self.purge_days is not None: def purge_ticker(event): """Rerun purge every second day.""" self._purge_old_data() track_point_in_utc_time(self.hass, purge_ticker, dt_util.utcnow() + timedelta(days=2)) track_point_in_utc_time(self.hass, purge_ticker, dt_util.utcnow() + timedelta(minutes=5)) while True: event = self.queue.get() if event is None: self._close_run() self._close_connection() self.queue.task_done() return if event.event_type == EVENT_TIME_CHANGED: self.queue.task_done() continue if ATTR_ENTITY_ID in event.data: entity_id = event.data[ATTR_ENTITY_ID] domain = split_entity_id(entity_id)[0] # Exclude entities OR # Exclude domains, but include specific entities if (entity_id in self.exclude) or \ (domain in self.exclude and entity_id not in self.include_e): self.queue.task_done() continue # Included domains only (excluded entities above) OR # Include entities only, but only if no excludes if (self.include_d and domain not in self.include_d) or \ (self.include_e and entity_id not in self.include_e and not self.exclude): self.queue.task_done() continue dbevent = Events.from_event(event) self._commit(dbevent) if event.event_type != EVENT_STATE_CHANGED: self.queue.task_done() continue dbstate = States.from_event(event) dbstate.event_id = dbevent.event_id self._commit(dbstate) self.queue.task_done()
def run(self): """Start processing events to save.""" from homeassistant.components.recorder.models import Events, States import sqlalchemy.exc while True: try: self._setup_connection() self._setup_run() break except sqlalchemy.exc.SQLAlchemyError as err: _LOGGER.error( "Error during connection setup: %s (retrying " "in %s seconds)", err, CONNECT_RETRY_WAIT) time.sleep(CONNECT_RETRY_WAIT) if self.purge_days is not None: async_track_time_interval(self.hass, self._purge_old_data, timedelta(days=2)) while True: event = self.queue.get() if event is None: self._close_run() self._close_connection() self.queue.task_done() return if event.event_type == EVENT_TIME_CHANGED: self.queue.task_done() continue if ATTR_ENTITY_ID in event.data: entity_id = event.data[ATTR_ENTITY_ID] domain = split_entity_id(entity_id)[0] # Exclude entities OR # Exclude domains, but include specific entities if (entity_id in self.exclude) or \ (domain in self.exclude and entity_id not in self.include_e): self.queue.task_done() continue # Included domains only (excluded entities above) OR # Include entities only, but only if no excludes if (self.include_d and domain not in self.include_d) or \ (self.include_e and entity_id not in self.include_e and not self.exclude): self.queue.task_done() continue with session_scope() as session: dbevent = Events.from_event(event) self._commit(session, dbevent) if event.event_type != EVENT_STATE_CHANGED: self.queue.task_done() continue dbstate = States.from_event(event) dbstate.event_id = dbevent.event_id self._commit(session, dbstate) self.queue.task_done()