393 files
This commit is contained in:
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import math
|
||||
from collections.abc import Callable, Mapping
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.components import websocket_api
|
||||
@@ -43,6 +43,8 @@ from ..const import (
|
||||
CONF_DEFAULT_WARNING_DAYS,
|
||||
CONF_DELETE_ARCHIVED_ONEOFF_DAYS,
|
||||
CONF_DISABLED_TEMPLATE_IDS,
|
||||
CONF_HOME_REGION,
|
||||
CONF_HOME_TYPE,
|
||||
CONF_INSTALL_ASSIST_SENTENCES,
|
||||
CONF_MAX_NOTIFICATIONS_PER_DAY,
|
||||
CONF_MEMBER_DISPLAY,
|
||||
@@ -91,6 +93,7 @@ from ..const import (
|
||||
from ..helpers.aggregate import compute_status_counts
|
||||
from ..helpers.global_options import get_global_options
|
||||
from ..helpers.notify_targets import build_notify_targets
|
||||
from ..helpers.region import is_region_setting
|
||||
from ..helpers.settings_registry import (
|
||||
ALLOWED_SETTING_KEYS,
|
||||
FLOAT_RANGES,
|
||||
@@ -113,7 +116,10 @@ _LOGGER = logging.getLogger(__name__)
|
||||
# Keys accepted by global/update + their range/cap tables are derived from the
|
||||
# single settings registry (helpers/settings_registry) so they can't drift from
|
||||
# each other or from the options-flow selectors that share the same specs.
|
||||
_ALLOWED_SETTING_KEYS = ALLOWED_SETTING_KEYS
|
||||
# Every value is a SettingSpec.py_type — a plain class, the isinstance()
|
||||
# target below; the registry's ``type | vol.Any`` value annotation is wider
|
||||
# than what it actually holds.
|
||||
_ALLOWED_SETTING_KEYS = cast(dict[str, type], ALLOWED_SETTING_KEYS)
|
||||
|
||||
|
||||
def _opt(options: Mapping[str, Any], key: str) -> Any:
|
||||
@@ -142,10 +148,20 @@ def _battery_lifetime_catalog(hass: HomeAssistant) -> list[dict[str, Any]]:
|
||||
from ..helpers.battery_lifetime import lifetime_catalog
|
||||
|
||||
names: dict[str, str] = {}
|
||||
dev_reg = dr.async_get(hass)
|
||||
# HA 2026.9 deprecates mapping-style access on DeviceRegistry.devices
|
||||
# (.values() logs a deprecation warning for custom integrations and
|
||||
# stops working in 2027.9) and iterates ENTRIES; 2026.8 and older
|
||||
# iterate ids and need .values(). Probe the collection itself, same as
|
||||
# repairs.py.
|
||||
registry_devices = dr.async_get(hass).devices
|
||||
all_devices: list[Any] = list(
|
||||
cast(Any, registry_devices).values() if isinstance(registry_devices, Mapping) else registry_devices
|
||||
)
|
||||
for bat in read_batteries(hass):
|
||||
if bat.model_key and bat.model_key not in names:
|
||||
for device in dev_reg.devices.values():
|
||||
for device in all_devices:
|
||||
if getattr(device, "parent_device_id", None) is not None:
|
||||
continue # 2026.9 sub-device: never the appliance, and its make/model reads are deprecated
|
||||
manufacturer = str(getattr(device, "manufacturer", None) or "")
|
||||
model = str(getattr(device, "model", None) or getattr(device, "model_id", None) or "")
|
||||
key = f"{manufacturer.strip().lower()}|{model.strip().lower()}"
|
||||
@@ -154,6 +170,9 @@ def _battery_lifetime_catalog(hass: HomeAssistant) -> list[dict[str, Any]]:
|
||||
break
|
||||
return lifetime_catalog(hass, list(discover_battery_types(hass)), model_names=names)
|
||||
except Exception: # noqa: BLE001 - a settings read must never fail on the fleet
|
||||
# Logged, not swallowed: an HA API change here once went unnoticed
|
||||
# because the table simply came back shorter.
|
||||
_LOGGER.warning("Battery lifetime table could not be built", exc_info=True)
|
||||
return []
|
||||
|
||||
|
||||
@@ -209,6 +228,10 @@ def _build_full_settings(
|
||||
"member_display": _opt(options, CONF_MEMBER_DISPLAY),
|
||||
# v2.21: template-gallery curation (ids hidden from the pickers).
|
||||
"disabled_template_ids": _opt(options, CONF_DISABLED_TEMPLATE_IDS),
|
||||
# v2.93: home profile dwelling type (auto / house / apartment).
|
||||
"home_type": _opt(options, CONF_HOME_TYPE),
|
||||
# 2.94: the state / province / region the templates follow (auto = located).
|
||||
"home_region": _opt(options, CONF_HOME_REGION),
|
||||
# v2.10.0: archive automation thresholds (panel Settings → Archive).
|
||||
# oneoff_days: auto-archive a completed one-off after N days (0 = off).
|
||||
# delete_archived_oneoff_days: auto-delete an auto-archived one-off N
|
||||
@@ -658,8 +681,7 @@ async def ws_get_budget_status(
|
||||
"""Return current budget status (monthly/yearly spent vs budget)."""
|
||||
from ..helpers.budget import compute_spend
|
||||
|
||||
global_entry = _get_global_entry(hass)
|
||||
global_options: Mapping[str, Any] = (global_entry.options or global_entry.data) if global_entry else {}
|
||||
global_options = get_global_options(hass)
|
||||
|
||||
monthly_budget = float(global_options.get(CONF_BUDGET_MONTHLY, 0))
|
||||
yearly_budget = float(global_options.get(CONF_BUDGET_YEARLY, 0))
|
||||
@@ -691,6 +713,16 @@ async def ws_get_budget_status(
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def settings_error_field(error: str) -> str:
|
||||
"""The setting a ``sanitize_settings_input`` error code refers to — the
|
||||
notify-service codes come from ``validate_notify_service``, the others are
|
||||
the sanitizer's own early returns."""
|
||||
return {
|
||||
"invalid_shopping_list_entity": CONF_SHOPPING_LIST_ENTITY,
|
||||
"invalid_search_template": CONF_PART_SEARCH_URL_TEMPLATE,
|
||||
}.get(error, CONF_NOTIFY_SERVICE)
|
||||
|
||||
|
||||
def sanitize_settings_input(settings_input: dict[str, Any]) -> tuple[dict[str, Any], str | None]:
|
||||
"""Filter + validate a flat settings dict against the registry.
|
||||
|
||||
@@ -746,8 +778,12 @@ def sanitize_settings_input(settings_input: dict[str, Any]) -> tuple[dict[str, A
|
||||
# v1.4.0 (#44): enum-validate notification_title_style. Anything outside
|
||||
# the known set is dropped silently so a bogus value can't get into the
|
||||
# ConfigEntry options.
|
||||
from ..const import NOTIFICATION_TITLE_STYLES, NOTIFY_COMPLETED_MODES
|
||||
from ..const import HOME_TYPES, NOTIFICATION_TITLE_STYLES, NOTIFY_COMPLETED_MODES
|
||||
|
||||
if CONF_HOME_TYPE in filtered and filtered[CONF_HOME_TYPE] not in HOME_TYPES:
|
||||
del filtered[CONF_HOME_TYPE]
|
||||
if CONF_HOME_REGION in filtered and not is_region_setting(filtered[CONF_HOME_REGION]):
|
||||
del filtered[CONF_HOME_REGION]
|
||||
if CONF_NOTIFY_COMPLETED in filtered and filtered[CONF_NOTIFY_COMPLETED] not in NOTIFY_COMPLETED_MODES:
|
||||
filtered[CONF_NOTIFY_COMPLETED] = "off"
|
||||
if CONF_NOTIFICATION_TITLE_STYLE in filtered and filtered[CONF_NOTIFICATION_TITLE_STYLE] not in NOTIFICATION_TITLE_STYLES:
|
||||
|
||||
Reference in New Issue
Block a user