Пример #1
0
    def __init__(
        self,
        entity_id: str,
        state: str,
        attributes: Optional[Mapping] = None,
        last_changed: Optional[datetime.datetime] = None,
        last_updated: Optional[datetime.datetime] = None,
        context: Optional[Context] = None,
        # Temp, because database can still store invalid entity IDs
        # Remove with 1.0 or in 2020.
        temp_invalid_id_bypass: Optional[bool] = False,
    ) -> None:
        """Initialize a new state."""
        state = str(state)

        if not valid_entity_id(entity_id) and not temp_invalid_id_bypass:
            raise InvalidEntityFormatError(
                ("Invalid entity id encountered: {}. "
                 "Format should be <domain>.<object_id>").format(entity_id))

        if not valid_state(state):
            raise InvalidStateError(
                ("Invalid state encountered for entity id: {}. "
                 "State max length is 255 characters.").format(entity_id))

        self.entity_id = entity_id.lower()
        self.state = state
        self.attributes = MappingProxyType(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()
Пример #2
0
    def __init__(self,
                 entity_id: str,
                 state: Any,
                 attributes: Optional[Dict] = None,
                 last_changed: Optional[datetime.datetime] = None,
                 last_updated: Optional[datetime.datetime] = None,
                 context: Optional[Context] = None) -> None:
        """Initialize a new state."""
        state = str(state)

        if not valid_entity_id(entity_id):
            raise InvalidEntityFormatError(
                ("Invalid entity id encountered: {}. "
                 "Format should be <domain>.<object_id>").format(entity_id))

        if not valid_state(state):
            raise InvalidStateError(
                ("Invalid state encountered for entity id: {}. "
                 "State max length is 255 characters.").format(entity_id))

        self.entity_id = entity_id.lower()
        self.state = state
        self.attributes = MappingProxyType(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()
Пример #3
0
    async def get(self, request, datetime=None):
        """Retrieve logbook entries."""
        if datetime:
            datetime = dt_util.parse_datetime(datetime)

            if datetime is None:
                return self.json_message("Invalid datetime", HTTP_BAD_REQUEST)
        else:
            datetime = dt_util.start_of_local_day()

        period = request.query.get("period")
        if period is None:
            period = 1
        else:
            period = int(period)

        entity_ids = request.query.get("entity")
        if entity_ids:
            try:
                entity_ids = cv.entity_ids(entity_ids)
            except vol.Invalid:
                raise InvalidEntityFormatError(
                    f"Invalid entity id(s) encountered: {entity_ids}. "
                    "Format should be <domain>.<object_id>") from vol.Invalid

        end_time = request.query.get("end_time")
        if end_time is None:
            start_day = dt_util.as_utc(datetime) - timedelta(days=period - 1)
            end_day = start_day + timedelta(days=period)
        else:
            start_day = datetime
            end_day = dt_util.parse_datetime(end_time)
            if end_day is None:
                return self.json_message("Invalid end_time", HTTP_BAD_REQUEST)

        hass = request.app["hass"]

        entity_matches_only = "entity_matches_only" in request.query
        context_id = request.query.get("context_id")

        if entity_ids and context_id:
            return self.json_message("Can't combine entity with context_id",
                                     HTTP_BAD_REQUEST)

        def json_events():
            """Fetch events and generate JSON."""
            return self.json(
                _get_events(
                    hass,
                    start_day,
                    end_day,
                    entity_ids,
                    self.filters,
                    self.entities_filter,
                    entity_matches_only,
                    context_id,
                ))

        return await hass.async_add_executor_job(json_events)
Пример #4
0
    def __init__(self, entity_id, state, attributes=None, last_changed=None,
                 last_updated=None):
        """Initialize a new state."""
        if not valid_entity_id(entity_id):
            raise InvalidEntityFormatError((
                "Invalid entity id encountered: {}. "
                "Format should be <domain>.<object_id>").format(entity_id))

        self.entity_id = entity_id.lower()
        self.state = str(state)
        self.attributes = MappingProxyType(attributes or {})
        self.last_updated = last_updated or dt_util.utcnow()
        self.last_changed = last_changed or self.last_updated
Пример #5
0
    def __init__(self, entity_id, state, attributes=None, last_changed=None,
                 last_updated=None):
        if not ENTITY_ID_PATTERN.match(entity_id):
            raise InvalidEntityFormatError((
                "Invalid entity id encountered: {}. "
                "Format should be <domain>.<object_id>").format(entity_id))

        self.entity_id = entity_id.lower()
        self.state = state
        self.attributes = attributes or {}
        self.last_updated = dt_util.strip_microseconds(
            last_updated or dt_util.utcnow())

        # Strip microsecond from last_changed else we cannot guarantee
        # state == State.from_dict(state.as_dict())
        # This behavior occurs because to_dict uses datetime_to_str
        # which does not preserve microseconds
        self.last_changed = dt_util.strip_microseconds(
            last_changed or self.last_updated)
Пример #6
0
                return self.json_message("Invalid datetime",
                                         HTTPStatus.BAD_REQUEST)
        else:
            datetime_dt = dt_util.start_of_local_day()

        if (period_str := request.query.get("period")) is None:
            period: int = 1
        else:
            period = int(period_str)

        if entity_ids_str := request.query.get("entity"):
            try:
                entity_ids = cv.entity_ids(entity_ids_str)
            except vol.Invalid:
                raise InvalidEntityFormatError(
                    f"Invalid entity id(s) encountered: {entity_ids_str}. "
                    "Format should be <domain>.<object_id>") from vol.Invalid
        else:
            entity_ids = None

        if (end_time_str := request.query.get("end_time")) is None:
            start_day = dt_util.as_utc(datetime_dt) - timedelta(days=period -
                                                                1)
            end_day = start_day + timedelta(days=period)
        else:
            start_day = datetime_dt
            if (end_day_dt := dt_util.parse_datetime(end_time_str)) is None:
                return self.json_message("Invalid end_time",
                                         HTTPStatus.BAD_REQUEST)
            end_day = end_day_dt
Пример #7
0
def _get_events(
    hass,
    start_day,
    end_day,
    entity_id=None,
    filters=None,
    entities_filter=None,
    entity_matches_only=False,
):
    """Get events for a period of time."""
    entity_attr_cache = EntityAttributeCache(hass)
    context_lookup = {None: None}
    entity_id_lower = None
    apply_sql_entities_filter = True

    def yield_events(query):
        """Yield Events that are not filtered away."""
        for row in query.yield_per(1000):
            event = LazyEventPartialState(row)
            context_lookup.setdefault(event.context_id, event)
            if _keep_event(hass, event, entities_filter):
                yield event

    if entity_id is not None:
        entity_id_lower = entity_id.lower()
        if not valid_entity_id(entity_id_lower):
            raise InvalidEntityFormatError(
                f"Invalid entity id encountered: {entity_id_lower}. "
                "Format should be <domain>.<object_id>")
        entities_filter = generate_filter([], [entity_id_lower], [], [])
        apply_sql_entities_filter = False

    with session_scope(hass=hass) as session:
        old_state = aliased(States, name="old_state")

        query = (
            session.query(
                Events.event_type,
                Events.event_data,
                Events.time_fired,
                Events.context_id,
                Events.context_user_id,
                States.state,
                States.entity_id,
                States.domain,
                States.attributes,
            ).order_by(Events.time_fired).outerjoin(
                States, (Events.event_id == States.event_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.
            #
            .filter((Events.event_type != EVENT_STATE_CHANGED)
                    | ((States.state_id.isnot(None))
                       & (old_state.state_id.isnot(None))
                       & (States.state.isnot(None))
                       & (States.state != old_state.state)))
            #
            # Prefilter out continuous domains that have
            # ATTR_UNIT_OF_MEASUREMENT as its much faster in sql.
            #
            .filter((Events.event_type != EVENT_STATE_CHANGED)
                    | sqlalchemy.not_(States.domain.in_(CONTINUOUS_DOMAINS))
                    | sqlalchemy.not_(
                        States.attributes.contains(UNIT_OF_MEASUREMENT_JSON))).
            filter(
                Events.event_type.in_(ALL_EVENT_TYPES +
                                      list(hass.data.get(DOMAIN, {})))).filter(
                                          (Events.time_fired > start_day)
                                          & (Events.time_fired < end_day)))

        if entity_id_lower is not None:
            if entity_matches_only:
                # When entity_matches_only is provided, contexts and events that do not
                # contain the entity_id are not included in the logbook response.
                entity_id_json = ENTITY_ID_JSON_TEMPLATE.format(
                    entity_id_lower)
                query = query.filter(
                    ((States.last_updated == States.last_changed)
                     & (States.entity_id == entity_id_lower))
                    | (States.state_id.is_(None)
                       & Events.event_data.contains(entity_id_json)))
            else:
                query = query.filter((
                    (States.last_updated == States.last_changed)
                    & (States.entity_id == entity_id_lower))
                                     | (States.state_id.is_(None)))
        else:
            query = query.filter((States.last_updated == States.last_changed)
                                 | (States.state_id.is_(None)))

        if apply_sql_entities_filter and filters:
            entity_filter = filters.entity_filter()
            if entity_filter is not None:
                query = query.filter(entity_filter | (
                    Events.event_type != EVENT_STATE_CHANGED))

        return list(
            humanify(hass, yield_events(query), entity_attr_cache,
                     context_lookup))