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:
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 []
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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__)}"
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user