Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions homeassistant/components/automation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,7 @@ async def trigger_service_handler(
async def reload_service_handler(service_call: ServiceCall) -> None:
"""Remove all automations and load new ones from config."""
await async_get_blueprints(hass).async_reset_cache()
if (conf := await component.async_prepare_reload(skip_reset=True)) is None:
return
conf = await component.async_prepare_reload(skip_reset=True)
if automation_id := service_call.data.get(CONF_ID):
await _async_process_single_config(hass, conf, component, automation_id)
else:
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/fritz/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
SCAN_INTERVAL,
MeshRoles,
)
from .helpers import _ha_is_stopping
from .helpers import ha_is_stopping
from .models import (
ConnectionInfo,
Device,
Expand Down Expand Up @@ -552,7 +552,7 @@ async def async_scan_devices(self, now: datetime | None = None) -> None:
"""Scan for new network devices."""

if self.hass.is_stopping:
_ha_is_stopping("scan devices")
ha_is_stopping("scan devices")
return

_LOGGER.debug("Checking devices for FRITZ!Box device %s", self.host)
Expand Down Expand Up @@ -727,7 +727,7 @@ async def _async_service_call(
"""Return service details."""

if self.hass.is_stopping:
_ha_is_stopping(f"{service_name}/{action_name}")
ha_is_stopping(f"{service_name}/{action_name}")
return {}

if f"{service_name}{service_suffix}" not in self.connection.services:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/fritz/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ def device_filter_out_from_trackers(
return bool(reason)


def _ha_is_stopping(activity: str) -> None:
def ha_is_stopping(activity: str) -> None:
"""Inform that HA is stopping."""
_LOGGER.warning("Cannot execute %s: HomeAssistant is shutting down", activity)
3 changes: 1 addition & 2 deletions homeassistant/components/group/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,7 @@ async def reload_service_handler(service: ServiceCall) -> None:
- Remove group.group entities not created by service calls and set them up again
- Reload xxx.group platforms
"""
if (conf := await component.async_prepare_reload(skip_reset=True)) is None:
return
conf = await component.async_prepare_reload(skip_reset=True)

# Simplified + modified version of EntityPlatform.async_reset:
# - group.group never retries setup
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/input_boolean/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None:
"""Remove all input booleans and load new ones from config."""
conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
return
await yaml_collection.async_load(
[
{CONF_ID: id_, **(conf or {})}
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/input_button/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None:
"""Remove all input buttons and load new ones from config."""
conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
return
await yaml_collection.async_load(
[
{CONF_ID: id_, **(conf or {})}
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/input_datetime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load(
[{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()]
)
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/input_number/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load(
[{CONF_ID: id_, **conf} for id_, conf in conf.get(DOMAIN, {}).items()]
)
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/input_select/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load(
[{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()]
)
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/input_text/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load(
[{CONF_ID: id_, **(cfg or {})} for id_, cfg in conf.get(DOMAIN, {}).items()]
)
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/person/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,6 @@ async def _handle_user_removed(event: Event) -> None:
async def async_reload_yaml(call: ServiceCall) -> None:
"""Reload YAML."""
conf = await entity_component.async_prepare_reload(skip_reset=True)
if conf is None:
return
await yaml_collection.async_load(
await filter_yaml_data(hass, conf.get(DOMAIN, []))
)
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/schedule/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load(
[{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()]
)
Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/script/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service(service: ServiceCall) -> None:
"""Call a service to reload scripts."""
await async_get_blueprints(hass).async_reset_cache()
if (conf := await component.async_prepare_reload(skip_reset=True)) is None:
return
conf = await component.async_prepare_reload(skip_reset=True)
await _async_process_config(hass, conf, component)

async def turn_on_service(service: ServiceCall) -> None:
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/timer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None:
"""Reload yaml entities."""
conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
conf = {DOMAIN: {}}
await yaml_collection.async_load(
[{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()]
)
Expand Down
18 changes: 9 additions & 9 deletions homeassistant/components/watts/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,16 @@ def __init__(
config_entry=config_entry,
)
self.client = client
self._last_discovery: datetime | None = None
self.last_discovery: datetime | None = None
self.previous_devices: set[str] = set()

async def _async_update_data(self) -> dict[str, Device]:
"""Fetch data and periodic device discovery."""
now = datetime.now()
is_first_refresh = self._last_discovery is None
is_first_refresh = self.last_discovery is None
discovery_interval_elapsed = (
self._last_discovery is not None
and now - self._last_discovery
self.last_discovery is not None
and now - self.last_discovery
>= timedelta(minutes=DISCOVERY_INTERVAL_MINUTES)
)

Expand All @@ -96,7 +96,7 @@ async def _async_update_data(self) -> dict[str, Device]:
"Periodic discovery failed: %s, falling back to update", err
)
else:
self._last_discovery = now
self.last_discovery = now
devices = {device.device_id: device for device in devices_list}

current_devices = set(devices.keys())
Expand Down Expand Up @@ -172,7 +172,7 @@ def __init__(
self.client = client
self.device_id = device_id
self.hub_coordinator = hub_coordinator
self._fast_polling_until: datetime | None = None
self.fast_polling_until: datetime | None = None

# Listen to hub coordinator updates
self.unsubscribe_hub_listener = hub_coordinator.async_add_listener(
Expand All @@ -187,8 +187,8 @@ def _handle_hub_update(self) -> None:

async def _async_update_data(self) -> WattsVisionDeviceData:
"""Refresh specific device."""
if self._fast_polling_until and datetime.now() > self._fast_polling_until:
self._fast_polling_until = None
if self.fast_polling_until and datetime.now() > self.fast_polling_until:
self.fast_polling_until = None
self.update_interval = None
_LOGGER.debug(
"Device %s: Fast polling period ended, returning to manual refresh",
Expand Down Expand Up @@ -217,7 +217,7 @@ async def _async_update_data(self) -> WattsVisionDeviceData:

def trigger_fast_polling(self, duration: int = 60) -> None:
"""Activate fast polling for a specified duration after a command."""
self._fast_polling_until = datetime.now() + timedelta(seconds=duration)
self.fast_polling_until = datetime.now() + timedelta(seconds=duration)
self.update_interval = timedelta(seconds=FAST_POLLING_INTERVAL_SECONDS)
_LOGGER.debug(
"Device %s: Activated fast polling for %d seconds", self.device_id, duration
Expand Down
69 changes: 69 additions & 0 deletions homeassistant/components/watts/diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Diagnostics support for Watts Vision +."""

from __future__ import annotations

import dataclasses
from datetime import datetime
from typing import Any

from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.core import HomeAssistant

from . import WattsVisionConfigEntry

TO_REDACT = ("refresh_token", "id_token", "profile_info", "unique_id")


async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
entry: WattsVisionConfigEntry,
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
runtime_data = entry.runtime_data
hub_coordinator = runtime_data.hub_coordinator
device_coordinators = runtime_data.device_coordinators
now = datetime.now()

return async_redact_data(
{
"entry": entry.as_dict(),
"hub_coordinator": {
"last_update_success": hub_coordinator.last_update_success,
"last_exception": (
str(hub_coordinator.last_exception)
if hub_coordinator.last_exception
else None
),
"last_discovery": (
hub_coordinator.last_discovery.isoformat()
if hub_coordinator.last_discovery
else None
),
"total_devices": len(hub_coordinator.data),
"supported_devices": len(device_coordinators),
},
"hub_data": {
device_id: dataclasses.asdict(device)
for device_id, device in hub_coordinator.data.items()
},
"devices": {
device_id: {
"device": dataclasses.asdict(coordinator.data.device),
"last_update_success": coordinator.last_update_success,
"fast_polling_active": (
coordinator.fast_polling_until is not None
and coordinator.fast_polling_until > now
),
"fast_polling_until": (
coordinator.fast_polling_until.isoformat()
if coordinator.fast_polling_until is not None
and coordinator.fast_polling_until > now
else None
),
}
for device_id, coordinator in device_coordinators.items()
},
},
{CONF_ACCESS_TOKEN, *TO_REDACT},
)
2 changes: 1 addition & 1 deletion homeassistant/components/watts/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ rules:

# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery-update-info:
status: exempt
comment: Integration does not support discovery.
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/zone/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def reload_service_handler(service_call: ServiceCall) -> None:
"""Remove all zones and load new ones from config."""
conf = await component.async_prepare_reload(skip_reset=True)
if conf is None:
return
await yaml_collection.async_load(conf[DOMAIN])

service.async_register_admin_service(
Expand Down
32 changes: 31 additions & 1 deletion homeassistant/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from pathlib import Path
import shutil
from types import ModuleType
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Literal, overload

from awesomeversion import AwesomeVersion
import voluptuous as vol
Expand Down Expand Up @@ -851,6 +851,36 @@ def _get_log_message_and_stack_print_pref(
return (log_message, show_stack_trace, placeholders)


# The complicated overloads are due to a limitation in mypy, details in
# https://github.com/python/mypy/issues/7333
@overload
async def async_process_component_and_handle_errors(
hass: HomeAssistant,
config: ConfigType,
integration: Integration,
) -> ConfigType | None: ...


@overload
async def async_process_component_and_handle_errors(
hass: HomeAssistant,
config: ConfigType,
integration: Integration,
*,
raise_on_failure: Literal[True],
) -> ConfigType: ...


@overload
async def async_process_component_and_handle_errors(
hass: HomeAssistant,
config: ConfigType,
integration: Integration,
*,
raise_on_failure: bool,
) -> ConfigType | None: ...


async def async_process_component_and_handle_errors(
hass: HomeAssistant,
config: ConfigType,
Expand Down
34 changes: 21 additions & 13 deletions homeassistant/helpers/entity_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
SupportsResponse,
callback,
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import (
ConfigValidationError,
HomeAssistantError,
ServiceValidationError,
)
from homeassistant.loader import async_get_integration, bind_hass
from homeassistant.setup import async_prepare_setup_platform
from homeassistant.util.hass_dict import HassKey
Expand Down Expand Up @@ -301,27 +305,31 @@ async def async_remove_entity(self, entity_id: str) -> None:
if found:
await found.async_remove_entity(entity_id)

async def async_prepare_reload(
self, *, skip_reset: bool = False
) -> ConfigType | None:
async def async_prepare_reload(self, *, skip_reset: bool = False) -> ConfigType:
"""Prepare reloading this entity component.

This method must be run in the event loop.
This method is intended to be called from service handlers implementing reload.
Will raise ServiceValidationError if the config is not valid.
"""
try:
conf = await conf_util.async_hass_config_yaml(self.hass)
except HomeAssistantError as err:
self.logger.error(err)
return None
raise ServiceValidationError(
f"Failed to load configuration: {err}"
) from err

integration = await async_get_integration(self.hass, self.domain)

processed_conf = await conf_util.async_process_component_and_handle_errors(
self.hass, conf, integration
)

if processed_conf is None:
return None
try:
processed_conf = await conf_util.async_process_component_and_handle_errors(
self.hass, conf, integration, raise_on_failure=True
)
except ConfigValidationError as err:
raise ServiceValidationError(
translation_domain=err.translation_domain,
translation_key=err.translation_key,
translation_placeholders=err.translation_placeholders,
) from err

if not skip_reset:
await self._async_reset()
Expand Down
Loading
Loading