1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-23 17:00:13 +01:00

Fix line length violations in core and helpers (#170534)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Franck Nijhof
2026-05-14 22:31:01 +02:00
committed by GitHub
parent bb964ccd95
commit eae809abd1
48 changed files with 420 additions and 212 deletions
+6 -4
View File
@@ -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:
@@ -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",
)
+6 -2
View File
@@ -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(
+10 -5
View File
@@ -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),
+39 -20
View File
@@ -560,8 +560,12 @@ class ConfigEntry[_DataT = Any]:
def __repr__(self) -> str:
"""Representation of ConfigEntry."""
return (
f"<ConfigEntry entry_id={self.entry_id} version={self.version} domain={self.domain} "
f"title={self.title} state={self.state} unique_id={self.unique_id}>"
f"<ConfigEntry entry_id={self.entry_id}"
f" version={self.version}"
f" domain={self.domain}"
f" title={self.title}"
f" state={self.state}"
f" unique_id={self.unique_id}>"
)
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 (
+4 -2
View File
@@ -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.
+4 -3
View File
@@ -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,
+7 -5
View File
@@ -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)
+4 -1
View File
@@ -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)
+4 -1
View File
@@ -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(
+5 -3
View File
@@ -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:
+4 -1
View File
@@ -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
+2 -1
View File
@@ -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
+19 -13
View File
@@ -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(
+15 -9
View File
@@ -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
+2 -1
View File
@@ -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.
+10 -6
View File
@@ -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",
)
+19 -15
View File
@@ -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
+19 -6
View File
@@ -196,7 +196,11 @@ class MatchFailedError(IntentError):
def __str__(self) -> str:
"""Return string representation."""
return f"<MatchFailedError result={self.result}, constraints={self.constraints}, preferences={self.preferences}>"
return (
f"<MatchFailedError result={self.result},"
f" constraints={self.constraints},"
f" preferences={self.preferences}>"
)
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 []
+67 -24
View File
@@ -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 {
+2 -1
View File
@@ -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 (
@@ -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]
+6 -3
View File
@@ -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
+26 -16
View File
@@ -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
+11 -4
View File
@@ -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"
+2 -1
View File
@@ -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
+2 -1
View File
@@ -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()
+11 -6
View File
@@ -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,
+3 -2
View File
@@ -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."""
+1 -1
View File
@@ -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__(
@@ -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
@@ -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(
@@ -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
@@ -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)
@@ -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
@@ -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)
+4 -3
View File
@@ -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 = (
+9 -7
View File
@@ -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
@@ -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}
+5 -2
View File
@@ -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(
+5 -3
View File
@@ -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.
"""
+4 -2
View File
@@ -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
+3 -1
View File
@@ -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}"
),
},
)
+2 -1
View File
@@ -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
)
+4 -1
View File
@@ -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)
+4 -2
View File
@@ -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__)}"
)
+2 -1
View File
@@ -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:
+12 -3
View File
@@ -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