diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index 71d1f48590a..33f5e3d6b73 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -73,10 +73,12 @@ async def auth_manager_from_config( provider_hash[key] = provider if isinstance(provider, HassAuthProvider): - # Can be removed in 2026.7 with the legacy mode of homeassistant auth provider - # We need to initialize the provider to create the repair if needed as otherwise - # the provider will be initialized on first use, which could be rare as users - # don't frequently change auth settings + # Can be removed in 2026.7 with the legacy mode of + # homeassistant auth provider. + # We need to initialize the provider to create the repair + # if needed as otherwise the provider will be initialized + # on first use, which could be rare as users don't + # frequently change auth settings await provider.async_initialize() if module_configs: diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index 13feddf9523..f402316b8b8 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -120,9 +120,10 @@ class Data: if self.normalize_username(username, force_normalize=True) != username: logging.getLogger(__name__).warning( ( - "Home Assistant auth provider is running in legacy mode " - "because we detected usernames that are normalized (lowercase and without spaces)." - " Please change the username: '%s'." + "Home Assistant auth provider is running in" + " legacy mode because we detected usernames" + " that are normalized (lowercase and without" + " spaces). Please change the username: '%s'." ), username, ) @@ -139,7 +140,9 @@ class Data: severity=ir.IssueSeverity.WARNING, translation_key="homeassistant_provider_not_normalized_usernames", translation_placeholders={ - "usernames": f'- "{'"\n- "'.join(sorted(not_normalized_usernames))}"' + "usernames": ( + f'- "{'"\n- "'.join(sorted(not_normalized_usernames))}"' + ) }, learn_more_url="homeassistant://config/users", ) diff --git a/homeassistant/backup_restore.py b/homeassistant/backup_restore.py index 040a4b58634..8a0056c187b 100644 --- a/homeassistant/backup_restore.py +++ b/homeassistant/backup_restore.py @@ -60,7 +60,10 @@ def restore_backup_file_content(config_dir: Path) -> RestoreBackupFileContent | def _clear_configuration_directory(config_dir: Path, keep: Iterable[str]) -> None: - """Delete all files and directories in the config directory except entries in the keep list.""" + """Delete all files and directories in the config dir. + + Entries in the keep list are preserved. + """ keep_paths = [config_dir.joinpath(path) for path in keep] entries_to_remove = sorted( entry for entry in config_dir.iterdir() if entry not in keep_paths @@ -101,7 +104,8 @@ def _extract_backup( ) ) > HA_VERSION: raise ValueError( - f"You need at least Home Assistant version {backup_meta_version} to restore this backup" + f"You need at least Home Assistant version" + f" {backup_meta_version} to restore this backup" ) with securetar.SecureTarFile( diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 7592ef78244..46bd5941a0c 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -17,7 +17,8 @@ from time import monotonic from typing import TYPE_CHECKING, Any # Import cryptography early since import openssl is not thread-safe -# _frozen_importlib._DeadlockError: deadlock detected by _ModuleLock('cryptography.hazmat.backends.openssl.backend') +# _frozen_importlib._DeadlockError: deadlock detected by +# _ModuleLock('cryptography.hazmat.backends.openssl.backend') import cryptography.hazmat.backends.openssl.backend # noqa: F401 import voluptuous as vol import yarl @@ -165,10 +166,14 @@ FRONTEND_INTEGRATIONS = { # visible in frontend "frontend", } -# Stage 0 is divided into substages. Each substage has a name, a set of integrations and a timeout. -# The substage containing recorder should have no timeout, as it could cancel a database migration. -# Recorder freezes "recorder" timeout during a migration, but it does not freeze other timeouts. -# If we add timeouts to the frontend substages, we should make sure they don't apply in recovery mode. +# Stage 0 is divided into substages. Each substage has a name, +# a set of integrations and a timeout. +# The substage containing recorder should have no timeout, as it +# could cancel a database migration. +# Recorder freezes "recorder" timeout during a migration, but it +# does not freeze other timeouts. +# If we add timeouts to the frontend substages, we should make sure +# they don't apply in recovery mode. STAGE_0_INTEGRATIONS = ( # Load logging and http deps as soon as possible ("logging, http deps", LOGGING_AND_HTTP_DEPS_INTEGRATIONS, None), diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 23c442673fb..a79b49d1fa2 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -560,8 +560,12 @@ class ConfigEntry[_DataT = Any]: def __repr__(self) -> str: """Representation of ConfigEntry.""" return ( - f"" + f"" ) def __setattr__(self, key: str, value: Any) -> None: @@ -663,7 +667,9 @@ class ConfigEntry[_DataT = Any]: "disabled_by": self.disabled_by, "reason": self.reason, "error_reason_translation_key": self.error_reason_translation_key, - "error_reason_translation_placeholders": self.error_reason_translation_placeholders, + "error_reason_translation_placeholders": ( + self.error_reason_translation_placeholders + ), "num_subentries": len(self.subentries), } return json_fragment(json_bytes(json_repr)) @@ -703,7 +709,8 @@ class ConfigEntry[_DataT = Any]: integration = await loader.async_get_integration(hass, self.domain) self._integration_for_domain = integration - # Log setup to the integration logger so it's visible when debug logs are enabled. + # Log setup to the integration logger so it's visible + # when debug logs are enabled. logger = self.logger # Only store setup result as state if it was not forwarded. @@ -892,8 +899,9 @@ class ConfigEntry[_DataT = Any]: await self._async_process_on_unload(hass) # - # After successfully calling async_setup_entry, it is important that this function - # does not yield to the event loop by using `await` or `async with` or + # After successfully calling async_setup_entry, it is important + # that this function does not yield to the event loop by using + # `await` or `async with` or # similar until after the state has been set by calling self._async_set_state. # # Otherwise we risk that any `call_soon`s @@ -1901,8 +1909,9 @@ class ConfigEntryItems(UserDict[str, ConfigEntry]): data = self.data self.check_unique_id(entry) if entry_id in data: - # This is likely a bug in a test that is adding the same entry twice. - # In the future, once we have fixed the tests, this will raise HomeAssistantError. + # This is likely a bug in a test that is adding the same + # entry twice. In the future, once we have fixed the tests, + # this will raise HomeAssistantError. entry.logger.error("An entry with the id %s already exists", entry_id) self._unindex_entry(entry_id) data[entry_id] = entry @@ -2317,7 +2326,8 @@ class ConfigEntries: await loader.async_get_integration(self.hass, entry.domain) except loader.IntegrationNotFound: entry.logger.info( - "Integration for ignored config entry %s not found. Creating repair issue", + "Integration for ignored config entry %s" + " not found. Creating repair issue", entry, ) ir.async_create_issue( @@ -2344,9 +2354,10 @@ class ConfigEntries: if entry.state is not ConfigEntryState.NOT_LOADED: raise OperationNotAllowed( - f"The config entry '{entry.title}' ({entry.domain}) with entry_id" - f" '{entry.entry_id}' cannot be set up because it is in state " - f"{entry.state}, but needs to be in the {ConfigEntryState.NOT_LOADED} state" + f"The config entry '{entry.title}' ({entry.domain})" + f" with entry_id '{entry.entry_id}' cannot be set up" + f" because it is in state {entry.state}, but needs to" + f" be in the {ConfigEntryState.NOT_LOADED} state" ) # Setup Component if not set up yet @@ -2603,7 +2614,8 @@ class ConfigEntries: for listener in entry.update_listeners: self.hass.async_create_task( listener(self.hass, entry), - f"config entry update listener {entry.title} {entry.domain} {entry.domain}", + "config entry update listener" + f" {entry.title} {entry.domain} {entry.domain}", ) self._async_schedule_save() @@ -2999,7 +3011,9 @@ class ConfigFlow(ConfigEntryBaseFlow): def async_update_title_placeholders( self, title_placeholders: Mapping[str, str] ) -> None: - """Update title placeholders for the discovery notification and notify listeners. + """Update title placeholders for the discovery notification. + + Notifies listeners. This updates the flow context title_placeholders and notifies listeners (such as the frontend) to reload the flow state, updating the discovery @@ -3207,8 +3221,9 @@ class ConfigFlow(ConfigEntryBaseFlow): is called when the user ignores a discovered device or service, we then store the key for the flow being ignored. - Once the ignore config entry is created, ConfigEntriesFlowManager.async_finish_flow - will make sure the discovery key is kept up to date since it may not be stable + Once the ignore config entry is created, + ConfigEntriesFlowManager.async_finish_flow will make sure the + discovery key is kept up to date since it may not be stable unlike the unique id. """ await self.async_set_unique_id(user_input["unique_id"], raise_on_progress=False) @@ -3402,7 +3417,8 @@ class ConfigFlow(ConfigEntryBaseFlow): ) -> bool: """Update config entry and return result. - Internal to be used by update_and_abort and update_reload_and_abort methods only. + Internal to be used by update_and_abort and + update_reload_and_abort methods only. """ if data_updates is not UNDEFINED: @@ -3613,7 +3629,8 @@ class ConfigSubentryFlowManager( subentry_types = handler.async_get_supported_subentry_types(entry) if subentry_type not in subentry_types: raise data_entry_flow.UnknownHandler( - f"Config entry '{entry.domain}' does not support subentry '{subentry_type}'" + f"Config entry '{entry.domain}' does not support" + f" subentry '{subentry_type}'" ) subentry_flow = subentry_types[subentry_type]() subentry_flow.init_step = context["source"] @@ -3705,7 +3722,8 @@ class ConfigSubentryFlow( ) -> bool: """Update config subentry and return result. - Internal to be used by update_and_abort and update_reload_and_abort methods only. + Internal to be used by update_and_abort and + update_reload_and_abort methods only. """ if data_updates is not UNDEFINED: @@ -3866,7 +3884,8 @@ class OptionsFlowManager( if automatic_reload and entry.update_listeners: raise ValueError( - "Config entry update listeners should not be used with OptionsFlowWithReload" + "Config entry update listeners should not be" + " used with OptionsFlowWithReload" ) if ( diff --git a/homeassistant/core.py b/homeassistant/core.py index 7e563587668..e4cc5a91385 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -558,7 +558,8 @@ class HomeAssistant: return # For @callback targets, schedule directly via call_soon_threadsafe # to avoid the extra deferral through _async_add_hass_job + call_soon. - # Check iscoroutinefunction to gracefully handle incorrectly labeled @callback functions. + # Check iscoroutinefunction to gracefully handle + # incorrectly labeled @callback functions. if is_callback_check_partial(target) and not inspect.iscoroutinefunction( target ): @@ -2733,7 +2734,8 @@ class ServiceRegistry: If return_response=True, indicates that the caller can consume return values from the service, if any. Return values are a dict that can be returned by the - standard JSON serialization process. Return values can only be used with blocking=True. + standard JSON serialization process. Return values can only + be used with blocking=True. This method will fire an event to indicate the service has been called. diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 7c3df336268..be40bd1c220 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -263,7 +263,7 @@ class FlowManager(abc.ABC, Generic[_FlowContextT, _FlowResultT, _HandlerT]): matcher: Callable[[Any], bool], include_uninitialized: bool = False, ) -> list[_FlowResultT]: - """Return flows in progress init matching by data type as a partial FlowResult.""" + """Return flows in progress matching by data type.""" return self._async_flow_handler_to_flow_result( [ progress @@ -363,7 +363,8 @@ class FlowManager(abc.ABC, Generic[_FlowContextT, _FlowResultT, _HandlerT]): try: _map_error_to_schema_errors(schema_errors, error, data_schema) except ValueError: - # If we get here, the path in the exception does not exist in the schema. + # If we get here, the path in the exception + # does not exist in the schema. schema_errors.setdefault("base", []).append(str(error)) raise InvalidData( "Schema validation failed", @@ -585,7 +586,7 @@ class FlowManager(abc.ABC, Generic[_FlowContextT, _FlowResultT, _HandlerT]): flows: Iterable[FlowHandler[_FlowContextT, _FlowResultT, _HandlerT]], include_uninitialized: bool, ) -> list[_FlowResultT]: - """Convert a list of FlowHandler to a partial FlowResult that can be serialized.""" + """Convert a list of FlowHandler to a partial FlowResult.""" return [ self._flow_result( flow_id=flow.flow_id, diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 6a4cfca391b..62f8d25b393 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -59,9 +59,9 @@ class HomeAssistantError(Exception): def __str__(self) -> str: """Return exception message. - If no message was passed to `__init__`, the exception message is generated from - the translation_key. The message will be in English, regardless of the configured - language. + If no message was passed to `__init__`, the exception message + is generated from the translation_key. The message will be in + English, regardless of the configured language. """ if self._message: @@ -182,8 +182,10 @@ class ConditionErrorIndex(ConditionError): """Initialize condition error with index. Args: - index: The zero-based index of the failed condition, for conditions with multiple parts. - total: The total number of parts in this condition, including non-failed parts. + index: The zero-based index of the failed condition, + for conditions with multiple parts. + total: The total number of parts in this condition, + including non-failed parts. error: The error that this error wraps. """ super().__init__(type) diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 6cf685ea5c9..c7f302a41f6 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -106,7 +106,10 @@ async def async_check_ha_config_file( # noqa: C901 ) -> None: """Handle errors from packages.""" message = f"Setup of package '{package}' failed: {message}" - domain = f"homeassistant.packages.{package}{'.' + component if component is not None else ''}" + domain = ( + f"homeassistant.packages.{package}" + f"{'.' + component if component is not None else ''}" + ) pack_config = core_config[CONF_PACKAGES].get(package, config) result.add_warning(message, domain, pack_config) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 51dd0acc4b1..d242b7180ab 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -776,7 +776,10 @@ class EntityNumericalConditionBase(EntityConditionBase): return None def _get_tracked_value(self, entity_state: State) -> Any: - """Get the tracked value from a state, with unit validation for state-based values.""" + """Get the tracked value from a state. + + Includes unit validation for state-based values. + """ domain_spec = self._domain_specs[entity_state.domain] if domain_spec.value_source is None: if not self._is_valid_unit( diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 3ab9612a8ad..3fe4a3012fa 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1078,7 +1078,8 @@ def renamed( if old_key in value: if new_key in value: raise vol.Invalid( - f"Cannot specify both '{old_key}' and '{new_key}'. Please use '{new_key}' only." + f"Cannot specify both '{old_key}' and" + f" '{new_key}'. Please use '{new_key}' only." ) value[new_key] = value.pop(old_key) @@ -1840,7 +1841,8 @@ def _trigger_pre_validator(value: Any | None) -> Any: if CONF_TRIGGER in value: if CONF_PLATFORM in value: raise vol.Invalid( - "Cannot specify both 'platform' and 'trigger'. Please use 'trigger' only." + "Cannot specify both 'platform' and 'trigger'." + " Please use 'trigger' only." ) value = dict(value) value[CONF_PLATFORM] = value.pop(CONF_TRIGGER) @@ -1865,7 +1867,7 @@ _base_trigger_validator_schema = TRIGGER_BASE_SCHEMA.extend({}, extra=vol.ALLOW_ def _base_trigger_list_flatten(triggers: list[Any]) -> list[Any]: - """Flatten trigger arrays containing 'triggers:' sublists into a single list of triggers.""" + """Flatten trigger arrays with 'triggers:' sublists into one list.""" flatlist = [] for t in triggers: if CONF_TRIGGERS in t and len(t) == 1: diff --git a/homeassistant/helpers/debounce.py b/homeassistant/helpers/debounce.py index 190947636cc..9b16a1db247 100644 --- a/homeassistant/helpers/debounce.py +++ b/homeassistant/helpers/debounce.py @@ -181,7 +181,10 @@ class Debouncer[_R_co]: if not self._execute_at_end_of_timer: return self._execute_at_end_of_timer = False - name = f"debouncer {self._job} finish cooldown={self.cooldown}, immediate={self.immediate}" + name = ( + f"debouncer {self._job} finish" + f" cooldown={self.cooldown}, immediate={self.immediate}" + ) if not self._background: self.hass.async_create_task( self._handle_timer_finish(), name, eager_start=True diff --git a/homeassistant/helpers/device.py b/homeassistant/helpers/device.py index bf0e2ab31be..2d90a9c7914 100644 --- a/homeassistant/helpers/device.py +++ b/homeassistant/helpers/device.py @@ -109,7 +109,8 @@ def async_remove_stale_devices_links_keep_current_device( continue ent_reg.async_update_entity(entity.entity_id, device_id=current_device_id) - # Removes all devices from the config entry that are not the same as the current device + # Removes all devices from the config entry that are not the same + # as the current device for device in dev_reg.devices.get_devices_for_config_entry_id(entry_id): if device.id == current_device_id: continue diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index df7cc565279..c022c470036 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -410,8 +410,8 @@ class DeviceEntry: "configuration_url": self.configuration_url, "config_entries": list(self.config_entries), "config_entries_subentries": { - config_entry_id: list(subentries) - for config_entry_id, subentries in self.config_entries_subentries.items() + entry_id: list(subentries) + for entry_id, subentries in self.config_entries_subentries.items() }, "connections": list(self.connections), "created_at": self.created_at.timestamp(), @@ -460,8 +460,8 @@ class DeviceEntry: # representation in HA Core 2026.2 "config_entries": list(self.config_entries), "config_entries_subentries": { - config_entry_id: list(subentries) - for config_entry_id, subentries in self.config_entries_subentries.items() + entry_id: list(subentries) + for entry_id, subentries in self.config_entries_subentries.items() }, "configuration_url": self.configuration_url, "connections": list(self.connections), @@ -559,8 +559,8 @@ class DeletedDeviceEntry: # representation in HA Core 2026.2 "config_entries": list(self.config_entries), "config_entries_subentries": { - config_entry_id: list(subentries) - for config_entry_id, subentries in self.config_entries_subentries.items() + entry_id: list(subentries) + for entry_id, subentries in self.config_entries_subentries.items() }, "connections": list(self.connections), "created_at": self.created_at, @@ -1093,8 +1093,10 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]): ) -> DeviceEntry | None: """Private update device attributes. - :param add_config_subentry_id: Add the device to a specific subentry of add_config_entry_id - :param remove_config_subentry_id: Remove the device from a specific subentry of remove_config_entry_id + :param add_config_subentry_id: Add the device to a specific + subentry of add_config_entry_id + :param remove_config_subentry_id: Remove the device from a + specific subentry of remove_config_entry_id """ old = self.devices[device_id] @@ -1126,7 +1128,8 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]): and add_config_subentry_id not in add_config_entry.subentries # type: ignore[union-attr] ): raise HomeAssistantError( - f"Config entry {add_config_entry_id} has no subentry {add_config_subentry_id}" + f"Config entry {add_config_entry_id} has no" + f" subentry {add_config_subentry_id}" ) if ( @@ -1182,8 +1185,9 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]): # Enable the device if it was disabled by config entry and we're adding # a non disabled config entry if ( - # mypy says add_config_entry can be None. That's impossible, because we - # raise above if that happens + # mypy says add_config_entry can be None. + # That's impossible, because we raise above if + # that happens not add_config_entry.disabled_by # type: ignore[union-attr] and old.disabled_by is DeviceEntryDisabler.CONFIG_ENTRY ): @@ -1391,8 +1395,10 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]): ) -> DeviceEntry | None: """Update device attributes. - :param add_config_subentry_id: Add the device to a specific subentry of add_config_entry_id - :param remove_config_subentry_id: Remove the device from a specific subentry of remove_config_entry_id + :param add_config_subentry_id: Add the device to a specific + subentry of add_config_entry_id + :param remove_config_subentry_id: Remove the device from a + specific subentry of remove_config_entry_id """ if suggested_area is not UNDEFINED: report_usage( diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 0f27dabc4cc..d8e21bdd7b3 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -373,7 +373,8 @@ class CachedProperties(type): attr_name = f"_attr_{property_name}" private_attr_name = f"__attr_{property_name}" # Check if an _attr_ class attribute exits and move it to __attr_. We check - # __dict__ here because we don't care about _attr_ class attributes in parents. + # __dict__ here because we don't care about _attr_ class + # attributes in parents. if attr_name in cls.__dict__: attr = getattr(cls, attr_name) if isinstance(attr, (FunctionType, property)): @@ -388,7 +389,8 @@ class CachedProperties(type): else: def wrapped_annotate(format: Format) -> dict[str, Any]: - # Note: to avoid complicating things, we only support FORWARDREF + # Note: to avoid complicating things, + # we only support FORWARDREF return annotations cls.__annotate__ = wrapped_annotate @@ -412,8 +414,9 @@ class CachedProperties(type): if property_name in seen_props: continue attr_name = f"_attr_{property_name}" - # Check if an _attr_ class attribute exits. We check __dict__ here because - # we don't care about _attr_ class attributes in parents. + # Check if an _attr_ class attribute exists. + # We check __dict__ here because we don't care + # about _attr_ class attributes in parents. if (attr_name) not in cls.__dict__: continue wrap_attr(cls, property_name) @@ -731,7 +734,8 @@ class Entity( return device_class_name return description_name - # The entity has no name set by _attr_name, translation_key or entity_description + # The entity has no name set by _attr_name, translation_key + # or entity_description # Check if the entity should be named by its device class if self._default_to_device_class_name(): return device_class_name @@ -1184,8 +1188,9 @@ class Entity( self._disabled_reported = True _LOGGER.warning( ( - "Entity %s is incorrectly being triggered for updates while it" - " is disabled. This is a bug in the %s integration" + "Entity %s is incorrectly being triggered" + " for updates while it is disabled." + " This is a bug in the %s integration" ), self.entity_id, self.platform.platform_name, @@ -1204,8 +1209,9 @@ class Entity( time_now = timer() if entry := self.registry_entry: - # Make sure capabilities and other data in the entity registry are up to date. - # Capabilities include capability attributes, device class and supported features. + # Make sure capabilities and other data in the entity + # registry are up to date. Capabilities include capability + # attributes, device class and supported features. supported_features = supported_features or 0 if ( capabilities != entry.capabilities diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index e92485ccc21..412c2b7c074 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -67,7 +67,8 @@ class EntityComponent[_EntityT: entity.Entity = entity.Entity]: as 'hue.light'. This class has the following responsibilities: - - Process the configuration and set up a platform based component, for example light. + - Process the configuration and set up a platform based component, + for example light. - Manage the platforms and their entities. - Help extract the entities from a service call. - Listen for discovery events for platforms related to the domain. diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 623cc370b6b..3ec63e06980 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -328,7 +328,8 @@ class EntityPlatform: if "custom_components" in self.platform.__file__: # type: ignore[attr-defined] self.logger.warning( ( - "The %s platform module for the %s custom integration does not implement" + "The %s platform module for the %s custom" + " integration does not implement" " async_setup_platform or setup_platform." ), self.platform_name, @@ -581,7 +582,8 @@ class EntityPlatform: update_before_add=update_before_add, config_subentry_id=config_subentry_id, ), - f"EntityPlatform async_add_entities_for_entry {self.domain}.{self.platform_name}", + "EntityPlatform async_add_entities_for_entry" + f" {self.domain}.{self.platform_name}", eager_start=True, ) @@ -712,8 +714,9 @@ class EntityPlatform: or config_subentry_id not in self.config_entry.subentries ): raise HomeAssistantError( - f"Can't add entities to unknown subentry {config_subentry_id} of config " - f"entry {self.config_entry.entry_id if self.config_entry else None}" + f"Can't add entities to unknown subentry" + f" {config_subentry_id} of config entry" + f" {self.config_entry.entry_id if self.config_entry else None}" ) entities: list[Entity] = ( @@ -843,8 +846,9 @@ class EntityPlatform: ) if domain != self.domain: report_usage( - f"sets an entity ID with wrong domain: '{entity.entity_id}'. " - f"Expected domain is '{self.domain}'", + "sets an entity ID with wrong domain:" + f" '{entity.entity_id}'." + f" Expected domain is '{self.domain}'", integration_domain=self.platform_name, breaks_in_ha_version="2027.5.0", ) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 9e5f75a37b4..326ee6b4de0 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -859,8 +859,8 @@ class EntityRegistryStore(storage.Store[dict[str, list[dict[str, Any]]]]): entity["name"] = None entity["options"] = {} if old_minor_version < 19: - # Version 1.19 adds undefined flags to deleted entities, this is a bugfix - # of version 1.18 + # Version 1.19 adds undefined flags to deleted + # entities, this is a bugfix of version 1.18 set_to_undefined = old_minor_version < 18 for entity in data["deleted_entities"]: entity["disabled_by_undefined"] = set_to_undefined @@ -895,10 +895,10 @@ class EntityRegistryStore(storage.Store[dict[str, list[dict[str, Any]]]]): entity["name"] = name if old_minor_version < 22: - # Version 1.22 adds support for COMPUTED_NAME in aliases and starts preserving - # their order. - # To avoid a major version bump, we keep the old aliases as-is and use aliases_v2 - # field instead. + # Version 1.22 adds support for COMPUTED_NAME in + # aliases and starts preserving their order. + # To avoid a major version bump, we keep the old + # aliases as-is and use aliases_v2 field instead. for entity in data["entities"]: entity["aliases_v2"] = [None, *entity["aliases"]] @@ -1082,7 +1082,8 @@ def _validate_item( and not isinstance(entity_category, EntityCategory) ): raise ValueError( - f"entity_category must be a valid EntityCategory instance, got {entity_category}" + "entity_category must be a valid EntityCategory" + f" instance, got {entity_category}" ) if ( hidden_by @@ -1172,8 +1173,9 @@ class EntityRegistry(BaseRegistry): This function is deprecated. Use `async_get_available_entity_id` instead. - Entity ID conflicts are checked against registered and currently existing entities, - as well as provided `reserved_entity_ids`. + Entity ID conflicts are checked against registered and + currently existing entities, as well as provided + `reserved_entity_ids`. """ report_usage( "calls `entity_registry.async_generate_entity_id`, " @@ -1201,8 +1203,9 @@ class EntityRegistry(BaseRegistry): ) -> str: """Get next available entity ID. - Entity ID conflicts are checked against registered and currently existing entities, - as well as provided `reserved_entity_ids`. + Entity ID conflicts are checked against registered and + currently existing entities, as well as provided + `reserved_entity_ids`. """ preferred_string = f"{domain}.{slugify(suggested_object_id)}" @@ -1277,8 +1280,9 @@ class EntityRegistry(BaseRegistry): ) -> str: """Regenerate an entity ID for an entry. - Entity ID conflicts are checked against registered and currently existing entities, - as well as provided `reserved_entity_ids`. + Entity ID conflicts are checked against registered and + currently existing entities, as well as provided + `reserved_entity_ids`. """ return self._async_generate_entity_id( current_entity_id=entry.entity_id, @@ -1602,8 +1606,8 @@ class EntityRegistry(BaseRegistry): ): self.async_remove(entity.entity_id) - # Remove entities which belong to config subentries no longer associated with the - # device + # Remove entities which belong to config subentries no longer + # associated with the device if old_config_entries_subentries := changes.get("config_entries_subentries"): entities = async_entries_for_device( self, event.data["device_id"], include_disabled_entities=True diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 2a1ef3964db..cdbfc10d822 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -196,7 +196,11 @@ class MatchFailedError(IntentError): def __str__(self) -> str: """Return string representation.""" - return f"" + return ( + f"" + ) class NoStatesMatchedError(MatchFailedError): @@ -258,7 +262,10 @@ class MatchFailedReason(Enum): """Floor name from constraint does not exist.""" DUPLICATE_NAME = auto() - """Two or more entities matched the same name constraint and could not be disambiguated.""" + """Two or more entities matched the same name constraint. + + Could not be disambiguated. + """ MULTIPLE_TARGETS = auto() """Two or more entities matched when a single target is required.""" @@ -286,7 +293,10 @@ class MatchTargetsResult: """List of matched entity states.""" no_match_name: str | None = None - """Name of invalid area/floor or duplicate name when match fails for those reasons.""" + """Name of invalid area/floor or duplicate name. + + Used when match fails for those reasons. + """ areas: list[ar.AreaEntry] = field(default_factory=list) """Areas that were targeted.""" @@ -346,7 +356,7 @@ class MatchTargetsConstraints: @dataclass class MatchTargetsPreferences: - """Preferences used to disambiguate duplicate name matches in async_match_targets.""" + """Preferences to disambiguate duplicate name matches.""" area_id: str | None = None """Id of area to use when deduplicating names.""" @@ -778,7 +788,10 @@ def async_match_states( states: list[State] | None = None, assistant: str | None = None, ) -> Iterable[State]: - """Simplified interface to async_match_targets that returns states matching the constraints.""" + """Return states matching the constraints. + + Simplified interface to async_match_targets. + """ result = async_match_targets( hass, constraints=MatchTargetsConstraints( @@ -1414,7 +1427,7 @@ class IntentResponse: def async_set_states( self, matched_states: list[State], unmatched_states: list[State] | None = None ) -> None: - """Set entity states that were matched or not matched during intent handling (query).""" + """Set matched/unmatched entity states during intent handling.""" self.matched_states = matched_states self.unmatched_states = unmatched_states or [] diff --git a/homeassistant/helpers/llm.py b/homeassistant/helpers/llm.py index d8baf9862c5..09273d3b516 100644 --- a/homeassistant/helpers/llm.py +++ b/homeassistant/helpers/llm.py @@ -71,18 +71,35 @@ NO_ENTITIES_PROMPT = ( "to their voice assistant in Home Assistant." ) -DYNAMIC_CONTEXT_PROMPT = """You ARE equipped to answer questions about the current state of -the home using the `GetLiveContext` tool. This is a primary function. Do not state you lack the -functionality if the question requires live data. -If the user asks about device existence/type (e.g., "Do I have lights in the bedroom?"): Answer -from the static context below. -If the user asks about the CURRENT state, value, or mode (e.g., "Is the lock locked?", -"Is the fan on?", "What mode is the thermostat in?", "What is the temperature outside?"): - 1. Recognize this requires live data. - 2. You MUST call `GetLiveContext`. This tool will provide the needed real-time information (like temperature from the local weather, lock status, etc.). - 3. Use the tool's response** to answer the user accurately (e.g., "The temperature outside is [value from tool]."). -For general knowledge questions not about the home: Answer truthfully from internal knowledge. -""" +DYNAMIC_CONTEXT_PROMPT = ( + "You ARE equipped to answer questions about the" + " current state of\n" + "the home using the `GetLiveContext` tool." + " This is a primary function." + " Do not state you lack the\n" + "functionality if the question requires live data.\n" + "If the user asks about device existence/type" + ' (e.g., "Do I have lights in the bedroom?"):' + " Answer\n" + "from the static context below.\n" + "If the user asks about the CURRENT state, value," + ' or mode (e.g., "Is the lock locked?",\n' + '"Is the fan on?",' + ' "What mode is the thermostat in?",' + ' "What is the temperature outside?"):\n' + " 1. Recognize this requires live data.\n" + " 2. You MUST call `GetLiveContext`." + " This tool will provide the needed real-time" + " information (like temperature from the local" + " weather, lock status, etc.).\n" + " 3. Use the tool's response** to answer the" + " user accurately" + ' (e.g., "The temperature outside is' + ' [value from tool].").\n' + "For general knowledge questions not about the" + " home: Answer truthfully from internal" + " knowledge.\n" +) @callback @@ -511,7 +528,11 @@ class AssistAPI(API): if area.floor_id: floor = floor_reg.async_get_floor(area.floor_id) - extra = "and all generic commands like 'turn on the lights' should target this area." + extra = ( + "and all generic commands like" + " 'turn on the lights' should target" + " this area." + ) if floor and area: prompt.append(f"You are in area {area.name} (floor {floor.name}) {extra}") @@ -520,7 +541,8 @@ class AssistAPI(API): else: prompt.append( "When a user asks to turn on all devices of a specific type, " - "ask user to specify an area, unless there is only one device of that type." + "ask user to specify an area, unless there" + " is only one device of that type." ) if not llm_context.device_id or not async_device_supports_timers( @@ -541,7 +563,8 @@ class AssistAPI(API): if exposed_entities and exposed_entities["entities"]: prompt.append( - "Static Context: An overview of the areas and the devices in this smart home:" + "Static Context: An overview of the areas" + " and the devices in this smart home:" ) prompt.append(yaml_util.dump(list(exposed_entities["entities"].values()))) @@ -1105,7 +1128,9 @@ class TodoGetItemsTool(Tool): name = "todo_get_items" description = ( "Query a to-do list to find out what items are on it. " - "Use this to answer questions like 'What's on my task list?' or 'Read my grocery list'. " + "Use this to answer questions like " + "'What's on my task list?' or " + "'Read my grocery list'. " "Filters items by status (needs_action, completed, all)." ) @@ -1116,7 +1141,11 @@ class TodoGetItemsTool(Tool): vol.Required("todo_list"): vol.In(todo_lists), vol.Optional( "status", - description="Filter returned items by status, by default returns incomplete items", + description=( + "Filter returned items by status," + " by default returns incomplete" + " items" + ), default="needs_action", ): vol.In(["needs_action", "completed", "all"]), } @@ -1188,12 +1217,21 @@ class GetLiveContextTool(Tool): name = "GetLiveContext" description = ( - "Provides real-time information about the CURRENT state, value, or mode of devices, sensors, entities, or areas. " + "Provides real-time information about the" + " CURRENT state, value, or mode of devices," + " sensors, entities, or areas. " "Use this tool for: " - "1. Answering questions about current conditions (e.g., 'Is the light on?'). " - "2. As the first step in conditional actions (e.g., 'If the weather is rainy, turn off sprinklers' requires checking the weather first). " - "You may filter for devices by name, domain, and area, including combining those filters. " - "Prefer filtering by domain when searching for multiple devices of the same type." + "1. Answering questions about current" + " conditions (e.g., 'Is the light on?'). " + "2. As the first step in conditional actions" + " (e.g., 'If the weather is rainy, turn off" + " sprinklers' requires checking the weather" + " first). " + "You may filter for devices by name, domain," + " and area, including combining those" + " filters. " + "Prefer filtering by domain when searching" + " for multiple devices of the same type." ) parameters = vol.Schema( { @@ -1203,7 +1241,11 @@ class GetLiveContextTool(Tool): ): cv.string, vol.Optional( "domain", - description="Filter entities by domain (e.g. 'light', 'sensor'). Accepts a single domain or a list.", + description=( + "Filter entities by domain" + " (e.g. 'light', 'sensor')." + " Accepts a single domain or a list." + ), ): vol.Any(cv.string, [cv.string]), vol.Optional( "area", @@ -1278,7 +1320,8 @@ class GetLiveContextTool(Tool): entities = list(exposed_entities["entities"].values()) prompt = [ - "Live Context: An overview of the areas and the devices in this smart home:", + "Live Context: An overview of the areas" + " and the devices in this smart home:", yaml_util.dump(entities), ] return { diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 3359a6f30ea..832ed319b67 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -195,7 +195,8 @@ def get_url( ) except HassioNotReadyError: _LOGGER.debug( - "Could not retrieve Supervisor host information, list of known URLs will be incomplete" + "Could not retrieve Supervisor host information," + " list of known URLs will be incomplete" ) if ( diff --git a/homeassistant/helpers/normalized_name_base_registry.py b/homeassistant/helpers/normalized_name_base_registry.py index 983d9e55340..c651adbf9b9 100644 --- a/homeassistant/helpers/normalized_name_base_registry.py +++ b/homeassistant/helpers/normalized_name_base_registry.py @@ -52,7 +52,8 @@ class NormalizedNameBaseRegistryItems[_VT: NormalizedNameBaseRegistryEntry]( and normalized_name in self._normalized_names ): raise ValueError( - f"The name {replacement_entry.name} ({normalized_name}) is already in use" + f"The name {replacement_entry.name}" + f" ({normalized_name}) is already in use" ) del self._normalized_names[old_entry.normalized_name] diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index dcac1e319e1..fe8a721e138 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -889,7 +889,8 @@ class _ScriptRun: if iteration > REPEAT_TERMINATE_ITERATIONS: self._log( - "While condition %s terminated because it looped %s times", + "While condition %s terminated because" + " it looped %s times", repeat[CONF_WHILE], REPEAT_TERMINATE_ITERATIONS, level=logging.CRITICAL, @@ -1015,7 +1016,8 @@ class _ScriptRun: if supports_response == SupportsResponse.NONE and return_response: raise vol.Invalid( f"Script does not support '{CONF_RESPONSE_VARIABLE}' for service " - f"'{params[CONF_DOMAIN]}.{params[CONF_SERVICE]}' which does not support response data." + f"'{params[CONF_DOMAIN]}.{params[CONF_SERVICE]}'" + " which does not support response data." ) running_script = ( @@ -1837,7 +1839,8 @@ class Script: variables = ScriptRunVariables.create_top_level(run_variables) variables["context"] = context else: - # This is not the top level script, run_variables is an instance of ScriptRunVariables + # This is not the top level script, run_variables + # is an instance of ScriptRunVariables variables = cast(ScriptRunVariables, run_variables) # Prevent non-allowed recursive calls which will cause deadlocks when we try to diff --git a/homeassistant/helpers/script_variables.py b/homeassistant/helpers/script_variables.py index 9140c342dfc..88f1809b8fa 100644 --- a/homeassistant/helpers/script_variables.py +++ b/homeassistant/helpers/script_variables.py @@ -91,15 +91,19 @@ class ScriptVariables: class _ParallelData: """Data used in each parallel sequence.""" - # `protected` is for variables that need special protection in parallel sequences. - # What this means is that such a variable defined in one parallel sequence will not be - # clobbered by the variable with the same name assigned in another parallel sequence. - # It also means that such a variable will not be visible in the outer scope. + # `protected` is for variables that need special protection + # in parallel sequences. What this means is that such a + # variable defined in one parallel sequence will not be + # clobbered by the variable with the same name assigned in + # another parallel sequence. It also means that such a + # variable will not be visible in the outer scope. # Currently the only such variable is `wait`. protected: dict[str, Any] = field(default_factory=dict) - # `outer_scope_writes` is for variables that are written to the outer scope from - # a parallel sequence. This is used for generating correct traces of changed variables - # for each of the parallel sequences, isolating them from one another. + # `outer_scope_writes` is for variables that are written + # to the outer scope from a parallel sequence. This is used + # for generating correct traces of changed variables for + # each of the parallel sequences, isolating them from one + # another. outer_scope_writes: dict[str, Any] = field(default_factory=dict) @@ -107,11 +111,14 @@ class _ParallelData: class ScriptRunVariables(UserDict[str, Any]): """Class to hold script run variables. - The purpose of this class is to provide proper variable scoping semantics for scripts. - Each instance institutes a new local scope, in which variables can be defined. - Each instance has a reference to the previous instance, except for the top-level instance. - The instances therefore form a chain, in which variable lookup and assignment is performed. - The variables defined lower in the chain naturally override those defined higher up. + The purpose of this class is to provide proper variable + scoping semantics for scripts. Each instance institutes a + new local scope, in which variables can be defined. Each + instance has a reference to the previous instance, except + for the top-level instance. The instances therefore form a + chain, in which variable lookup and assignment is performed. + The variables defined lower in the chain naturally override + those defined higher up. """ # _previous is the previous ScriptRunVariables in the chain @@ -124,7 +131,8 @@ class ScriptRunVariables(UserDict[str, Any]): # _parallel_data is used for each parallel sequence _parallel_data: _ParallelData | None = None - # _non_parallel_scope includes all scopes all the way to the most recent parallel split + # _non_parallel_scope includes all scopes all the way to + # the most recent parallel split _non_parallel_scope: ChainMap[str, Any] # _full_scope includes all scopes (all the way to the top-level) _full_scope: ChainMap[str, Any] @@ -202,10 +210,12 @@ class ScriptRunVariables(UserDict[str, Any]): def _assign(self, key: str, value: Any, *, parallel_protected: bool) -> None: """Assign value to a variable. - Value is always assigned to the variable in the nearest scope, in which it is defined. - If the variable is not defined at all, it is created in the top-level scope. + Value is always assigned to the variable in the nearest + scope, in which it is defined. If the variable is not + defined at all, it is created in the top-level scope. - :param parallel_protected: Whether variable is to be protected in parallel sequences. + :param parallel_protected: Whether variable is to be + protected in parallel sequences. """ if self._local_data is not None and key in self._local_data: self._local_data[key] = value diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index eabedcbf579..f49d556507c 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -55,9 +55,12 @@ class Selector[_T: Mapping[str, Any]]: CONFIG_SCHEMA: Callable config: _T selector_type: str - # Context keys that are allowed to be used in the selector, with list of allowed selector types. - # Selectors can use the value of other fields in the same schema as context for filtering for example. - # The selector defines which context keys it supports and what selector types are allowed for each key. + # Context keys that are allowed to be used in the + # selector, with list of allowed selector types. Selectors + # can use the value of other fields in the same schema as + # context for filtering for example. The selector defines + # which context keys it supports and what selector types + # are allowed for each key. allowed_context_keys: dict[str, set[str]] = {} def __init__(self, config: Mapping[str, Any] | None = None) -> None: @@ -298,7 +301,11 @@ AddonSelectorConfig = AppSelectorConfig @SELECTORS.register("addon") class AddonSelector(Selector[AddonSelectorConfig]): - """Selector of an add-on, kept for backward compatibility after add-ons -> apps rename.""" + """Selector of an add-on. + + Kept for backward compatibility after add-ons -> apps + rename. + """ selector_type = "addon" diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 72f77254f7e..4cc7a96a19c 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -1184,7 +1184,8 @@ def _validate_entity_service_schema( return cv.make_entity_service_schema(schema) if not cv.is_entity_service_schema(schema): raise HomeAssistantError( - f"The {service} service registers an entity service with a non entity service schema" + f"The {service} service registers an entity service" + " with a non entity service schema" ) return schema diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index 180e2481d72..d57a06b7409 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -369,7 +369,8 @@ class Store[_T: Mapping[str, Any] | Sequence[Any]]: except HomeAssistantError as err: if isinstance(err.__cause__, JSONDecodeError): # If we have a JSONDecodeError, it means the file is corrupt. - # We can't recover from this, so we'll log an error, rename the file and + # We can't recover from this, so we'll log + # an error, rename the file and # return None so that we can start with a clean slate which will # allow startup to continue so they can restore from a backup. isotime = dt_util.utcnow().isoformat() diff --git a/homeassistant/helpers/target.py b/homeassistant/helpers/target.py index 04aa9c8bf0b..1d716a281a1 100644 --- a/homeassistant/helpers/target.py +++ b/homeassistant/helpers/target.py @@ -301,7 +301,7 @@ class TargetEntityChangeTracker(abc.ABC): @abc.abstractmethod @callback def _handle_entities_update(self, tracked_entities: set[str]) -> None: - """Called when there's an update to the list of entities of the tracked targets.""" + """Called when there's an update to tracked target entities.""" @callback def _handle_target_update(self, event: Event[Any] | None = None) -> None: @@ -321,7 +321,8 @@ class TargetEntityChangeTracker(abc.ABC): """Set up listeners for registry changes that require resubscription.""" # Subscribe to registry updates that can change the entities to track: - # - Entity registry: entity added/removed; entity labels changed; entity area changed. + # - Entity registry: entity added/removed; + # entity labels changed; entity area changed. # - Device registry: device labels changed; device area changed. # - Area registry: area floor changed. # @@ -412,15 +413,19 @@ def async_track_target_selector_state_change_event( *, primary_entities_only: bool = True, ) -> CALLBACK_TYPE: - """Track state changes for entities referenced directly or indirectly in a target selector. + """Track state changes for entities in a target selector. - When `primary_entities_only` is True, indirect target expansion (via device, area, - and floor) skips entities with an `entity_category` (i.e. config or diagnostic entities). + Tracks entities referenced directly or indirectly. + When `primary_entities_only` is True, indirect target + expansion (via device, area, and floor) skips entities + with an `entity_category` (config or diagnostic entities). """ target_selection = TargetSelection(target_selector_config) if not target_selection.has_any_target: raise HomeAssistantError( - f"Target selector {target_selector_config} does not have any selectors defined" + "Target selector" + f" {target_selector_config}" + " does not have any selectors defined" ) tracker = TargetStateChangeTracker( hass, diff --git a/homeassistant/helpers/template/__init__.py b/homeassistant/helpers/template/__init__.py index 9f4d769f242..cbd22b96d08 100644 --- a/homeassistant/helpers/template/__init__.py +++ b/homeassistant/helpers/template/__init__.py @@ -359,7 +359,8 @@ class Template: if len(render_result) > MAX_TEMPLATE_OUTPUT: raise TemplateError( - f"Template output exceeded maximum size of {MAX_TEMPLATE_OUTPUT} characters" + "Template output exceeded maximum size of" + f" {MAX_TEMPLATE_OUTPUT} characters" ) render_result = render_result.strip() @@ -674,7 +675,7 @@ def _get_hass_loader(hass: HomeAssistant) -> HassLoader: class HassLoader(jinja2.BaseLoader): - """An in-memory jinja loader that keeps track of templates that need to be reloaded.""" + """An in-memory jinja loader that tracks templates needing reload.""" def __init__(self, sources: dict[str, str]) -> None: """Initialize an empty HassLoader.""" diff --git a/homeassistant/helpers/template/context.py b/homeassistant/helpers/template/context.py index c26338d9396..3490baf4e5a 100644 --- a/homeassistant/helpers/template/context.py +++ b/homeassistant/helpers/template/context.py @@ -17,7 +17,7 @@ class TemplateContextManager(AbstractContextManager): """Context manager to store template being parsed or rendered in a ContextVar.""" def set_template(self, template_str: str, action: str) -> None: - """Store template being parsed or rendered in a Contextvar to aid error handling.""" + """Store template being parsed/rendered to aid error handling.""" template_cv.set((template_str, action)) def __exit__( diff --git a/homeassistant/helpers/template/extensions/areas.py b/homeassistant/helpers/template/extensions/areas.py index a32df1e5e6e..226be1a464c 100644 --- a/homeassistant/helpers/template/extensions/areas.py +++ b/homeassistant/helpers/template/extensions/areas.py @@ -99,8 +99,9 @@ class AreaExtension(BaseTemplateExtension): # If entity has an area ID, get the area name for that if entity.area_id: return self._get_area_name(area_reg, entity.area_id) - # If entity has a device ID and the device exists with an area ID, get the - # area name for that + # If entity has a device ID and the device + # exists with an area ID, get the area name + # for that if ( entity.device_id and (device := dev_reg.async_get(entity.device_id)) @@ -129,8 +130,9 @@ class AreaExtension(BaseTemplateExtension): entry.entity_id for entry in er.async_entries_for_area(ent_reg, _area_id) ] dev_reg = dr.async_get(self.hass) - # We also need to add entities tied to a device in the area that don't themselves - # have an area specified since they inherit the area from the device. + # We also need to add entities tied to a device in the + # area that don't themselves have an area specified + # since they inherit the area from the device. entity_ids.extend( [ entity.entity_id diff --git a/homeassistant/helpers/template/extensions/base.py b/homeassistant/helpers/template/extensions/base.py index 41672c0e560..8a1a52b3106 100644 --- a/homeassistant/helpers/template/extensions/base.py +++ b/homeassistant/helpers/template/extensions/base.py @@ -77,7 +77,8 @@ class BaseTemplateExtension(Extension): if template_func.requires_hass and self.environment.hass is None: continue - # Register unsupported stub for functions not allowed in limited environments + # Register unsupported stub for functions not + # allowed in limited environments if self.environment.limited and not template_func.limited_ok: unsupported_func = self._create_unsupported_function( template_func.name @@ -107,7 +108,7 @@ class BaseTemplateExtension(Extension): @staticmethod def _create_unsupported_function(name: str) -> Callable[[], NoReturn]: - """Create a function that raises an error for unsupported functions in limited templates.""" + """Create a function that raises for unsupported limited template functions.""" def unsupported(*args: Any, **kwargs: Any) -> NoReturn: raise TemplateError( diff --git a/homeassistant/helpers/template/extensions/datetime.py b/homeassistant/helpers/template/extensions/datetime.py index 8ca8eea61cf..476437c0483 100644 --- a/homeassistant/helpers/template/extensions/datetime.py +++ b/homeassistant/helpers/template/extensions/datetime.py @@ -265,8 +265,9 @@ class DateTimeExtension(BaseTemplateExtension): If the input are not a datetime object the input will be returned unmodified. - Note: This template function is deprecated in favor of `time_until`, but is still - supported so as not to break old templates. + Note: This template function is deprecated in favor + of `time_until`, but is still supported so as not to + break old templates. """ if (render_info := render_info_cv.get()) is not None: render_info.has_time = True diff --git a/homeassistant/helpers/template/extensions/floors.py b/homeassistant/helpers/template/extensions/floors.py index 89a24f3d936..934ec50ff35 100644 --- a/homeassistant/helpers/template/extensions/floors.py +++ b/homeassistant/helpers/template/extensions/floors.py @@ -70,7 +70,7 @@ class FloorExtension(BaseTemplateExtension): return [floor.floor_id for floor in floor_registry.async_list_floors()] def floor_id(self, lookup_value: Any) -> str | None: - """Get the floor ID from a floor or area name, alias, device id, or entity id.""" + """Get the floor ID from a floor/area name, alias, device/entity id.""" floor_registry = fr.async_get(self.hass) lookup_str = str(lookup_value) diff --git a/homeassistant/helpers/template/extensions/functional.py b/homeassistant/helpers/template/extensions/functional.py index 617ca61d741..61a0528ea7e 100644 --- a/homeassistant/helpers/template/extensions/functional.py +++ b/homeassistant/helpers/template/extensions/functional.py @@ -175,7 +175,9 @@ class FunctionalExtension(BaseTemplateExtension): if isinstance(dict_in_list, dict): if ATTR_ENTITY_ID in dict_in_list: raise ValueError( - f"Response dictionary already contains key '{ATTR_ENTITY_ID}'" + "Response dictionary already" + " contains key" + f" '{ATTR_ENTITY_ID}'" ) dict_in_list[ATTR_ENTITY_ID] = entity_id dict_in_list["value_key"] = value_key diff --git a/homeassistant/helpers/template/extensions/math.py b/homeassistant/helpers/template/extensions/math.py index c1c1f55d571..8bb2e032ae9 100644 --- a/homeassistant/helpers/template/extensions/math.py +++ b/homeassistant/helpers/template/extensions/math.py @@ -167,8 +167,10 @@ class MathExtension(BaseTemplateExtension): def arc_tangent2(*args: Any, default: Any = _SENTINEL) -> Any: """Filter and function to calculate four quadrant arc tangent of y / x. - The parameters to atan2 may be passed either in an iterable or as separate arguments - The default value may be passed either as a positional or in a keyword argument + The parameters to atan2 may be passed either in an + iterable or as separate arguments. The default value + may be passed either as a positional or in a keyword + argument. """ try: if 1 <= len(args) <= 2 and isinstance(args[0], (list, tuple)): @@ -207,8 +209,9 @@ class MathExtension(BaseTemplateExtension): if len(args) == 0: raise TypeError("average expected at least 1 argument, got 0") - # If first argument is iterable and more than 1 argument provided but not a named - # default, then use 2nd argument as default. + # If first argument is iterable and more than 1 + # argument provided but not a named default, + # then use 2nd argument as default. if isinstance(args[0], Iterable): average_list = args[0] if len(args) > 1 and default is _SENTINEL: @@ -236,8 +239,9 @@ class MathExtension(BaseTemplateExtension): if len(args) == 0: raise TypeError("median expected at least 1 argument, got 0") - # If first argument is a list or tuple and more than 1 argument provided but not a named - # default, then use 2nd argument as default. + # If first argument is a list or tuple and more than 1 + # argument provided but not a named default, + # then use 2nd argument as default. if isinstance(args[0], Iterable): median_list = args[0] if len(args) > 1 and default is _SENTINEL: @@ -265,8 +269,9 @@ class MathExtension(BaseTemplateExtension): if not args: raise TypeError("statistical_mode expected at least 1 argument, got 0") - # If first argument is a list or tuple and more than 1 argument provided but not a named - # default, then use 2nd argument as default. + # If first argument is a list or tuple and more than 1 + # argument provided but not a named default, + # then use 2nd argument as default. if len(args) == 1 and isinstance(args[0], Iterable): mode_list = args[0] elif isinstance(args[0], list | tuple): @@ -401,7 +406,8 @@ class MathExtension(BaseTemplateExtension): def wrap(value: Any, min_value: Any, max_value: Any) -> Any: """Filter and function to wrap a value within a range. - Wraps value cyclically within [min_value, max_value) (inclusive min, exclusive max). + Wraps value cyclically within [min_value, max_value) + (inclusive min, exclusive max). """ try: value_num = float(value) @@ -437,7 +443,8 @@ class MathExtension(BaseTemplateExtension): the specified number of discrete steps. The edges parameter controls how out-of-bounds input values are handled: - - "none": No special handling; values outside the input range are extrapolated into the output range. + - "none": No special handling; values outside the + input range are extrapolated into the output range. - "clamp": Values outside the input range are clamped to the nearest boundary. - "wrap": Values outside the input range are wrapped around cyclically. - "mirror": Values outside the input range are mirrored back into the range. @@ -482,7 +489,9 @@ class MathExtension(BaseTemplateExtension): steps = max(steps, 0) if not steps and (in_min_num == out_min_num and in_max_num == out_max_num): - return value_num # No remapping needed. Save some cycles and floating-point precision. + # No remapping needed. Save some cycles and + # floating-point precision. + return value_num normalized = (value_num - in_min_num) / (in_max_num - in_min_num) diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index 1c79415c807..f43970503fe 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -281,7 +281,8 @@ class _TranslationCache: if updated_placeholders != cached_placeholders: _LOGGER.error( ( - "Validation of translation placeholders for localized (%s) string " + "Validation of translation placeholders" + " for localized (%s) string " "%s failed: (%s != %s)" ), language, @@ -464,7 +465,7 @@ def async_translate_state( translation_key: str | None, device_class: str | None, ) -> str: - """Translate provided state using cached translations for currently selected language.""" + """Translate provided state using cached translations.""" if state in [STATE_UNAVAILABLE, STATE_UNKNOWN]: return state language = hass.config.language @@ -500,7 +501,7 @@ def async_translate_state_attr( device_class: str | None, attribute_name: str, ) -> str: - """Translate provided state attribute value using cached translations for currently selected language.""" + """Translate state attribute value using cached translations.""" language = hass.config.language if platform is not None and translation_key is not None: localize_key = ( diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py index 9d052de3c86..b2f86bc16a0 100644 --- a/homeassistant/helpers/trigger.py +++ b/homeassistant/helpers/trigger.py @@ -634,7 +634,7 @@ class EntityOriginStateTriggerBase(EntityTriggerBase): _from_state: str def is_valid_transition(self, from_state: State, to_state: State) -> bool: - """Check if the origin state matches the expected one and that the state changed.""" + """Check if origin state matches expected and that the state changed.""" return bool( self._get_tracked_value(from_state) == self._from_state and self._get_tracked_value(to_state) != self._from_state @@ -886,8 +886,8 @@ NUMERICAL_ATTRIBUTE_CROSSED_THRESHOLD_SCHEMA = ( class EntityNumericalStateCrossedThresholdTriggerBase(EntityNumericalStateTriggerBase): """Trigger for numerical state and state attribute changes. - This trigger only fires when the observed attribute changes from not within to within - the defined threshold. + This trigger only fires when the observed attribute + changes from not within to within the defined threshold. """ _schema = NUMERICAL_ATTRIBUTE_CROSSED_THRESHOLD_SCHEMA @@ -902,8 +902,8 @@ def _make_numerical_state_crossed_threshold_with_unit_schema( ) -> vol.Schema: """Trigger for numerical state and state attribute changes. - This trigger only fires when the observed attribute changes from not within to within - the defined threshold. + This trigger only fires when the observed attribute + changes from not within to within the defined threshold. """ return ENTITY_STATE_TRIGGER_SCHEMA_FIRST_LAST.extend( { @@ -1441,8 +1441,10 @@ async def _async_attach_trigger_cls( return payload # Wrap sync action so that it is always async. - # This simplifies the Trigger action runner interface by always returning a coroutine, - # removing the need for integrations to check for the return type when awaiting the action. + # This simplifies the Trigger action runner interface by + # always returning a coroutine, removing the need for + # integrations to check for the return type when awaiting + # the action. match get_hassjob_callable_job_type(action): case HassJobType.Executor: original_action = action diff --git a/homeassistant/helpers/trigger_template_entity.py b/homeassistant/helpers/trigger_template_entity.py index 4e919e36388..5cc050f1ffb 100644 --- a/homeassistant/helpers/trigger_template_entity.py +++ b/homeassistant/helpers/trigger_template_entity.py @@ -106,7 +106,10 @@ TEMPLATE_SENSOR_BASE_SCHEMA = vol.Schema( class ValueTemplate(Template): - """Class to hold a value_template and manage caching and rendering it with 'value' in variables.""" + """Class to hold a value_template. + + Manages caching and rendering it with 'value' in variables. + """ @classmethod def from_template(cls, template: Template) -> ValueTemplate: @@ -135,7 +138,11 @@ class ValueTemplate(Template): self.template, compiled, **variables ).strip() except jinja2.TemplateError as ex: - message = f"Error parsing value for {entity_id}: {ex} (value: {variables['value']}, template: {self.template})" + message = ( + f"Error parsing value for {entity_id}:" + f" {ex} (value: {variables['value']}," + f" template: {self.template})" + ) logger = logging.getLogger( f"{__package__}.{entity_id.split('.', maxsplit=1)[0]}" ) @@ -361,7 +368,8 @@ class ManualTriggerEntity(TriggerBaseEntity): ) -> dict[str, Any]: """Render template variables. - Implementing class should call this first in update method to render variables for templates. + Implementing class should call this first in update + method to render variables for templates. Ex: variables = self._render_template_variables_with_value(payload) """ run_variables: dict[str, Any] = {"value": value} diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index b41b9fa3386..e1d6f6e95b6 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -340,8 +340,11 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]): is not config_entries.ConfigEntryState.SETUP_IN_PROGRESS ): raise ConfigEntryError( - f"`async_config_entry_first_refresh` called when config entry state is {self.config_entry.state}, " - f"but should only be called in state {config_entries.ConfigEntryState.SETUP_IN_PROGRESS}" + "`async_config_entry_first_refresh` called when" + f" config entry state is" + f" {self.config_entry.state}," + " but should only be called in state" + f" {config_entries.ConfigEntryState.SETUP_IN_PROGRESS}" ) if await self.__wrap_async_setup(): await self._async_refresh( diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 16edb0d099f..7614b805c1c 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -1172,8 +1172,9 @@ class Integration: load_executor_platforms, exc_info=ex, ) - # If importing in the executor deadlocks because there is a circular - # dependency, we fall back to the event loop. + # If importing in the executor deadlocks + # because there is a circular dependency, + # we fall back to the event loop. load_event_loop_platforms.extend(load_executor_platforms) if load_event_loop_platforms: @@ -1514,8 +1515,9 @@ async def _resolve_integrations_dependencies( possible_after_dependencies: set[str] | None | UndefinedType = UNDEFINED, ignore_exceptions: bool, ) -> dict[str, set[str]]: - """Resolve all dependencies, possibly including after_dependencies, for integrations. + """Resolve all dependencies for integrations. + Possibly includes after_dependencies. Detects circular dependencies and missing integrations. """ diff --git a/homeassistant/runner.py b/homeassistant/runner.py index f82ca4d7100..120fc06c603 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -90,7 +90,8 @@ def _report_existing_instance(lock_file_path: Path, config_dir: str) -> None: if content := f.read().strip(): existing_info = json.loads(content) start_dt = datetime.fromtimestamp(existing_info["start_ts"]) - # Format with timezone abbreviation if available, otherwise add local time indicator + # Format with timezone abbreviation if available, + # otherwise add local time indicator if tz_abbr := start_dt.strftime("%Z"): start_time = start_dt.strftime(f"%Y-%m-%d %H:%M:%S {tz_abbr}") else: @@ -144,7 +145,8 @@ def ensure_single_execution(config_dir: str) -> Generator[SingleExecutionLock]: # If we got the lock (no exception), write our instance info _write_lock_info(lock_file) - # Yield the context - lock will be released when the with statement closes the file + # Yield the context - lock will be released when the + # with statement closes the file # IMPORTANT: We don't unlink the file to avoid races where multiple processes # could create different lock files yield lock_context diff --git a/homeassistant/setup.py b/homeassistant/setup.py index a5ba5ac29c5..0c4431014ea 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -384,7 +384,9 @@ async def _async_setup_component( translation_key="config_entry_only", translation_placeholders={ "domain": domain, - "add_integration": f"/config/integrations/dashboard/add?domain={domain}", + "add_integration": ( + f"/config/integrations/dashboard/add?domain={domain}" + ), }, ) diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 33b4bdc5298..1ce18792acc 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -100,7 +100,8 @@ def snakecase(text: str) -> str: """Convert a string to snake_case.""" text = re.sub(r"[\s.-]", "_", text) if not text.isupper(): - # Underscore before last uppercase of groups of 2+ uppercase ("HTTPResponse", "IPAddress") + # Underscore before last uppercase of groups of 2+ + # uppercase ("HTTPResponse", "IPAddress") text = re.sub( r"[A-Z]{2,}(?=[A-Z][^A-Z])", lambda match: match.group(0) + "_", text ) diff --git a/homeassistant/util/language.py b/homeassistant/util/language.py index 4ce4c0178ad..5c524ad3b30 100644 --- a/homeassistant/util/language.py +++ b/homeassistant/util/language.py @@ -183,7 +183,10 @@ class Dialect: def matches( target: str, supported: Iterable[str], country: str | None = None ) -> list[str]: - """Return a sorted list of matching language tags based on a target tag and country hint.""" + """Return a sorted list of matching language tags. + + Based on a target tag and country hint. + """ if target == MATCH_ALL: return list(supported) diff --git a/homeassistant/util/loop.py b/homeassistant/util/loop.py index 541d93d1445..190e152c51f 100644 --- a/homeassistant/util/loop.py +++ b/homeassistant/util/loop.py @@ -159,8 +159,10 @@ def raise_for_blocking_call( f"{mapped_args.get('args')} inside the event loop by " f"{'custom ' if integration_frame.custom_integration else ''}" f"integration '{integration_frame.integration}' at " - f"{integration_frame.relative_filename}, line {integration_frame.line_number}:" - f" {integration_frame.line}. (offender: {offender_filename}, line " + f"{integration_frame.relative_filename}, line " + f"{integration_frame.line_number}:" + f" {integration_frame.line}. " + f"(offender: {offender_filename}, line " f"{offender_lineno}: {offender_line}), please {report_issue}\n" f"{_dev_help_message(func.__name__)}" ) diff --git a/homeassistant/util/timeout.py b/homeassistant/util/timeout.py index 507bf85520c..912fe87b3b9 100644 --- a/homeassistant/util/timeout.py +++ b/homeassistant/util/timeout.py @@ -243,7 +243,8 @@ class _GlobalTaskContext: if self._task.done(): return self._task.cancel( - f"Global task timeout{': ' + self._cancel_message if self._cancel_message else ''}" + "Global task timeout" + f"{': ' + self._cancel_message if self._cancel_message else ''}" ) def pause(self) -> None: diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index eb535aa22db..d3226f693b3 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -154,7 +154,10 @@ class BaseUnitConverter: def converter_factory_allow_none( cls, from_unit: str | None, to_unit: str | None ) -> Callable[[float | None], float | None]: - """Return a function to convert one unit of measurement to another which allows None.""" + """Return a function to convert a unit to another. + + Allows None values. + """ if from_unit == to_unit: return lambda value: value from_ratio, to_ratio = cls._get_from_to_ratio(from_unit, to_unit) @@ -693,7 +696,10 @@ class SpeedConverter(BaseUnitConverter): def converter_factory_allow_none( cls, from_unit: str | None, to_unit: str | None ) -> Callable[[float | None], float | None]: - """Return a function to convert a speed from one unit to another which allows None.""" + """Return a function to convert speed units. + + Allows None values. + """ if from_unit == to_unit: # Return a function that does nothing. This is not # in _converter_factory because we do not want to wrap @@ -789,7 +795,10 @@ class TemperatureConverter(BaseUnitConverter): def converter_factory_allow_none( cls, from_unit: str | None, to_unit: str | None ) -> Callable[[float | None], float | None]: - """Return a function to convert a temperature from one unit to another which allows None.""" + """Return a function to convert temperature units. + + Allows None values. + """ if from_unit == to_unit: # Return a function that does nothing. This is not # in _converter_factory because we do not want to wrap