diff --git a/homeassistant/components/recorder/executor.py b/homeassistant/components/recorder/executor.py index 0d913310e74..6eea2f651c3 100644 --- a/homeassistant/components/recorder/executor.py +++ b/homeassistant/components/recorder/executor.py @@ -39,7 +39,11 @@ class DBInterruptibleThreadPoolExecutor(InterruptibleThreadPoolExecutor): # When the executor gets lost, the weakref callback will wake up # the worker threads. - def weakref_cb(_: Any, q=self._work_queue) -> None: # type: ignore[no-untyped-def] # pylint: disable=invalid-name + # pylint: disable=invalid-name + def weakref_cb( # type: ignore[no-untyped-def] + _: Any, + q=self._work_queue, + ) -> None: q.put(None) num_threads = len(self._threads) diff --git a/homeassistant/components/recorder/filters.py b/homeassistant/components/recorder/filters.py index 72cfa784074..48251b6db59 100644 --- a/homeassistant/components/recorder/filters.py +++ b/homeassistant/components/recorder/filters.py @@ -93,10 +93,13 @@ class Filters: """Return human readable excludes/includes.""" return ( "" + f" excluded_entities={self.excluded_entities}" + f" excluded_domains={self.excluded_domains}" + f" excluded_entity_globs={self.excluded_entity_globs}" + f" included_entities={self.included_entities}" + f" included_domains={self.included_domains}" + f" included_entity_globs={self.included_entity_globs}" + ">" ) @property diff --git a/homeassistant/components/recorder/history.py b/homeassistant/components/recorder/history.py index 1d68becfa88..e4f5a39f1cc 100644 --- a/homeassistant/components/recorder/history.py +++ b/homeassistant/components/recorder/history.py @@ -398,7 +398,11 @@ def get_full_significant_states_with_session( significant_changes_only: bool = True, no_attributes: bool = False, ) -> MutableMapping[str, list[State]]: - """Variant of get_significant_states_with_session that does not return minimal responses.""" + """Variant of get_significant_states_with_session. + + Difference with get_significant_states_with_session is that it does not + return minimal responses. + """ return cast( MutableMapping[str, list[State]], get_significant_states_with_session( diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index b8a303104b9..9bddf11fcad 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -55,7 +55,7 @@ _LOGGER = logging.getLogger(__name__) def raise_if_exception_missing_str(ex: Exception, match_substrs: Iterable[str]) -> None: - """Raise an exception if the exception and cause do not contain the match substrs.""" + """Raise if the exception and cause do not contain the match substrs.""" lower_ex_strs = [str(ex).lower(), str(ex.__cause__).lower()] for str_sub in match_substrs: for exc_str in lower_ex_strs: @@ -665,7 +665,8 @@ def _apply_update( # noqa: C901 with session_scope(session=session_maker()) as session: connection = session.connection() connection.execute( - # Using LOCK=EXCLUSIVE to prevent the database from corrupting + # Using LOCK=EXCLUSIVE to prevent + # the database from corrupting # https://github.com/home-assistant/core/issues/56104 text( f"ALTER TABLE {table} CONVERT TO CHARACTER SET utf8mb4" @@ -806,7 +807,8 @@ def _apply_update( # noqa: C901 with contextlib.suppress(SQLAlchemyError): with session_scope(session=session_maker()) as session: connection = session.connection() - # This is safe to run multiple times and fast since the table is small + # This is safe to run multiple times and fast + # since the table is small. connection.execute( text("ALTER TABLE statistics_meta ROW_FORMAT=DYNAMIC") ) @@ -935,7 +937,7 @@ def _migrate_columns_to_timestamp( def _initialize_database(session: Session) -> bool: - """Initialize a new database, or a database created before introducing schema changes. + """Initialize a new database. The function determines the schema version by inspecting the db structure. @@ -962,7 +964,7 @@ def _initialize_database(session: Session) -> bool: def initialize_database(session_maker: Callable[[], Session]) -> bool: - """Initialize a new database, or a database created before introducing schema changes.""" + """Initialize a new database.""" try: with session_scope(session=session_maker()) as session: if _get_schema_version(session) is not None: diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 00673d86cf6..01b10ea966a 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -269,21 +269,22 @@ def _select_event_data_ids_to_purge( def _select_unused_attributes_ids( session: Session, attributes_ids: set[int], using_sqlite: bool ) -> set[int]: - """Return a set of attributes ids that are not used by any states in the database.""" + """Return a set of attributes ids that are not used by any states in the db.""" if not attributes_ids: return set() if using_sqlite: # - # SQLite has a superior query optimizer for the distinct query below as it uses the - # covering index without having to examine the rows directly for both of the queries - # below. + # SQLite has a superior query optimizer for the distinct query below as it uses + # the covering index without having to examine the rows directly for both of the + # queries below. # # We use the distinct query for SQLite since the query in the other branch can # generate more than 500 unions which SQLite does not support. # # How MariaDB's query optimizer handles this query: - # > explain select distinct attributes_id from states where attributes_id in (136723); + # > explain select distinct attributes_id from states where attributes_id in + # (136723); # ...Using index # seen_ids = { @@ -294,15 +295,16 @@ def _select_unused_attributes_ids( } else: # - # This branch is for DBMS that cannot optimize the distinct query well and has to examine - # all the rows that match. + # This branch is for DBMS that cannot optimize the distinct query well and has + # to examine all the rows that match. # - # This branch uses a union of simple queries, as each query is optimized away as the answer - # to the query can be found in the index. + # This branch uses a union of simple queries, as each query is optimized away + # as the answer to the query can be found in the index. # - # The below query works for SQLite as long as there are no more than 500 attributes_id - # to be selected. We currently do not have MySQL or PostgreSQL servers running in the - # test suite; we test this path using SQLite when there are less than 500 attributes_id. + # The below query works for SQLite as long as there are no more than 500 + # attributes_id to be selected. We currently do not have MySQL or PostgreSQL + # servers running in the test suite; we test this path using SQLite when there + # are less than 500 attributes_id. # # How MariaDB's query optimizer handles this query: # > explain select min(attributes_id) from states where attributes_id = 136723; @@ -349,7 +351,7 @@ def _purge_unused_attributes_ids( def _select_unused_event_data_ids( session: Session, data_ids: set[int], using_sqlite: bool ) -> set[int]: - """Return a set of event data ids that are not used by any events in the database.""" + """Return a set of event data ids that are not used by any events in the db.""" if not data_ids: return set() @@ -391,7 +393,10 @@ def _purge_unused_data_ids( def _select_statistics_runs_to_purge( session: Session, purge_before: datetime ) -> list[int]: - """Return a list of statistic runs to purge, but take care to keep the newest run.""" + """Return a list of statistic runs to purge. + + Takes care to keep the newest run. + """ statistic_runs = session.execute(find_statistics_runs_to_purge(purge_before)).all() statistic_runs_list = [run.run_id for run in statistic_runs] # Exclude the newest statistics run @@ -418,7 +423,7 @@ def _select_short_term_statistics_to_purge( def _select_legacy_event_state_and_attributes_and_data_ids_to_purge( session: Session, purge_before: datetime ) -> tuple[set[int], set[int], set[int], set[int]]: - """Return a list of event, state, and attribute ids to purge that are linked by the event_id. + """Return a list of event, state, and attribute ids to purge linked by the event_id. We do not link these anymore since state_change events do not exist in the events table anymore, however we @@ -676,7 +681,8 @@ def purge_entity_data(instance: Recorder, entity_filter: Callable[[str], bool]) ] _LOGGER.debug("Purging entity data for %s", selected_entity_ids) if len(selected_entity_ids) > 0: - # Purge a max of MAX_ROWS_TO_PURGE, based on the oldest states or events record + # Purge a max of MAX_ROWS_TO_PURGE, based on the oldest states + # or events record. _purge_filtered_states(instance, session, selected_entity_ids, using_sqlite) _LOGGER.debug("Purging entity data hasn't fully completed yet") return False diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index b8a58500747..4a39abd9f63 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -1672,7 +1672,10 @@ def _get_last_statistics_short_term_stmt( metadata_id: int, number_of_stats: int, ) -> StatementLambdaElement: - """Generate a statement for number_of_stats short term statistics for a given statistic_id.""" + """Generate a statement for number_of_stats short term statistics. + + For a given statistic_id. + """ return lambda_stmt( lambda: select(*QUERY_STATISTICS_SHORT_TERM) .filter_by(metadata_id=metadata_id) @@ -1881,7 +1884,10 @@ def _sorted_statistics_to_dict( result[stat_id] = [] # Identify metadata IDs for which no data was available at the requested start time - for meta_id, group in groupby(stats, lambda stat: stat.metadata_id): # type: ignore[no-any-return] + for meta_id, group in groupby( + stats, + lambda stat: stat.metadata_id, # type: ignore[no-any-return] + ): first_start_time = process_timestamp(next(group).start) if start_time and first_start_time > start_time: need_stat_at_start_time.add(meta_id) @@ -1897,7 +1903,10 @@ def _sorted_statistics_to_dict( stats_at_start_time[stat.metadata_id] = (stat,) # Append all statistic entries, and optionally do unit conversion - for meta_id, group in groupby(stats, lambda stat: stat.metadata_id): # type: ignore[no-any-return] + for meta_id, group in groupby( + stats, + lambda stat: stat.metadata_id, # type: ignore[no-any-return] + ): state_unit = unit = metadata[meta_id]["unit_of_measurement"] statistic_id = metadata[meta_id]["statistic_id"] if state := hass.states.get(statistic_id): @@ -1964,7 +1973,7 @@ def _async_import_statistics( metadata: StatisticMetaData, statistics: Iterable[StatisticData], ) -> None: - """Validate timestamps and insert an import_statistics job in the recorder's queue.""" + """Validate timestamps and insert an import_statistics job in the queue.""" for statistic in statistics: start = statistic["start"] if start.tzinfo is None or start.tzinfo.utcoffset(start) is None: diff --git a/homeassistant/components/recorder/tasks.py b/homeassistant/components/recorder/tasks.py index ba6c8dd0427..63dc8e9d2e3 100644 --- a/homeassistant/components/recorder/tasks.py +++ b/homeassistant/components/recorder/tasks.py @@ -121,7 +121,10 @@ class PurgeEntitiesTask(RecorderTask): @dataclass class PerodicCleanupTask(RecorderTask): - """An object to insert into the recorder to trigger cleanup tasks when auto purge is disabled.""" + """An object to insert into the recorder to trigger cleanup tasks. + + Trigger cleanup tasks when auto purge is disabled. + """ def run(self, instance: Recorder) -> None: """Handle the task.""" @@ -195,7 +198,10 @@ class AdjustStatisticsTask(RecorderTask): @dataclass class WaitTask(RecorderTask): - """An object to insert into the recorder queue to tell it set the _queue_watch event.""" + """An object to insert into the recorder queue. + + Tell it set the _queue_watch event. + """ commit_before = False diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index 02ec644dd9e..8bffe7fc088 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -196,9 +196,11 @@ def execute_stmt_lambda_element( """ executed = session.execute(stmt) use_all = not start_time or ((end_time or dt_util.utcnow()) - start_time).days <= 1 - for tryno in range(0, RETRIES): + for tryno in range(RETRIES): try: - return executed.all() if use_all else executed.yield_per(yield_per) # type: ignore[no-any-return] + if use_all: + return executed.all() # type: ignore[no-any-return] + return executed.yield_per(yield_per) # type: ignore[no-any-return] except SQLAlchemyError as err: _LOGGER.error("Error executing query: %s", err) if tryno == RETRIES - 1: @@ -400,12 +402,11 @@ def _datetime_or_none(value: str) -> datetime | None: def build_mysqldb_conv() -> dict: """Build a MySQLDB conv dict that uses cisco8601 to parse datetimes.""" # Late imports since we only call this if they are using mysqldb - from MySQLdb.constants import ( # pylint: disable=import-outside-toplevel,import-error - FIELD_TYPE, - ) - from MySQLdb.converters import ( # pylint: disable=import-outside-toplevel,import-error - conversions, - ) + # pylint: disable=import-outside-toplevel,import-error + from MySQLdb.constants import FIELD_TYPE + + # pylint: disable=import-outside-toplevel,import-error + from MySQLdb.converters import conversions return {**conversions, FIELD_TYPE.DATETIME: _datetime_or_none} @@ -444,7 +445,8 @@ def setup_connection_for_dialect( # or NORMAL if they do not. # # https://sqlite.org/pragma.html#pragma_synchronous - # The synchronous=NORMAL setting is a good choice for most applications running in WAL mode. + # The synchronous=NORMAL setting is a good choice for most applications + # running in WAL mode. # synchronous = "NORMAL" if instance.commit_interval else "FULL" execute_on_connection(dbapi_connection, f"PRAGMA synchronous={synchronous}") diff --git a/homeassistant/components/recorder/websocket_api.py b/homeassistant/components/recorder/websocket_api.py index 35879bfc076..c63e5bc43ca 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -242,7 +242,10 @@ def _ws_get_list_statistic_ids( msg_id: int, statistic_type: Literal["mean"] | Literal["sum"] | None = None, ) -> str: - """Fetch a list of available statistic_id and convert them to json in the executor.""" + """Fetch a list of available statistic_id and convert them to JSON. + + Runs in the executor. + """ return JSON_DUMP( messages.result_message(msg_id, list_statistic_ids(hass, None, statistic_type)) )