Compare commits

..

38 Commits

Author SHA1 Message Date
Franck Nijhof 37288849b3 Register Insteon modem device before platform setup (#171839) 2026-05-22 10:23:47 -04:00
zhangluofeng aa8659f507 Add xthings cloud lock (#171176)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-05-22 15:54:37 +02:00
DeerMaximum 40c0d79d1d Replaced duplicate constant with homeassistant.const in NINA (#171852) 2026-05-22 15:41:04 +02:00
Franck Nijhof bef8632d78 Fix OpenHome config flow crash when UDN is a list (#171841) 2026-05-22 15:23:42 +02:00
Duco Sebel f00decfaa3 Use uptime device class for HomeWizard uptime sensor (#171830) 2026-05-22 15:23:09 +02:00
Manu 42e7add026 Add selector options translations to System Bridge integration (#171771) 2026-05-22 15:22:22 +02:00
Franck Nijhof 263aa3f16e Fix Hue device trigger crash for devices removed from bridge (#171844) 2026-05-22 15:18:00 +02:00
mhuiskes 03b364dcf0 Refactor zeversolar tests: use fixtures, patch at use site, add unique_id (#171697) 2026-05-22 14:58:56 +02:00
Duco Sebel 3b1aaf39af Bumb python homewizard energy 10.1.0 (#171826) 2026-05-22 14:51:58 +02:00
Franck Nijhof b82ba43fa4 Add pylint checker for invalid MDI icon references (#171824)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 13:45:40 +02:00
starkillerOG d81ef5593c Bump reolink_aio to 0.20.0: Reolink battery camera support (#171836) 2026-05-22 12:59:33 +02:00
Manu 5c5e50f024 Fix platform unloading in System Bridge integration (#171822) 2026-05-22 12:56:03 +02:00
Lex Postma e796d9c467 Update strings.json to align with HomeWizard app (#171740)
Co-authored-by: Duco Sebel <74970928+DCSBL@users.noreply.github.com>
2026-05-22 12:54:58 +02:00
Karl Beecken 342f23526f Remove empty requirements_test_all.txt (#171530 follow-up) (#171834) 2026-05-22 12:38:58 +02:00
Erik Montnemery 814ec697cf Remove advanced mode from hue service actions (#171442) 2026-05-22 11:45:33 +02:00
Erik Montnemery 120f1446d4 Rename advanced section to additional options in telegram_bot service actions (#171460) 2026-05-22 11:44:05 +02:00
Franck Nijhof 170af75b7d Fix Lutron Caseta battery sensor crash on unsupported devices (#171829) 2026-05-22 11:37:05 +02:00
Ariel Ebersberger 5432d29489 Use is/is not for same-enum identity comparisons (tests) (#171689) 2026-05-22 11:32:27 +02:00
Franck Nijhof 8098f4f6bc Fix invalid MDI icon references (#171831)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 11:25:28 +02:00
Simone Chemelli 6a70077687 Fix exception translation placeholder mismatches in comelit (#171748) 2026-05-22 11:17:17 +02:00
Max Michels 5dbb0464ba Replace duplicate constants with homeassistant.const imports (#171815) 2026-05-22 11:10:21 +02:00
dependabot[bot] 1df165ea02 Bump j178/prek-action from 2.0.3 to 2.0.4 (#171812)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-22 10:39:43 +02:00
Manu 62542eb911 Replace duplicate constants with homeassistant.const imports in xiaomi_miio (#171823) 2026-05-22 10:39:09 +02:00
Max Michels a842cac34c Replace duplicate constants with homeassistant.const imports (#171817) 2026-05-22 10:38:06 +02:00
Simone Chemelli 2460f688e3 Add missing exception translation keys in alexa_devices (#171749) 2026-05-22 10:34:00 +02:00
Simone Chemelli a868ea443c Fix hardcoded exception strings in uptimerobot (#171744) 2026-05-22 10:33:07 +02:00
Franck Nijhof 1d8565483b Apply web search citation stripping for GPT-5.x models in OpenAI conversation (#170956) 2026-05-22 10:31:10 +02:00
dependabot[bot] 1ef3301253 Bump github/codeql-action from 4.35.4 to 4.35.5 (#171813)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-22 09:47:16 +02:00
Manu 525952f016 Add entity translations to System Bridge integration (#171807) 2026-05-22 09:00:54 +02:00
Shay Levy 3257275c5a Fix LG webOS TV hardcoded exception strings (#171777) 2026-05-22 08:28:19 +02:00
Max Michels cb54fd4921 Replace duplicate constants with homeassistant.const imports (#171809) 2026-05-22 07:57:08 +02:00
Max Michels b391fc61ea Replace duplicate constants with homeassistant.const imports (#171808) 2026-05-22 07:56:29 +02:00
J. Nick Koston fcd4e4939c Bump habluetooth to 6.2.0 (#171800) 2026-05-21 23:08:17 -05:00
J. Nick Koston deb8b5da05 Parallelize pytest --collect-only in split_tests.py (#171772) 2026-05-21 22:58:01 -04:00
g4bri3lDev c7754a6ce9 Bump py-opendisplay to 7.2.3 (#171775) 2026-05-21 22:52:36 -04:00
J. Nick Koston 242724bd50 Bump aiodiscover to 3.2.3 (#171803) 2026-05-21 22:51:54 -04:00
Max Michels 42454563db Replace duplicate constants with homeassistant.const imports (#171790)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2026-05-21 22:51:34 -04:00
J. Nick Koston bf03d0c216 Bump dbus-fast to 5.0.3 (#171595) 2026-05-21 21:11:35 -05:00
216 changed files with 12153 additions and 1202 deletions
+1
View File
@@ -15,6 +15,7 @@ Dockerfile.dev linguist-language=Dockerfile
# Generated files
CODEOWNERS linguist-generated=true
homeassistant/generated/*.py linguist-generated=true
pylint/plugins/pylint_home_assistant/generated/*.py linguist-generated=true
machine/* linguist-generated=true
mypy.ini linguist-generated=true
requirements.txt linguist-generated=true
+2 -2
View File
@@ -281,7 +281,7 @@ jobs:
echo "::add-matcher::.github/workflows/matchers/check-executables-have-shebangs.json"
echo "::add-matcher::.github/workflows/matchers/codespell.json"
- name: Run prek
uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2.0.3
uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
env:
PREK_SKIP: no-commit-to-branch,mypy,pylint,gen_requirements_all,hassfest,hassfest-metadata,hassfest-mypy-config,zizmor
RUFF_OUTPUT_FORMAT: github
@@ -302,7 +302,7 @@ jobs:
with:
persist-credentials: false
- name: Run zizmor
uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2.0.3
uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
with:
extra-args: --all-files zizmor
+2 -2
View File
@@ -28,11 +28,11 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
with:
category: "/language:python"
+1 -1
View File
@@ -18,7 +18,7 @@ repos:
exclude_types: [csv, json, html]
exclude: ^tests/fixtures/|homeassistant/generated/|tests/components/.*/snapshots/
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.25.0
rev: v1.24.1
hooks:
- id: zizmor
args:
@@ -11,7 +11,7 @@
"service": "mdi:dialpad"
},
"alarm_toggle_chime": {
"service": "mdi:abc"
"service": "mdi:bell-ring"
}
}
}
@@ -91,7 +91,6 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
translation_placeholders={"error": repr(err)},
) from err
except CannotAuthenticate as err:
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
@@ -102,6 +102,9 @@
"entry_not_loaded": {
"message": "Entry not loaded: {entry}"
},
"invalid_auth": {
"message": "Invalid authentication credentials: {error}"
},
"invalid_device_id": {
"message": "Invalid device ID specified: {device_id}"
},
@@ -20,7 +20,7 @@
"bluetooth-adapters==2.1.0",
"bluetooth-auto-recovery==1.5.3",
"bluetooth-data-tools==1.29.11",
"dbus-fast==5.0.0",
"habluetooth==6.1.0"
"dbus-fast==5.0.3",
"habluetooth==6.2.0"
]
}
@@ -65,11 +65,9 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
translation_placeholders={"error": repr(err)},
) from err
except aiocomelit_exceptions.CannotAuthenticate as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise InvalidAuth(
translation_domain=DOMAIN,
translation_key="cannot_authenticate",
translation_placeholders={"error": repr(err)},
) from err
finally:
await api.logout()
@@ -61,7 +61,7 @@ class CurrencylayerSensor(SensorEntity):
"""Implementing the Currencylayer sensor."""
_attr_attribution = "Data provided by currencylayer.com"
_attr_icon = "mdi:currency"
_attr_icon = "mdi:currency-usd"
def __init__(self, rest: CurrencylayerData, base: str, quote: str) -> None:
"""Initialize the sensor."""
+1 -1
View File
@@ -16,7 +16,7 @@
"quality_scale": "internal",
"requirements": [
"aiodhcpwatcher==1.2.1",
"aiodiscover==3.2.0",
"aiodiscover==3.2.3",
"cached-ipaddress==1.0.1"
]
}
+1 -1
View File
@@ -23,7 +23,7 @@
"service": "mdi:refresh"
},
"set_dhw_override": {
"service": "mdi:water-heater"
"service": "mdi:water-boiler"
},
"set_system_mode": {
"service": "mdi:pencil"
+1 -1
View File
@@ -16,7 +16,7 @@ class DeviceType(Enum):
GAME_CONSOLE = "mdi:nintendo-game-boy"
STREAMING_DONGLE = "mdi:cast"
LOUDSPEAKER = SOUND_SYSTEM = STB = SATELLITE = MUSIC = "mdi:speaker"
DISC_PLAYER = "mdi:disk-player"
DISC_PLAYER = "mdi:disc-player"
REMOTE_CONTROL = "mdi:remote-tv"
RADIO = "mdi:radio"
PHOTO_CAMERA = PHOTOS = "mdi:camera"
+1 -1
View File
@@ -2,7 +2,7 @@
"entity": {
"button": {
"sync_clock": {
"default": "mdi:clock-sync"
"default": "mdi:clock-check"
}
},
"number": {
-2
View File
@@ -2,8 +2,6 @@
from homeassistant.const import Platform
# pylint: disable-next=home-assistant-duplicate-const
ATTR_MODE = "mode"
ATTR_TIME_PERIOD = "time_period"
ATTR_ONOFF = "on_off"
CONF_CODE = "2fa"
+1 -1
View File
@@ -12,12 +12,12 @@ from homeassistant.components.light import (
ColorMode,
LightEntity,
)
from homeassistant.const import ATTR_MODE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import color as color_util
from . import HiveConfigEntry, refresh_system
from .const import ATTR_MODE
from .entity import HiveEntity
PARALLEL_UPDATES = 0
+1 -2
View File
@@ -6,12 +6,11 @@ from typing import Any
from apyhiveapi import Hive
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.const import ATTR_MODE, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import HiveConfigEntry, refresh_system
from .const import ATTR_MODE
from .entity import HiveEntity
PARALLEL_UPDATES = 0
@@ -13,6 +13,6 @@
"iot_class": "local_polling",
"loggers": ["homewizard_energy"],
"quality_scale": "platinum",
"requirements": ["python-homewizard-energy==10.0.1"],
"requirements": ["python-homewizard-energy==10.1.0"],
"zeroconf": ["_hwenergy._tcp.local.", "_homewizard._tcp.local."]
}
+2 -10
View File
@@ -35,7 +35,6 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util.dt import utcnow
from homeassistant.util.variance import ignore_variance
from .const import DOMAIN
from .coordinator import HomeWizardConfigEntry, HWEnergyDeviceUpdateCoordinator
@@ -66,13 +65,6 @@ def to_percentage(value: float | None) -> float | None:
return value * 100 if value is not None else None
def uptime_to_datetime(value: int) -> datetime:
"""Convert seconds to datetime timestamp."""
return utcnow().replace(microsecond=0) - timedelta(seconds=value)
uptime_to_stable_datetime = ignore_variance(uptime_to_datetime, timedelta(minutes=5))
SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
HomeWizardSensorEntityDescription(
key="smr_version",
@@ -643,7 +635,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
HomeWizardSensorEntityDescription(
key="uptime",
translation_key="uptime",
device_class=SensorDeviceClass.TIMESTAMP,
device_class=SensorDeviceClass.UPTIME,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
has_fn=(
@@ -651,7 +643,7 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
),
value_fn=(
lambda data: (
uptime_to_stable_datetime(data.system.uptime_s)
utcnow() - timedelta(seconds=data.system.uptime_s)
if data.system is not None and data.system.uptime_s is not None
else None
)
@@ -61,13 +61,14 @@
},
"select": {
"battery_group_mode": {
"name": "Battery group mode",
"name": "Battery group charging strategy",
"state": {
"predictive": "Smart charging",
"standby": "Standby",
"to_full": "Manual charge mode",
"zero": "Zero mode",
"zero_charge_only": "Zero mode (charge only)",
"zero_discharge_only": "Zero mode (discharge only)"
"to_full": "One-time full charge",
"zero": "Net zero",
"zero_charge_only": "Net zero (charge only)",
"zero_discharge_only": "Net zero (discharge only)"
}
}
},
+13 -12
View File
@@ -31,15 +31,16 @@ activate_scene:
dynamic:
selector:
boolean:
speed:
advanced: true
selector:
number:
min: 0
max: 100
brightness:
advanced: true
selector:
number:
min: 1
max: 255
scene_customization:
collapsed: true
fields:
speed:
selector:
number:
min: 0
max: 100
brightness:
selector:
number:
min: 1
max: 255
+6 -1
View File
@@ -184,7 +184,12 @@
"name": "Transition"
}
},
"name": "Activate Hue scene"
"name": "Activate Hue scene",
"sections": {
"scene_customization": {
"name": "Scene customization"
}
}
},
"hue_activate_scene": {
"description": "Activates a Hue scene stored in the Hue hub.",
@@ -87,6 +87,8 @@ def async_get_triggers(
# Get Hue device id from device identifier
hue_dev_id = get_hue_device_id(device_entry)
if hue_dev_id is None or hue_dev_id not in api.devices:
return []
# extract triggers from all button resources of this Hue device
triggers: list[dict[str, Any]] = []
model_id = api.devices[hue_dev_id].product_data.product_name
+2 -2
View File
@@ -118,6 +118,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
device = devices.add_x10_device(housecode, unitcode, x10_type, steps)
create_insteon_device(hass, devices.modem, entry.entry_id)
await hass.config_entries.async_forward_entry_setups(entry, INSTEON_PLATFORMS)
for address in devices:
@@ -131,8 +133,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
register_new_device_callback(hass)
async_setup_services(hass)
create_insteon_device(hass, devices.modem, entry.entry_id)
entry.async_create_background_task(
hass, async_get_device_config(hass, entry), "insteon-get-device-config"
)
@@ -1,7 +1,7 @@
{
"entity": {
"binary_sensor": {
"erev_shabbat_hag": { "default": "mdi:candle-light" },
"erev_shabbat_hag": { "default": "mdi:candle" },
"issur_melacha_in_effect": { "default": "mdi:power-plug-off" },
"motzei_shabbat_hag": { "default": "mdi:fire" }
},
+1 -1
View File
@@ -7,7 +7,7 @@
"service": "mdi:lock-open"
},
"disable": {
"service": "mdi:fash-off"
"service": "mdi:flash-off"
},
"enable": {
"service": "mdi:flash"
+4 -4
View File
@@ -28,25 +28,25 @@
"ice_maker": {
"default": "mdi:cube-outline",
"state": {
"off": "mdi:cube-outline-off"
"off": "mdi:cube-off-outline"
}
},
"ice_maker_bottom_zone": {
"default": "mdi:cube-outline",
"state": {
"off": "mdi:cube-outline-off"
"off": "mdi:cube-off-outline"
}
},
"ice_maker_middle_zone": {
"default": "mdi:cube-outline",
"state": {
"off": "mdi:cube-outline-off"
"off": "mdi:cube-off-outline"
}
},
"ice_maker_top_zone": {
"default": "mdi:cube-outline",
"state": {
"off": "mdi:cube-outline-off"
"off": "mdi:cube-off-outline"
}
}
},
@@ -3,7 +3,7 @@
from datetime import timedelta
from typing import Any
from pylutron_caseta import OCCUPANCY_GROUP_OCCUPIED
from pylutron_caseta import OCCUPANCY_GROUP_OCCUPIED, BridgeResponseError
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
@@ -131,7 +131,11 @@ class LutronCasetaBatterySensor(LutronCasetaEntity, BinarySensorEntity):
async def async_update(self) -> None:
"""Fetch the latest battery status from the bridge."""
status = await self._smartbridge.get_battery_status(self.device_id)
try:
status = await self._smartbridge.get_battery_status(self.device_id)
except BridgeResponseError:
self._attr_is_on = None
return
normalized_status = status.strip().casefold() if status else None
if normalized_status == BATTERY_STATUS_LOW:
self._attr_is_on = True
+1 -1
View File
@@ -102,7 +102,7 @@
"default": "mdi:home-lightning-bolt"
},
"eve_weather_trend": {
"default": "mdi:weather",
"default": "mdi:weather-cloudy",
"state": {
"cloudy": "mdi:weather-cloudy",
"rainy": "mdi:weather-rainy",
@@ -6,6 +6,7 @@ from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.const import ATTR_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -14,7 +15,6 @@ from .const import (
ATTR_DESCRIPTION,
ATTR_EXPIRES,
ATTR_HEADLINE,
ATTR_ID,
ATTR_RECOMMENDED_ACTIONS,
ATTR_SENDER,
ATTR_SENT,
-2
View File
@@ -29,8 +29,6 @@ ATTR_SEVERITY: str = "severity"
ATTR_RECOMMENDED_ACTIONS: str = "recommended_actions"
ATTR_AFFECTED_AREAS: str = "affected_areas"
ATTR_WEB: str = "web"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_ID: str = "id"
ATTR_SENT: str = "sent"
ATTR_START: str = "start"
ATTR_EXPIRES: str = "expires"
@@ -595,8 +595,8 @@ class OpenAIBaseLLMEntity(Entity):
)
)
if "reasoning" not in model_args:
# Reasoning models handle this correctly with just a prompt
if not model_args["model"].startswith("o"):
# o-series models handle this correctly with just a prompt
remove_citations = True
tools.append(web_search)
@@ -15,5 +15,5 @@
"iot_class": "local_push",
"loggers": ["opendisplay"],
"quality_scale": "silver",
"requirements": ["py-opendisplay==5.9.0"]
"requirements": ["py-opendisplay==7.2.3"]
}
@@ -218,7 +218,7 @@ async def _async_upload_image(call: ServiceCall) -> None:
pil_image,
refresh_mode=refresh_mode,
dither_mode=dither_mode,
tone_compression=tone_compression,
tone=tone_compression,
fit=fit_mode,
rotate=rotation,
)
@@ -37,11 +37,15 @@ class OpenhomeConfigFlow(ConfigFlow, domain=DOMAIN):
_LOGGER.debug("async_step_ssdp: Incomplete discovery, ignoring")
return self.async_abort(reason="incomplete_discovery")
_LOGGER.debug(
"async_step_ssdp: setting unique id %s", discovery_info.upnp[ATTR_UPNP_UDN]
)
udn = discovery_info.upnp[ATTR_UPNP_UDN]
if isinstance(udn, list):
if not udn:
return self.async_abort(reason="incomplete_discovery")
udn = udn[0]
await self.async_set_unique_id(discovery_info.upnp[ATTR_UPNP_UDN])
_LOGGER.debug("async_step_ssdp: setting unique id %s", udn)
await self.async_set_unique_id(udn)
self._abort_if_unique_id_configured({CONF_HOST: discovery_info.ssdp_location})
_LOGGER.debug(
+16 -16
View File
@@ -29,29 +29,29 @@
}
},
"sensor": {
"translation_key_0": {
"default": "mdi:abc"
"flow_sensor_clicks_cubic_meter": {
"default": "mdi:water-pump"
},
"translation_key_1": {
"default": "mdi:abc"
"flow_sensor_consumed_liters": {
"default": "mdi:water-pump"
},
"translation_key_2": {
"default": "mdi:abc"
"flow_sensor_leak_clicks": {
"default": "mdi:pipe-leak"
},
"translation_key_3": {
"default": "mdi:abc"
"flow_sensor_leak_volume": {
"default": "mdi:pipe-leak"
},
"translation_key_4": {
"default": "mdi:abc"
"flow_sensor_start_index": {
"default": "mdi:water-pump"
},
"translation_key_5": {
"default": "mdi:abc"
"flow_sensor_watering_clicks": {
"default": "mdi:water-pump"
},
"translation_key_6": {
"default": "mdi:abc"
"last_leak_detected": {
"default": "mdi:pipe-leak"
},
"translation_key_7": {
"default": "mdi:abc"
"rain_sensor_rain_start": {
"default": "mdi:weather-pouring"
}
},
"switch": {
@@ -20,5 +20,5 @@
"iot_class": "local_push",
"loggers": ["reolink_aio"],
"quality_scale": "platinum",
"requirements": ["reolink-aio==0.19.1"]
"requirements": ["reolink-aio==0.20.0"]
}
@@ -23,7 +23,7 @@ from homeassistant.config_entries import (
ConfigFlowResult,
OptionsFlowWithReload,
)
from homeassistant.const import CONF_USERNAME
from homeassistant.const import CONF_REGION, CONF_USERNAME
from homeassistant.core import callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -38,7 +38,6 @@ from . import RoborockConfigEntry
from .const import (
CONF_BASE_URL,
CONF_ENTRY_CODE,
CONF_REGION,
CONF_SHOW_BACKGROUND,
CONF_SHOW_ROOMS,
CONF_SHOW_WALLS,
@@ -13,8 +13,6 @@ CONF_USER_DATA = "user_data"
CONF_SHOW_BACKGROUND = "show_background"
CONF_SHOW_WALLS = "show_walls"
CONF_SHOW_ROOMS = "show_rooms"
# pylint: disable-next=home-assistant-duplicate-const
CONF_REGION = "region"
REGION_OPTIONS = ["auto", "us", "eu", "ru", "cn"]
# Option Flow steps
+1 -2
View File
@@ -15,6 +15,7 @@ from homeassistant.components.sensor import (
SensorEntity,
)
from homeassistant.const import (
ATTR_MODEL,
CONF_MAC,
CONF_NAME,
EVENT_HOMEASSISTANT_STOP,
@@ -30,8 +31,6 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
_LOGGER = logging.getLogger(__name__)
ATTR_DEVICE = "device"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_MODEL = "model"
BLE_TEMP_HANDLE = 0x24
BLE_TEMP_UUID = "0000ff92-0000-1000-8000-00805f9b34fb"
+2 -2
View File
@@ -1,5 +1,7 @@
"""Define constants for the SleepIQ component."""
from homeassistant.const import PRESSURE
DATA_SLEEPIQ = "data_sleepiq"
DOMAIN = "sleepiq"
@@ -11,8 +13,6 @@ FIRMNESS = "firmness"
ICON_EMPTY = "mdi:bed-empty"
ICON_OCCUPIED = "mdi:bed"
IS_IN_BED = "is_in_bed"
# pylint: disable-next=home-assistant-duplicate-const
PRESSURE = "pressure"
SLEEP_NUMBER = "sleep_number"
FOOT_WARMING_TIMER = "foot_warming_timer"
FOOT_WARMER = "foot_warmer"
+1 -2
View File
@@ -11,14 +11,13 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import UnitOfTime
from homeassistant.const import PRESSURE, UnitOfTime
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import (
HEART_RATE,
HRV,
PRESSURE,
RESPIRATORY_RATE,
SLEEP_DURATION,
SLEEP_NUMBER,
-4
View File
@@ -38,12 +38,8 @@ PLATFORMS = [
Platform.SENSOR,
]
# pylint: disable-next=home-assistant-duplicate-const
SERVICE_LOCK = "lock"
SERVICE_REMOTE_START = "remote_start"
SERVICE_REMOTE_STOP = "remote_stop"
# pylint: disable-next=home-assistant-duplicate-const
SERVICE_UNLOCK = "unlock"
SERVICE_UNLOCK_SPECIFIC_DOOR = "unlock_specific_door"
ATTR_DOOR = "door"
@@ -4,9 +4,10 @@ import logging
from subarulink.exceptions import SubaruException
from homeassistant.const import SERVICE_UNLOCK
from homeassistant.exceptions import HomeAssistantError
from .const import SERVICE_REMOTE_START, SERVICE_UNLOCK, VEHICLE_NAME, VEHICLE_VIN
from .const import SERVICE_REMOTE_START, VEHICLE_NAME, VEHICLE_VIN
_LOGGER = logging.getLogger(__name__)
@@ -454,9 +454,7 @@ async def async_unload_entry(
hass: HomeAssistant, entry: SystemBridgeConfigEntry
) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
entry, [platform for platform in PLATFORMS if platform != Platform.NOTIFY]
)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
coordinator = entry.runtime_data
@@ -17,15 +17,51 @@
"boot_time": {
"default": "mdi:av-timer"
},
"cpu_power_core": {
"default": "mdi:chip"
},
"cpu_power_package": {
"default": "mdi:chip"
},
"cpu_speed": {
"default": "mdi:speedometer"
},
"display_refresh_rate": {
"default": "mdi:monitor"
},
"display_resolution_x": {
"default": "mdi:monitor"
},
"display_resolution_y": {
"default": "mdi:monitor"
},
"displays_connected": {
"default": "mdi:monitor"
},
"gpu_core_clock_speed": {
"default": "mdi:speedometer"
},
"gpu_fan_speed": {
"default": "mdi:fan"
},
"gpu_memory_clock_speed": {
"default": "mdi:speedometer"
},
"gpu_memory_free": {
"default": "mdi:memory"
},
"gpu_memory_used": {
"default": "mdi:memory"
},
"gpu_memory_used_percentage": {
"default": "mdi:memory"
},
"gpu_power_usage": {
"default": "mdi:lightning-bolt"
},
"gpu_usage_percentage": {
"default": "mdi:percent"
},
"kernel": {
"default": "mdi:devices"
},
@@ -38,6 +74,9 @@
"memory_used": {
"default": "mdi:memory"
},
"memory_used_percentage": {
"default": "mdi:memory"
},
"os": {
"default": "mdi:devices"
},
@@ -47,6 +86,12 @@
"processes": {
"default": "mdi:counter"
},
"processes_load_cpu": {
"default": "mdi:percent"
},
"space_used": {
"default": "mdi:harddisk"
},
"version": {
"default": "mdi:counter"
},
@@ -27,7 +27,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import UNDEFINED, StateType
from homeassistant.helpers.typing import StateType
from homeassistant.util import dt as dt_util
from .coordinator import SystemBridgeConfigEntry, SystemBridgeDataUpdateCoordinator
@@ -284,10 +284,10 @@ BASE_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = (
),
SystemBridgeSensorEntityDescription(
key="memory_used_percentage",
translation_key="memory_used_percentage",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=2,
icon="mdi:memory",
value=lambda data: data.memory.virtual.percent,
),
SystemBridgeSensorEntityDescription(
@@ -380,11 +380,11 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"filesystem_{partition.mount_point.replace(':', '')}",
name=f"{partition.mount_point} space used",
translation_key="space_used",
translation_placeholders={"partition": partition.mount_point},
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=2,
icon="mdi:harddisk",
value=(
lambda data, dk=index_device, pk=index_partition: (
partition_usage(data, dk, pk)
@@ -427,10 +427,10 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"display_{display.id}_resolution_x",
name=f"Display {display.id} resolution x",
translation_key="display_resolution_x",
translation_placeholders={"display_id": display.id},
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PIXELS,
icon="mdi:monitor",
value=lambda data, k=index: display_resolution_horizontal(
data, k
),
@@ -441,10 +441,10 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"display_{display.id}_resolution_y",
name=f"Display {display.id} resolution y",
translation_key="display_resolution_y",
translation_placeholders={"display_id": display.id},
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PIXELS,
icon="mdi:monitor",
value=lambda data, k=index: display_resolution_vertical(
data, k
),
@@ -455,12 +455,12 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"display_{display.id}_refresh_rate",
name=f"Display {display.id} refresh rate",
translation_key="display_refresh_rate",
translation_placeholders={"display_id": display.id},
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfFrequency.HERTZ,
device_class=SensorDeviceClass.FREQUENCY,
suggested_display_precision=0,
icon="mdi:monitor",
value=lambda data, k=index: display_refresh_rate(data, k),
),
entry.data[CONF_PORT],
@@ -474,13 +474,13 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"gpu_{gpu.id}_core_clock_speed",
name=f"{gpu.name} clock speed",
translation_key="gpu_core_clock_speed",
translation_placeholders={"gpu_name": gpu.name},
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ,
device_class=SensorDeviceClass.FREQUENCY,
suggested_display_precision=0,
icon="mdi:speedometer",
value=lambda data, k=index: gpu_core_clock_speed(data, k),
),
entry.data[CONF_PORT],
@@ -489,13 +489,13 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"gpu_{gpu.id}_memory_clock_speed",
name=f"{gpu.name} memory clock speed",
translation_key="gpu_memory_clock_speed",
translation_placeholders={"gpu_name": gpu.name},
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ,
device_class=SensorDeviceClass.FREQUENCY,
suggested_display_precision=0,
icon="mdi:speedometer",
value=lambda data, k=index: gpu_memory_clock_speed(data, k),
),
entry.data[CONF_PORT],
@@ -504,12 +504,12 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"gpu_{gpu.id}_memory_free",
name=f"{gpu.name} memory free",
translation_key="gpu_memory_free",
translation_placeholders={"gpu_name": gpu.name},
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
device_class=SensorDeviceClass.DATA_SIZE,
suggested_display_precision=0,
icon="mdi:memory",
value=lambda data, k=index: gpu_memory_free(data, k),
),
entry.data[CONF_PORT],
@@ -518,11 +518,11 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"gpu_{gpu.id}_memory_used_percentage",
name=f"{gpu.name} memory used %",
translation_key="gpu_memory_used_percentage",
translation_placeholders={"gpu_name": gpu.name},
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=2,
icon="mdi:memory",
value=lambda data, k=index: gpu_memory_used_percentage(data, k),
),
entry.data[CONF_PORT],
@@ -531,13 +531,13 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"gpu_{gpu.id}_memory_used",
name=f"{gpu.name} memory used",
translation_key="gpu_memory_used",
translation_placeholders={"gpu_name": gpu.name},
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
device_class=SensorDeviceClass.DATA_SIZE,
suggested_display_precision=0,
icon="mdi:memory",
value=lambda data, k=index: gpu_memory_used(data, k),
),
entry.data[CONF_PORT],
@@ -546,11 +546,11 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"gpu_{gpu.id}_fan_speed",
name=f"{gpu.name} fan speed",
translation_key="gpu_fan_speed",
translation_placeholders={"gpu_name": gpu.name},
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
icon="mdi:fan",
value=lambda data, k=index: gpu_fan_speed(data, k),
),
entry.data[CONF_PORT],
@@ -559,7 +559,8 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"gpu_{gpu.id}_power_usage",
name=f"{gpu.name} power usage",
translation_key="gpu_power_usage",
translation_placeholders={"gpu_name": gpu.name},
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfPower.WATT,
@@ -571,7 +572,8 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"gpu_{gpu.id}_temperature",
name=f"{gpu.name} temperature",
translation_key="gpu_temperature",
translation_placeholders={"gpu_name": gpu.name},
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
@@ -585,11 +587,11 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"gpu_{gpu.id}_usage_percentage",
name=f"{gpu.name} usage %",
translation_key="gpu_usage_percentage",
translation_placeholders={"gpu_name": gpu.name},
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=2,
icon="mdi:percent",
value=lambda data, k=index: gpu_usage_percentage(data, k),
),
entry.data[CONF_PORT],
@@ -605,11 +607,11 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"processes_load_cpu_{cpu.id}",
name=f"Load CPU {cpu.id}",
translation_key="processes_load_cpu",
translation_placeholders={"cpu_id": str(cpu.id)},
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
icon="mdi:percent",
suggested_display_precision=2,
value=lambda data, k=cpu.id: cpu_usage_per_cpu(data, k),
),
@@ -619,11 +621,11 @@ async def async_setup_entry(
coordinator,
SystemBridgeSensorEntityDescription(
key=f"cpu_power_core_{cpu.id}",
name=f"CPU Core {cpu.id} Power",
translation_key="cpu_power_core",
translation_placeholders={"cpu_id": str(cpu.id)},
entity_registry_enabled_default=False,
native_unit_of_measurement=UnitOfPower.WATT,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:chip",
suggested_display_precision=2,
value=lambda data, k=cpu.id: cpu_power_per_cpu(data, k),
),
@@ -653,8 +655,6 @@ class SystemBridgeSensor(SystemBridgeEntity, SensorEntity):
description.key,
)
self.entity_description = description
if description.name is not UNDEFINED:
self._attr_has_entity_name = False
@property
def native_value(self) -> StateType:
@@ -89,3 +89,4 @@ power_command:
- "restart"
- "shutdown"
- "sleep"
translation_key: "power_command"
@@ -54,6 +54,9 @@
"boot_time": {
"name": "Boot time"
},
"cpu_power_core": {
"name": "CPU core {cpu_id} power"
},
"cpu_power_package": {
"name": "CPU package power"
},
@@ -66,9 +69,45 @@
"cpu_voltage": {
"name": "CPU voltage"
},
"display_refresh_rate": {
"name": "Display {display_id} refresh rate"
},
"display_resolution_x": {
"name": "Display {display_id} resolution x"
},
"display_resolution_y": {
"name": "Display {display_id} resolution y"
},
"displays_connected": {
"name": "Displays connected"
},
"gpu_core_clock_speed": {
"name": "{gpu_name} clock speed"
},
"gpu_fan_speed": {
"name": "{gpu_name} fan speed"
},
"gpu_memory_clock_speed": {
"name": "{gpu_name} memory clock speed"
},
"gpu_memory_free": {
"name": "{gpu_name} memory free"
},
"gpu_memory_used": {
"name": "{gpu_name} memory used"
},
"gpu_memory_used_percentage": {
"name": "{gpu_name} memory used %"
},
"gpu_power_usage": {
"name": "{gpu_name} power usage"
},
"gpu_temperature": {
"name": "{gpu_name} temperature"
},
"gpu_usage_percentage": {
"name": "{gpu_name} usage %"
},
"kernel": {
"name": "Kernel"
},
@@ -81,6 +120,9 @@
"memory_used": {
"name": "Memory used"
},
"memory_used_percentage": {
"name": "Memory used %"
},
"os": {
"name": "Operating system"
},
@@ -90,6 +132,12 @@
"processes": {
"name": "Processes"
},
"processes_load_cpu": {
"name": "Load CPU {cpu_id}"
},
"space_used": {
"name": "{partition} space used"
},
"version": {
"name": "Version"
},
@@ -130,6 +178,18 @@
"title": "System Bridge upgrade required"
}
},
"selector": {
"power_command": {
"options": {
"hibernate": "Hibernate",
"lock": "Lock",
"logout": "Logout",
"restart": "[%key:common::action::restart%]",
"shutdown": "Shutdown",
"sleep": "Sleep"
}
}
},
"services": {
"get_process_by_id": {
"description": "Gets a process by the ID.",
@@ -32,6 +32,7 @@ class SystemBridgeUpdateEntity(SystemBridgeEntity, UpdateEntity):
_attr_has_entity_name = True
_attr_title = "System Bridge"
_attr_name = None
def __init__(
self,
@@ -44,7 +45,6 @@ class SystemBridgeUpdateEntity(SystemBridgeEntity, UpdateEntity):
api_port,
"update",
)
self._attr_name = coordinator.data.system.hostname
@property
def installed_version(self) -> str | None:
@@ -58,7 +58,7 @@ send_message:
selector:
number:
mode: box
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -101,7 +101,7 @@ send_chat_action:
selector:
number:
mode: box
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -195,7 +195,7 @@ send_photo:
selector:
number:
mode: box
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -287,7 +287,7 @@ send_media_group:
selector:
number:
mode: box
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -372,7 +372,7 @@ send_sticker:
selector:
number:
mode: box
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -466,7 +466,7 @@ send_animation:
selector:
number:
mode: box
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -560,7 +560,7 @@ send_video:
selector:
number:
mode: box
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -645,7 +645,7 @@ send_voice:
selector:
number:
mode: box
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -739,7 +739,7 @@ send_document:
selector:
number:
mode: box
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -804,7 +804,7 @@ send_location:
selector:
number:
mode: box
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -861,7 +861,7 @@ send_poll:
selector:
number:
mode: box
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -913,7 +913,7 @@ edit_message:
["Text button2", "/button2"]], [["Text button3", "/button3"]]]'
selector:
object:
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -991,7 +991,7 @@ edit_message_media:
["Text button2", "/button2"]], [["Text button3", "/button3"]]]'
selector:
object:
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -1028,7 +1028,7 @@ edit_caption:
["Text button2", "/button2"]], [["Text button3", "/button3"]]]'
selector:
object:
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -1061,7 +1061,7 @@ edit_replymarkup:
["Text button2", "/button2"]], [["Text button3", "/button3"]]]'
selector:
object:
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -1108,7 +1108,7 @@ delete_message:
example: "{{ trigger.event.data.message.message_id }}"
selector:
text:
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -1129,7 +1129,7 @@ leave_chat:
filter:
domain: notify
integration: telegram_bot
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -1164,7 +1164,7 @@ set_message_reaction:
required: false
selector:
boolean:
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -1233,7 +1233,7 @@ send_message_draft:
- "markdownv2"
- "plain_text"
translation_key: "parse_mode"
advanced:
additional_fields:
collapsed: true
fields:
config_entry_id:
@@ -367,8 +367,8 @@
},
"name": "Delete message",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
}
}
},
@@ -425,8 +425,8 @@
},
"name": "Edit caption",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
}
}
},
@@ -472,8 +472,8 @@
},
"name": "Edit message",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
}
}
},
@@ -535,8 +535,8 @@
},
"name": "Edit message media",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
},
"url_options": {
"name": "[%key:component::telegram_bot::services::send_photo::sections::url_options::name%]"
@@ -569,8 +569,8 @@
},
"name": "Edit reply markup",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
}
}
},
@@ -592,8 +592,8 @@
},
"name": "Leave chat",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
}
}
},
@@ -671,8 +671,8 @@
},
"name": "Send animation",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
},
"url_options": {
"name": "[%key:component::telegram_bot::services::send_photo::sections::url_options::name%]"
@@ -705,8 +705,8 @@
},
"name": "Send chat action",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
}
}
},
@@ -784,8 +784,8 @@
},
"name": "Send document",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
},
"url_options": {
"name": "[%key:component::telegram_bot::services::send_photo::sections::url_options::name%]"
@@ -842,8 +842,8 @@
},
"name": "Send location",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
}
}
},
@@ -889,8 +889,8 @@
},
"name": "Send media group",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
}
}
},
@@ -952,8 +952,8 @@
},
"name": "Send message",
"sections": {
"advanced": {
"name": "Advanced"
"additional_fields": {
"name": "Additional options"
}
}
},
@@ -991,8 +991,8 @@
},
"name": "Send message draft",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
}
}
},
@@ -1070,8 +1070,8 @@
},
"name": "Send photo",
"sections": {
"advanced": {
"name": "Advanced"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
},
"url_options": {
"name": "URL options"
@@ -1128,8 +1128,8 @@
},
"name": "Send poll",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
}
}
},
@@ -1203,8 +1203,8 @@
},
"name": "Send sticker",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
},
"url_options": {
"name": "[%key:component::telegram_bot::services::send_photo::sections::url_options::name%]"
@@ -1285,8 +1285,8 @@
},
"name": "Send video",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
},
"url_options": {
"name": "[%key:component::telegram_bot::services::send_photo::sections::url_options::name%]"
@@ -1363,8 +1363,8 @@
},
"name": "Send voice",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
},
"url_options": {
"name": "[%key:component::telegram_bot::services::send_photo::sections::url_options::name%]"
@@ -1401,8 +1401,8 @@
},
"name": "Set message reaction",
"sections": {
"advanced": {
"name": "[%key:component::telegram_bot::services::send_message::sections::advanced::name%]"
"additional_fields": {
"name": "[%key:component::telegram_bot::services::send_message::sections::additional_fields::name%]"
}
}
}
@@ -497,7 +497,7 @@
"default": "mdi:battery-clock"
},
"forward_collision_warning": {
"default": "mdi:car-crash",
"default": "mdi:car-emergency",
"state": {
"average": "mdi:alert-circle",
"early": "mdi:alert-octagon",
@@ -634,7 +634,7 @@
"default": "mdi:key"
},
"pedal_position": {
"default": "mdi:pedestal"
"default": "mdi:gauge"
},
"powershare_hours_left": {
"default": "mdi:clock-time-eight-outline"
@@ -794,7 +794,7 @@
"service": "mdi:calendar-plus"
},
"add_precondition_schedule": {
"service": "mdi:hvac-outline"
"service": "mdi:hvac"
},
"navigation_gps_request": {
"service": "mdi:crosshairs-gps"
@@ -803,7 +803,7 @@
"service": "mdi:calendar-minus"
},
"remove_precondition_schedule": {
"service": "mdi:hvac-off-outline"
"service": "mdi:hvac-off"
},
"set_scheduled_charging": {
"service": "mdi:timeline-clock-outline"
@@ -7,7 +7,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import PLATFORMS
from .const import DOMAIN, PLATFORMS
from .coordinator import UptimeRobotConfigEntry, UptimeRobotDataUpdateCoordinator
@@ -15,9 +15,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: UptimeRobotConfigEntry)
"""Set up UptimeRobot from a config entry."""
key: str = entry.data[CONF_API_KEY]
if key.startswith(("ur", "m")):
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryAuthFailed(
"Wrong API key type detected, use the 'main' API key"
translation_domain=DOMAIN,
translation_key="api_key_wrong_type",
)
uptime_robot_api = UptimeRobot(key, async_get_clientsession(hass))
@@ -48,11 +48,16 @@ class UptimeRobotDataUpdateCoordinator(
try:
response = await self.api.async_get_monitors()
except UptimeRobotAuthenticationException as exception:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryAuthFailed(exception) from exception
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="api_authentication_exception",
) from exception
except UptimeRobotException as exception:
# pylint: disable-next=home-assistant-exception-not-translated
raise UpdateFailed(exception) from exception
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="api_generic_exception",
translation_placeholders={"error": "Generic UptimeRobot exception"},
) from exception
if TYPE_CHECKING:
assert isinstance(response.data, list)
@@ -57,7 +57,16 @@
}
},
"exceptions": {
"api_exception": {
"api_authentication_exception": {
"message": "API authentication failed, please check your API key"
},
"api_generic_exception": {
"message": "API error: {error}"
},
"api_key_wrong_type": {
"message": "Wrong API key type detected, use the 'main' API key"
},
"api_switch_exception": {
"message": "Could not turn on/off monitoring: {error}"
}
}
@@ -33,7 +33,7 @@ def uptimerobot_api_call[_T: UptimeRobotEntity, **_P](
except UptimeRobotException as exception:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="api_exception",
translation_key="api_switch_exception",
translation_placeholders={"error": "Generic UptimeRobot exception"},
) from exception
@@ -68,7 +68,7 @@
"state": {
"lightning": "mdi:weather-lightning-rainy",
"rain": "mdi:weather-rainy",
"rain_snow": "mdi:weather-snoy-rainy",
"rain_snow": "mdi:weather-snowy-rainy",
"snow": "mdi:weather-snowy"
}
},
+4 -2
View File
@@ -46,8 +46,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: WebOsTvConfigEntry) -> b
try:
await client.connect()
except WebOsTvPairError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryAuthFailed(err) from err
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="auth_failed",
) from err
# If pairing request accepted there will be no error
# Update the stored key without triggering reauth
@@ -6,6 +6,7 @@ from homeassistant.components.device_automation import (
DEVICE_TRIGGER_BASE_SCHEMA,
InvalidDeviceAutomationConfig,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_DEVICE_ID, CONF_PLATFORM, CONF_TYPE
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
@@ -13,10 +14,7 @@ from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
from homeassistant.helpers.typing import ConfigType
from . import DOMAIN, trigger
from .helpers import (
async_get_client_by_device_entry,
async_get_device_entry_by_device_id,
)
from .helpers import async_get_device_entry_by_device_id
from .triggers.turn_on import (
PLATFORM_TYPE as TURN_ON_PLATFORM_TYPE,
async_get_turn_on_trigger,
@@ -40,10 +38,31 @@ async def async_validate_trigger_config(
device_id = config[CONF_DEVICE_ID]
try:
device = async_get_device_entry_by_device_id(hass, device_id)
async_get_client_by_device_entry(hass, device)
except ValueError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise InvalidDeviceAutomationConfig(err) from err
raise InvalidDeviceAutomationConfig(
translation_domain=DOMAIN,
translation_key="device_not_valid",
translation_placeholders={"device_id": device_id},
) from err
for config_entry_id in device.config_entries:
if (
entry := hass.config_entries.async_get_entry(config_entry_id)
) and entry.domain == DOMAIN:
if entry.state is ConfigEntryState.LOADED:
break
raise InvalidDeviceAutomationConfig(
translation_domain=DOMAIN,
translation_key="device_config_entry_not_loaded",
translation_placeholders={"device_id": device.id},
)
else:
raise InvalidDeviceAutomationConfig(
translation_domain=DOMAIN,
translation_key="device_not_valid",
translation_placeholders={"device_id": device.id},
)
return config
+1 -26
View File
@@ -4,7 +4,7 @@ import logging
from aiowebostv import WebOsClient, WebOsTvState
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
@@ -56,31 +56,6 @@ def async_get_device_id_from_entity_id(hass: HomeAssistant, entity_id: str) -> s
return entity_entry.device_id
@callback
def async_get_client_by_device_entry(
hass: HomeAssistant, device: DeviceEntry
) -> WebOsClient:
"""Get WebOsClient from Device Registry by device entry.
Raises ValueError if client is not found.
"""
for config_entry_id in device.config_entries:
entry: WebOsTvConfigEntry | None = hass.config_entries.async_get_entry(
config_entry_id
)
if entry and entry.domain == DOMAIN:
if entry.state is ConfigEntryState.LOADED:
return entry.runtime_data
raise ValueError(
f"Device {device.id} is not from a loaded {DOMAIN} config entry"
)
raise ValueError(
f"Device {device.id} is not from an existing {DOMAIN} config entry"
)
def get_sources(tv_state: WebOsTvState) -> list[str]:
"""Construct sources list."""
sources = []
@@ -46,9 +46,18 @@
}
},
"exceptions": {
"auth_failed": {
"message": "Pairing failed, make sure to accept the pairing request on your TV."
},
"communication_error": {
"message": "Communication error while calling {func} for device {name}: {error}"
},
"device_config_entry_not_loaded": {
"message": "The LG webOS TV integration for device {device_id} is not loaded."
},
"device_not_valid": {
"message": "Device {device_id} is not a valid LG webOS TV device."
},
"device_off": {
"message": "Error calling {func} for device {name}: Device is off and cannot be controlled."
},
@@ -28,7 +28,7 @@ from miio.integrations.humidifier.zhimi.airhumidifier_miot import (
)
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import CONF_DEVICE, CONF_MODEL, EntityCategory
from homeassistant.const import ATTR_MODE, CONF_DEVICE, CONF_MODEL, EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@@ -65,8 +65,6 @@ from .typing import XiaomiMiioConfigEntry
ATTR_DISPLAY_ORIENTATION = "display_orientation"
ATTR_LED_BRIGHTNESS = "led_brightness"
ATTR_PTC_LEVEL = "ptc_level"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_MODE = "mode"
_LOGGER = logging.getLogger(__name__)
@@ -25,6 +25,7 @@ from homeassistant.components.switch import (
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_MODE,
ATTR_MODEL,
ATTR_TEMPERATURE,
CONF_DEVICE,
CONF_HOST,
@@ -149,8 +150,6 @@ ATTR_LED = "led"
ATTR_IONIZER = "ionizer"
ATTR_ANION = "anion"
ATTR_LOAD_POWER = "load_power"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_MODEL = "model"
ATTR_POWER = "power"
ATTR_POWER_MODE = "power_mode"
ATTR_POWER_PRICE = "power_price"
@@ -21,4 +21,4 @@ CONF_INSTANCE_ID = "instance_id"
# Polling interval (seconds)
DEFAULT_SCAN_INTERVAL = 1800
PLATFORMS: list[Platform] = [Platform.LIGHT, Platform.SWITCH]
PLATFORMS: list[Platform] = [Platform.LIGHT, Platform.LOCK, Platform.SWITCH]
@@ -0,0 +1,47 @@
"""Lock platform for Xthings Cloud."""
from typing import Any
from homeassistant.components.lock import LockEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import XthingsCloudConfigEntry
from .entity import XthingsCloudEntity
async def async_setup_entry(
hass: HomeAssistant,
entry: XthingsCloudConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up lock platform."""
coordinator = entry.runtime_data
entities = [
XthingsCloudLock(coordinator, device_id, device_data)
for device_id, device_data in coordinator.data.items()
if device_data["type"] == "lock"
]
async_add_entities(entities)
class XthingsCloudLock(XthingsCloudEntity, LockEntity):
"""Xthings Cloud lock entity."""
@property
def is_locked(self) -> bool | None:
"""Return true if lock is locked."""
return self.device_data["status"].get("locked")
@property
def is_jammed(self) -> bool | None:
"""Return true if lock is jammed."""
return self.device_data["status"].get("jammed")
async def async_lock(self, **kwargs: Any) -> None:
"""Lock the device."""
await self.coordinator.client.async_lock_lock(self._device_id)
async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock the device."""
await self.coordinator.client.async_lock_unlock(self._device_id)
+3 -3
View File
@@ -1,7 +1,7 @@
# Automatically generated by gen_requirements_all.py, do not edit
aiodhcpwatcher==1.2.1
aiodiscover==3.2.0
aiodiscover==3.2.3
aiodns==4.0.4
aiogithubapi==26.0.0
aiohttp-asyncmdnsresolver==0.1.1
@@ -30,12 +30,12 @@ certifi>=2021.5.30
ciso8601==2.3.3
cronsim==2.7
cryptography==48.0.0
dbus-fast==5.0.0
dbus-fast==5.0.3
file-read-backwards==2.0.0
fnv-hash-fast==2.0.2
go2rtc-client==0.4.0
ha-ffmpeg==3.2.2
habluetooth==6.1.0
habluetooth==6.2.0
hass-nabucasa==2.2.0
hassil==3.5.0
home-assistant-bluetooth==2.0.0
@@ -0,0 +1,113 @@
"""Checker for invalid MDI icon references.
Validates that ``mdi:`` icon references in integration code and
``icons.json`` files refer to icons that actually exist in the
Material Design Icons set.
- ``E7409``: MDI icon reference not found in Python code
- ``E7410``: MDI icon reference not found in icons.json
"""
import re
from astroid import nodes
from pylint.checkers import BaseChecker
from pylint.lint import PyLinter
from pylint_home_assistant.generated.mdi_icons import MDI_ICONS
from pylint_home_assistant.helpers.icons import collect_mdi_icons, load_icons
from pylint_home_assistant.helpers.module_info import parse_module
# Matches strings that look like intentional icon name attempts
# (letters, digits, hyphens, underscores). Rejects format templates
# (%s, {}, {name}), empty names, and other dynamic patterns.
_LOOKS_LIKE_ICON_NAME = re.compile(r"^[a-zA-Z][a-zA-Z0-9_-]*[a-zA-Z0-9]$")
class MdiIconsChecker(BaseChecker):
"""Checker for invalid MDI icon references."""
name = "home_assistant_mdi_icons"
priority = -1
msgs = {
"E7409": (
"MDI icon '%s' does not exist in the Material Design Icons set",
"home-assistant-mdi-icon-not-found",
"Used when an integration references an MDI icon in Python "
"code that does not exist. Check the icon name at "
"https://pictogrammers.com/library/mdi/",
),
"E7410": (
"MDI icon '%s' in icons.json does not exist in the "
"Material Design Icons set",
"home-assistant-mdi-icon-json-not-found",
"Used when an integration's icons.json references an MDI "
"icon that does not exist. Check the icon name at "
"https://pictogrammers.com/library/mdi/",
),
}
options = ()
_in_integration: bool
_checked_icons_json: set[str]
def open(self) -> None:
"""Initialize per-run state."""
self._checked_icons_json = set()
def visit_module(self, node: nodes.Module) -> None:
"""Check icons.json and track integration context."""
parsed = parse_module(node.name)
self._in_integration = parsed is not None
if parsed is None:
return
# Only check icons.json once per integration
if parsed.domain in self._checked_icons_json:
return
self._checked_icons_json.add(parsed.domain)
icons_data = load_icons(node)
if icons_data is None:
return
mdi_refs = collect_mdi_icons(icons_data)
for icon_ref in sorted(mdi_refs):
icon_name = icon_ref[4:] # Strip "mdi:" prefix
if icon_name not in MDI_ICONS:
self.add_message(
"home-assistant-mdi-icon-json-not-found",
node=node,
args=(icon_ref,),
)
def visit_const(self, node: nodes.Const) -> None:
"""Check string constants for invalid MDI icon references."""
if not self._in_integration:
return
if not isinstance(node.value, str):
return
if not node.value.startswith("mdi:"):
return
icon_name = node.value[4:] # Strip "mdi:" prefix
# Only check names that look like intentional icon name attempts.
# This skips f-string fragments, format templates (%s, {}),
# partial names, and other dynamic patterns.
if not _LOOKS_LIKE_ICON_NAME.match(icon_name):
return
if icon_name not in MDI_ICONS:
self.add_message(
"home-assistant-mdi-icon-not-found",
node=node,
args=(node.value,),
)
def register(linter: PyLinter) -> None:
"""Register the checker."""
linter.register_checker(MdiIconsChecker(linter))
@@ -0,0 +1 @@
"""Generated files for the pylint Home Assistant plugin."""
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,60 @@
"""Helpers for reading integration icon files."""
import contextlib
from astroid import nodes
import orjson
from .integration import get_integration_dir
_icons_cache: dict[str, dict | None] = {}
def clear_icons_cache() -> None:
"""Clear the icons cache (used by tests)."""
_icons_cache.clear()
def load_icons(module: nodes.Module) -> dict | None:
"""Load and cache the icons.json for the current integration.
Returns the parsed JSON as a dict, or ``None`` if not found.
"""
integration_dir = get_integration_dir(module)
if integration_dir is None:
return None
cache_key = str(integration_dir)
if cache_key in _icons_cache:
return _icons_cache[cache_key]
icons_path = integration_dir / "icons.json"
result: dict | None = None
if icons_path.exists():
with contextlib.suppress(orjson.JSONDecodeError, OSError):
parsed = orjson.loads(icons_path.read_bytes())
if isinstance(parsed, dict):
result = parsed
_icons_cache[cache_key] = result
return result
def collect_mdi_icons(
data: dict | list | str, icons: set[str] | None = None
) -> set[str]:
"""Recursively collect all mdi: icon references from a data structure."""
if icons is None:
icons = set()
if isinstance(data, str):
if data.startswith("mdi:"):
icons.add(data)
elif isinstance(data, dict):
for value in data.values():
collect_mdi_icons(value, icons)
elif isinstance(data, list):
for item in data:
collect_mdi_icons(item, icons)
return icons
+6 -6
View File
@@ -233,7 +233,7 @@ aiocomelit==2.0.3
aiodhcpwatcher==1.2.1
# homeassistant.components.dhcp
aiodiscover==3.2.0
aiodiscover==3.2.3
# homeassistant.components.dnsip
aiodns==4.0.4
@@ -794,7 +794,7 @@ datadog==0.52.0
datapoint==0.12.1
# homeassistant.components.bluetooth
dbus-fast==5.0.0
dbus-fast==5.0.3
# homeassistant.components.debugpy
debugpy==1.8.17
@@ -1210,7 +1210,7 @@ ha-xthings-cloud==1.0.5
habiticalib==0.4.7
# homeassistant.components.bluetooth
habluetooth==6.1.0
habluetooth==6.2.0
# homeassistant.components.hanna
hanna-cloud==0.0.7
@@ -1923,7 +1923,7 @@ py-nightscout==1.2.2
py-nymta==0.4.0
# homeassistant.components.opendisplay
py-opendisplay==5.9.0
py-opendisplay==7.2.3
# homeassistant.components.schluter
py-schluter==0.1.7
@@ -2641,7 +2641,7 @@ python-google-weather-api==0.0.6
python-homeassistant-analytics==0.9.0
# homeassistant.components.homewizard
python-homewizard-energy==10.0.1
python-homewizard-energy==10.1.0
# homeassistant.components.hp_ilo
python-hpilo==4.4.3
@@ -2871,7 +2871,7 @@ renault-api==0.5.10
renson-endura-delta==1.7.2
# homeassistant.components.reolink
reolink-aio==0.19.1
reolink-aio==0.20.0
# homeassistant.components.radio_frequency
rf-protocols==3.2.0
View File
+1 -1
View File
@@ -3,4 +3,4 @@
codespell==2.4.2
ruff==0.15.13
yamllint==1.38.0
zizmor==1.25.0
zizmor==1.24.1
+2
View File
@@ -23,6 +23,7 @@ from . import (
json,
labs,
manifest,
mdi_icons,
metadata,
mqtt,
mypy_config,
@@ -65,6 +66,7 @@ INTEGRATION_PLUGINS = [
HASS_PLUGINS = [
core_files,
docker,
mdi_icons,
mypy_config,
metadata,
]
+76
View File
@@ -0,0 +1,76 @@
"""Generate MDI icons file for the pylint plugin."""
from importlib.metadata import PackageNotFoundError, version
from importlib.resources import files
import json
from .model import Config, Integration
from .serializer import format_python_namespace
_TARGET = "pylint/plugins/pylint_home_assistant/generated/mdi_icons.py"
def _get_frontend_version() -> str | None:
"""Get the installed home-assistant-frontend version."""
try:
return version("home-assistant-frontend")
except PackageNotFoundError:
return None
def _load_mdi_icons() -> set[str]:
"""Load the MDI icon names from the frontend package."""
try:
mdi_dir = files("hass_frontend") / "static" / "mdi"
icon_list_path = mdi_dir / "iconList.json"
data = json.loads(icon_list_path.read_text(encoding="utf-8"))
return {icon["name"] for icon in data}
except ImportError, FileNotFoundError, json.JSONDecodeError, KeyError:
return set()
def validate(integrations: dict[str, Integration], config: Config) -> None:
"""Validate the generated MDI icons file is up to date."""
frontend_version = _get_frontend_version()
if frontend_version is None:
return
icons = _load_mdi_icons()
if not icons:
config.add_error(
"mdi_icons",
"Could not load MDI icons from home-assistant-frontend",
)
return
content = format_python_namespace(
{
"FRONTEND_VERSION": frontend_version,
"MDI_ICONS": icons,
},
annotations={
"FRONTEND_VERSION": "Final[str]",
"MDI_ICONS": "Final[set[str]]",
},
)
config.cache["mdi_icons_content"] = content
if config.specific_integrations:
return
target_path = config.root / _TARGET
if not target_path.exists() or target_path.read_text() != content:
config.add_error(
"mdi_icons",
f"File {_TARGET} is not up to date. Run python3 -m script.hassfest",
fixable=True,
)
def generate(integrations: dict[str, Integration], config: Config) -> None:
"""Generate MDI icons file."""
if "mdi_icons_content" not in config.cache:
return
target_path = config.root / _TARGET
target_path.write_text(config.cache["mdi_icons_content"])
+75 -16
View File
@@ -2,13 +2,19 @@
"""Helper script to split test into n buckets."""
import argparse
from concurrent.futures import ProcessPoolExecutor
from dataclasses import dataclass, field
from math import ceil
import os
from pathlib import Path
import subprocess
import sys
from typing import Final
# tests/components has ~1000 sub-directories, which makes it the natural
# place to subdivide to keep each pytest invocation roughly equal in size.
_FAN_OUT_DIRS: Final = frozenset({"components"})
class Bucket:
"""Class to hold bucket."""
@@ -164,33 +170,86 @@ class TestFolder:
return result
def collect_tests(path: Path) -> TestFolder:
"""Collect all tests."""
def _collect_batch(paths: list[Path]) -> tuple[str, str, int]:
"""Run pytest --collect-only on a batch of paths."""
result = subprocess.run(
["pytest", "--collect-only", "-qq", "-p", "no:warnings", path],
["pytest", "--collect-only", "-qq", "-p", "no:warnings", *map(str, paths)],
check=False,
capture_output=True,
text=True,
)
return result.stdout, result.stderr, result.returncode
if result.returncode != 0:
print("Failed to collect tests:")
print(result.stderr)
print(result.stdout)
def _iter_eligible_children(path: Path) -> list[Path]:
"""Return immediate children of ``path`` that pytest should collect.
Filters out hidden/dunder entries, non-``test_*.py`` files (so helper
modules like ``conftest.py`` and ``common.py`` are not passed as
explicit collection targets), and pycache-style directories.
"""
children: list[Path] = []
for entry in sorted(path.iterdir()):
if entry.name.startswith((".", "_")):
continue
if entry.is_dir() or (entry.suffix == ".py" and entry.name.startswith("test_")):
children.append(entry)
return children
def _enumerate_batch_paths(path: Path) -> list[Path]:
"""Return the child paths to run pytest --collect-only over.
Files are returned as-is. Directories are expanded one level deep, with
a second level of expansion for entries named in ``_FAN_OUT_DIRS`` so the
enormous ``tests/components`` tree fans out into per-integration paths.
"""
if path.is_file():
return [path]
paths: list[Path] = []
for entry in _iter_eligible_children(path):
if entry.is_dir() and entry.name in _FAN_OUT_DIRS:
paths.extend(_iter_eligible_children(entry))
else:
paths.append(entry)
return paths
def collect_tests(path: Path) -> TestFolder:
"""Collect all tests."""
batch_paths = _enumerate_batch_paths(path)
if not batch_paths:
print(f"No eligible test paths found under {path}")
sys.exit(1)
workers = min(len(batch_paths), os.cpu_count() or 1) or 1
# Round-robin chunking keeps batches roughly balanced when path
# ordering correlates with test size.
batches = [batch_paths[i::workers] for i in range(workers)]
if workers == 1:
results = [_collect_batch(batches[0])]
else:
with ProcessPoolExecutor(max_workers=workers) as executor:
results = list(executor.map(_collect_batch, batches))
folder = TestFolder(path)
for line in result.stdout.splitlines():
if not line.strip():
continue
file_path, _, total_tests = line.partition(": ")
if not path or not total_tests:
print(f"Unexpected line: {line}")
for stdout, stderr, returncode in results:
if returncode != 0:
print("Failed to collect tests:")
print(stderr)
print(stdout)
sys.exit(1)
for line in stdout.splitlines():
if not line.strip():
continue
file_path, _, total_tests = line.partition(": ")
if not file_path or not total_tests:
print(f"Unexpected line: {line}")
sys.exit(1)
file = TestFile(int(total_tests), Path(file_path))
folder.add_test_file(file)
file = TestFile(int(total_tests), Path(file_path))
folder.add_test_file(file)
return folder
@@ -102,37 +102,37 @@ async def test_login(hass: HomeAssistant) -> None:
provider = hass.auth.auth_providers[0]
result = await hass.auth.login_flow.async_init((provider.type, provider.id))
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"username": "incorrect-user", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_auth"
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"username": "test-user", "password": "incorrect-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_auth"
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"username": "test-user", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "mfa"
assert result["data_schema"].schema.get("pin") is str
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"pin": "invalid-code"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_code"
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"pin": "123456"}
)
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"].id == "mock-id"
@@ -149,9 +149,9 @@ async def test_setup_flow(hass: HomeAssistant) -> None:
flow = await auth_module.async_setup_flow("new-user")
result = await flow.async_step_init()
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
result = await flow.async_step_init({"pin": "abcdefg"})
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert auth_module._data[1]["user_id"] == "new-user"
assert auth_module._data[1]["pin"] == "abcdefg"
+15 -15
View File
@@ -137,25 +137,25 @@ async def test_login_flow_validates_mfa(hass: HomeAssistant) -> None:
provider = hass.auth.auth_providers[0]
result = await hass.auth.login_flow.async_init((provider.type, provider.id))
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"username": "incorrect-user", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_auth"
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"username": "test-user", "password": "incorrect-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_auth"
with patch("pyotp.HOTP.at", return_value=MOCK_CODE):
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"username": "test-user", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "mfa"
assert result["data_schema"].schema.get("code") is str
@@ -173,7 +173,7 @@ async def test_login_flow_validates_mfa(hass: HomeAssistant) -> None:
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"code": "invalid-code"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "mfa"
assert result["errors"]["base"] == "invalid_code"
@@ -191,7 +191,7 @@ async def test_login_flow_validates_mfa(hass: HomeAssistant) -> None:
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"code": "invalid-code"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "mfa"
assert result["errors"]["base"] == "invalid_code"
@@ -199,7 +199,7 @@ async def test_login_flow_validates_mfa(hass: HomeAssistant) -> None:
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"code": "invalid-code"}
)
assert result["type"] == data_entry_flow.FlowResultType.ABORT
assert result["type"] is data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "too_many_retry"
# wait service call finished
@@ -207,13 +207,13 @@ async def test_login_flow_validates_mfa(hass: HomeAssistant) -> None:
# restart login
result = await hass.auth.login_flow.async_init((provider.type, provider.id))
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
with patch("pyotp.HOTP.at", return_value=MOCK_CODE):
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"username": "test-user", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "mfa"
assert result["data_schema"].schema.get("code") is str
@@ -231,7 +231,7 @@ async def test_login_flow_validates_mfa(hass: HomeAssistant) -> None:
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"code": MOCK_CODE}
)
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"].id == "mock-id"
@@ -246,7 +246,7 @@ async def test_setup_user_notify_service(hass: HomeAssistant) -> None:
flow = await notify_auth_module.async_setup_flow("test-user")
step = await flow.async_step_init()
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
assert step["step_id"] == "init"
schema = step["data_schema"]
schema({"notify_service": "test2"})
@@ -277,7 +277,7 @@ async def test_setup_user_notify_service(hass: HomeAssistant) -> None:
with patch("pyotp.HOTP.at", return_value=MOCK_CODE):
step = await flow.async_step_init({"notify_service": "test1"})
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
assert step["step_id"] == "setup"
# wait service call finished
@@ -357,7 +357,7 @@ async def test_setup_user_no_notify_service(hass: HomeAssistant) -> None:
flow = await notify_auth_module.async_setup_flow("test-user")
step = await flow.async_step_init()
assert step["type"] == data_entry_flow.FlowResultType.ABORT
assert step["type"] is data_entry_flow.FlowResultType.ABORT
assert step["reason"] == "no_available_service"
@@ -394,13 +394,13 @@ async def test_not_raise_exception_when_service_not_exist(hass: HomeAssistant) -
provider = hass.auth.auth_providers[0]
result = await hass.auth.login_flow.async_init((provider.type, provider.id))
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
with patch("pyotp.HOTP.at", return_value=MOCK_CODE):
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"username": "test-user", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.ABORT
assert result["type"] is data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "unknown_error"
# wait service call finished
+6 -6
View File
@@ -95,24 +95,24 @@ async def test_login_flow_validates_mfa(hass: HomeAssistant) -> None:
provider = hass.auth.auth_providers[0]
result = await hass.auth.login_flow.async_init((provider.type, provider.id))
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"username": "incorrect-user", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_auth"
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"username": "test-user", "password": "incorrect-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_auth"
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"username": "test-user", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "mfa"
assert result["data_schema"].schema.get("code") is str
@@ -120,7 +120,7 @@ async def test_login_flow_validates_mfa(hass: HomeAssistant) -> None:
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"code": "invalid-code"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "mfa"
assert result["errors"]["base"] == "invalid_code"
@@ -128,7 +128,7 @@ async def test_login_flow_validates_mfa(hass: HomeAssistant) -> None:
result = await hass.auth.login_flow.async_configure(
result["flow_id"], {"code": MOCK_CODE}
)
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"].id == "mock-id"
+4 -4
View File
@@ -139,18 +139,18 @@ async def test_login_flow_validates(
"""Test login flow."""
flow = await provider.async_login_flow({})
result = await flow.async_step_init()
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
result = await flow.async_step_init(
{"username": "bad-user", "password": "bad-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_auth"
result = await flow.async_step_init(
{"username": "good-user", "password": "good-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"]["username"] == "good-user"
@@ -160,5 +160,5 @@ async def test_strip_username(provider: command_line.CommandLineAuthProvider) ->
result = await flow.async_step_init(
{"username": "\t\ngood-user ", "password": "good-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"]["username"] == "good-user"
+8 -8
View File
@@ -161,24 +161,24 @@ async def test_login_flow_validates(data: hass_auth.Data, hass: HomeAssistant) -
)
flow = await provider.async_login_flow({})
result = await flow.async_step_init()
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
result = await flow.async_step_init(
{"username": "incorrect-user", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_auth"
result = await flow.async_step_init(
{"username": "TEST-user ", "password": "incorrect-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_auth"
result = await flow.async_step_init(
{"username": "test-USER", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"]["username"] == "test-USER"
@@ -260,24 +260,24 @@ async def test_legacy_login_flow_validates(
)
flow = await provider.async_login_flow({})
result = await flow.async_step_init()
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
result = await flow.async_step_init(
{"username": "incorrect-user", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_auth"
result = await flow.async_step_init(
{"username": "test-user", "password": "incorrect-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"]["base"] == "invalid_auth"
result = await flow.async_step_init(
{"username": "test-user", "password": "test-pass"}
)
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"]["username"] == "test-user"
+15 -15
View File
@@ -172,12 +172,12 @@ async def test_create_new_user(hass: HomeAssistant) -> None:
)
step = await manager.login_flow.async_init(("insecure_example", None))
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
step = await manager.login_flow.async_configure(
step["flow_id"], {"username": "test-user", "password": "test-pass"}
)
assert step["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert step["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
credential = step["result"]
assert credential is not None
@@ -241,12 +241,12 @@ async def test_login_as_existing_user(mock_hass) -> None:
)
step = await manager.login_flow.async_init(("insecure_example", None))
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
step = await manager.login_flow.async_configure(
step["flow_id"], {"username": "test-user", "password": "test-pass"}
)
assert step["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert step["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
credential = step["result"]
user = await manager.async_get_user_by_credentials(credential)
@@ -840,14 +840,14 @@ async def test_login_with_auth_module(mock_hass) -> None:
)
step = await manager.login_flow.async_init(("insecure_example", None))
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
step = await manager.login_flow.async_configure(
step["flow_id"], {"username": "test-user", "password": "test-pass"}
)
# After auth_provider validated, request auth module input form
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
assert step["step_id"] == "mfa"
step = await manager.login_flow.async_configure(
@@ -855,7 +855,7 @@ async def test_login_with_auth_module(mock_hass) -> None:
)
# Invalid code error
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
assert step["step_id"] == "mfa"
assert step["errors"] == {"base": "invalid_code"}
@@ -864,7 +864,7 @@ async def test_login_with_auth_module(mock_hass) -> None:
)
# Finally passed, get credential
assert step["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert step["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert step["result"]
assert step["result"].id == "mock-id"
@@ -915,21 +915,21 @@ async def test_login_with_multi_auth_module(mock_hass) -> None:
)
step = await manager.login_flow.async_init(("insecure_example", None))
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
step = await manager.login_flow.async_configure(
step["flow_id"], {"username": "test-user", "password": "test-pass"}
)
# After auth_provider validated, request select auth module
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
assert step["step_id"] == "select_mfa_module"
step = await manager.login_flow.async_configure(
step["flow_id"], {"multi_factor_auth_module": "module2"}
)
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
assert step["step_id"] == "mfa"
step = await manager.login_flow.async_configure(
@@ -937,7 +937,7 @@ async def test_login_with_multi_auth_module(mock_hass) -> None:
)
# Finally passed, get credential
assert step["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert step["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert step["result"]
assert step["result"].id == "mock-id"
@@ -983,13 +983,13 @@ async def test_auth_module_expired_session(mock_hass) -> None:
)
step = await manager.login_flow.async_init(("insecure_example", None))
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
step = await manager.login_flow.async_configure(
step["flow_id"], {"username": "test-user", "password": "test-pass"}
)
assert step["type"] == data_entry_flow.FlowResultType.FORM
assert step["type"] is data_entry_flow.FlowResultType.FORM
assert step["step_id"] == "mfa"
with freeze_time(dt_util.utcnow() + MFA_SESSION_EXPIRATION):
@@ -997,7 +997,7 @@ async def test_auth_module_expired_session(mock_hass) -> None:
step["flow_id"], {"pin": "test-pin"}
)
# login flow abort due session timeout
assert step["type"] == data_entry_flow.FlowResultType.ABORT
assert step["type"] is data_entry_flow.FlowResultType.ABORT
assert step["reason"] == "login_expired"
+3 -3
View File
@@ -231,7 +231,7 @@ async def test_reauth_flow_scenario(
data=mock_config_entry.data,
)
assert flow["type"] == FlowResultType.FORM
assert flow["type"] is FlowResultType.FORM
assert flow["step_id"] == REAUTH_STEP
fw_major = int(ap_status_fixture.host.fwversion.lstrip("v").split(".", 1)[0])
@@ -305,7 +305,7 @@ async def test_reauth_flow_scenarios(
data=mock_config_entry.data,
)
assert flow["type"] == FlowResultType.FORM
assert flow["type"] is FlowResultType.FORM
assert flow["step_id"] == REAUTH_STEP
with patch(
@@ -337,7 +337,7 @@ async def test_reauth_flow_scenarios(
user_input={CONF_PASSWORD: NEW_PASSWORD},
)
assert result["type"] == FlowResultType.ABORT
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
updated_entry = hass.config_entries.async_get_entry(mock_config_entry.entry_id)
+1 -1
View File
@@ -284,7 +284,7 @@ async def test_setup_entry_failure(
result = await hass.config_entries.async_setup(mock_config_entry.entry_id)
assert result is False
assert mock_config_entry.state == state
assert mock_config_entry.state is state
async def test_fetch_airos_data_auth_error(mock_airos_client: MagicMock) -> None:
+1 -1
View File
@@ -138,4 +138,4 @@ async def test_migrate_future_version_returns_false(
await setup_integration(hass, config_entry)
assert config_entry.state == ConfigEntryState.MIGRATION_ERROR
assert config_entry.state is ConfigEntryState.MIGRATION_ERROR
@@ -164,7 +164,7 @@ async def test_error_handling(
hass, "hello", None, Context(), agent_id="conversation.claude_conversation"
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == "unknown", result
@@ -189,7 +189,7 @@ async def test_template_error(
hass, "hello", None, Context(), agent_id="conversation.claude_conversation"
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == "unknown", result
@@ -230,7 +230,7 @@ async def test_template_variables(
hass, "hello", None, context, agent_id="conversation.claude_conversation"
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert (
result.response.speech["plain"]["speech"]
== "Okay, let me take care of that for you."
@@ -382,7 +382,7 @@ async def test_function_call(
system_text = " ".join(block["text"] for block in system if "text" in block)
assert "You are a voice assistant for Home Assistant." in system_text
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert (
result.response.speech["plain"]["speech"]
== "I have successfully called the function"
@@ -457,7 +457,7 @@ async def test_function_exception(
agent_id=agent_id,
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert (
result.response.speech["plain"]["speech"]
== "There was an error calling the function"
@@ -638,7 +638,7 @@ async def test_refusal(
agent_id="conversation.claude_conversation",
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == "unknown"
assert (
result.response.speech["plain"]["speech"]
@@ -670,7 +670,7 @@ async def test_stream_wrong_type(
agent_id="conversation.claude_conversation",
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == "unknown"
assert result.response.speech["plain"]["speech"] == "Expected a stream of messages"
@@ -700,7 +700,7 @@ async def test_double_system_messages(
agent_id="conversation.claude_conversation",
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == "unknown"
assert (
result.response.speech["plain"]["speech"]
@@ -42,7 +42,7 @@ async def test_auth_error_handling(
hass, "hello", None, Context(), agent_id="conversation.claude_conversation"
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == "unknown", result
await hass.async_block_till_done()
@@ -86,7 +86,7 @@ async def test_connection_error_handling(
hass, "hello", None, Context(), agent_id="conversation.claude_conversation"
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == "unknown", result
# Check new state
+2 -2
View File
@@ -190,7 +190,7 @@ async def test_device_trigger_reauth_flow(
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
mock_flow_init.assert_called_once()
assert config_entry.state == ConfigEntryState.SETUP_ERROR
assert config_entry.state is ConfigEntryState.SETUP_ERROR
async def test_shutdown(config_entry_data: MappingProxyType[str, Any]) -> None:
@@ -235,4 +235,4 @@ async def test_get_axis_api_errors(
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state == state
assert config_entry.state is state
@@ -26,7 +26,7 @@ async def test_config_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) ->
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=None
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"] == {}
result2 = await hass.config_entries.flow.async_configure(
@@ -34,7 +34,7 @@ async def test_config_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) ->
BASE_CONFIG.copy(),
)
assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result2["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert (
result2["title"]
== "cluster.region.kusto.windows.net / test-database-name (test-table-name)"
@@ -61,7 +61,7 @@ async def test_config_flow_errors(
context={"source": config_entries.SOURCE_USER},
data=None,
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert result["errors"] == {}
# Test error handling with error
@@ -71,7 +71,7 @@ async def test_config_flow_errors(
result["flow_id"],
BASE_CONFIG.copy(),
)
assert result2["type"] == data_entry_flow.FlowResultType.FORM
assert result2["type"] is data_entry_flow.FlowResultType.FORM
assert result2["errors"] == {"base": expected}
schema = result2["data_schema"]
@@ -99,7 +99,7 @@ async def test_config_flow_errors(
await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.FlowResultType.FORM
assert result2["type"] is data_entry_flow.FlowResultType.FORM
# Retest error handling if error is corrected and connection is successful
@@ -112,4 +112,4 @@ async def test_config_flow_errors(
await hass.async_block_till_done()
assert result3["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result3["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
@@ -70,7 +70,7 @@ async def test_config_flow_step_user(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()
assert result1["type"] == FlowResultType.CREATE_ENTRY
assert result1["type"] is FlowResultType.CREATE_ENTRY
assert result1["result"].title == "Office occupied"
assert result1["next_flow"][0] == FlowType.CONFIG_SUBENTRIES_FLOW
@@ -260,7 +260,7 @@ async def test_single_state_observation(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["type"] is FlowResultType.CREATE_ENTRY
entry_id = result["result"].entry_id
sub_flow_id = result["next_flow"][1]
@@ -287,7 +287,7 @@ async def test_single_state_observation(hass: HomeAssistant) -> None:
},
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["type"] is FlowResultType.CREATE_ENTRY
await hass.async_block_till_done()
config_entry = hass.config_entries.async_get_entry(entry_id)
@@ -337,7 +337,7 @@ async def test_single_numeric_state_observation(hass: HomeAssistant) -> None:
CONF_PRIOR: 20,
},
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["type"] is FlowResultType.CREATE_ENTRY
config_entry = result["result"]
sub_flow_id = result["next_flow"][1]
await hass.async_block_till_done()
@@ -408,7 +408,7 @@ async def test_multi_numeric_state_observation(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["type"] is FlowResultType.CREATE_ENTRY
config_entry = result["result"]
sub_flow_id = result["next_flow"][1]
@@ -546,7 +546,7 @@ async def test_single_template_observation(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["type"] is FlowResultType.CREATE_ENTRY
config_entry = result["result"]
sub_flow_id = result["next_flow"][1]
@@ -1086,7 +1086,7 @@ async def test_invalid_configs(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
assert result.get("errors") is None
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["type"] is FlowResultType.CREATE_ENTRY
config_entry = result["result"]
sub_flow_id = result["next_flow"][1]
@@ -182,8 +182,13 @@ async def test_diagnostics(
"scanners": [
{
"adapter": "hci0",
"connect_completed_total": 0,
"connect_failed_total": 0,
"connect_failures": {},
"connect_in_progress": {},
"connectable": True,
"discovered_devices_and_advertisement_data": [],
"last_connect_completed_time": 0.0,
"last_detection": ANY,
"monotonic_time": ANY,
"name": "hci0 (00:00:00:00:00:01)",
@@ -202,6 +207,11 @@ async def test_diagnostics(
},
{
"adapter": "hci1",
"connect_completed_total": 0,
"connect_failed_total": 0,
"connect_failures": {},
"connect_in_progress": {},
"last_connect_completed_time": 0.0,
"discovered_devices_and_advertisement_data": [
{
"address": "44:44:33:11:23:45",
@@ -397,7 +407,12 @@ async def test_diagnostics_macos(
"scanners": [
{
"adapter": "Core Bluetooth",
"connect_completed_total": 0,
"connect_failed_total": 0,
"connect_failures": {},
"connect_in_progress": {},
"connectable": True,
"last_connect_completed_time": 0.0,
"discovered_devices_and_advertisement_data": [
{
"address": "44:44:33:11:23:45",
@@ -602,8 +617,13 @@ async def test_diagnostics_remote_adapter(
"scanners": [
{
"adapter": "hci0",
"connect_completed_total": 0,
"connect_failed_total": 0,
"connect_failures": {},
"connect_in_progress": {},
"connectable": True,
"discovered_devices_and_advertisement_data": [],
"last_connect_completed_time": 0.0,
"last_detection": ANY,
"monotonic_time": ANY,
"name": "hci0 (00:00:00:00:00:01)",
@@ -621,9 +641,14 @@ async def test_diagnostics_remote_adapter(
},
},
{
"connect_completed_total": 0,
"connect_failed_total": 0,
"connect_failures": {},
"connect_in_progress": {},
"connectable": True,
"current_mode": None,
"requested_mode": None,
"last_connect_completed_time": 0.0,
"discovered_device_timestamps": {"44:44:33:11:23:45": ANY},
"discovered_devices_and_advertisement_data": [
{
+5 -3
View File
@@ -138,8 +138,10 @@ async def test_setup_and_stop_passive(
await hass.async_block_till_done()
assert init_kwargs == {
"adapter": "hci0",
"bluez": scanner.PASSIVE_SCANNER_ARGS, # pylint: disable=c-extension-no-member
"bluez": {
**scanner.PASSIVE_SCANNER_ARGS, # pylint: disable=c-extension-no-member
"adapter": "hci0",
},
"scanning_mode": "passive",
}
@@ -188,7 +190,7 @@ async def test_setup_and_stop_old_bluez(
await hass.async_block_till_done()
assert init_kwargs == {
"adapter": "hci0",
"bluez": {"adapter": "hci0"},
"scanning_mode": "active",
}
+1 -1
View File
@@ -72,7 +72,7 @@ async def test_init_failure(
"""Test an initialization error on integration load."""
mock_bring_client.login.side_effect = exception
await setup_integration(hass, bring_config_entry)
assert bring_config_entry.state == status
assert bring_config_entry.state is status
assert (
any(
+1 -1
View File
@@ -67,7 +67,7 @@ async def test_client_failure(
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state == expected_state
assert config_entry.state is expected_state
flows = hass.config_entries.flow.async_progress()
assert [flow.get("step_id") for flow in flows] == expected_flows
+1 -1
View File
@@ -56,7 +56,7 @@ async def test_async_unload_entry(
result = await hass.config_entries.async_unload(config_entry.entry_id)
assert result is True
assert config_entry.state == ConfigEntryState.NOT_LOADED
assert config_entry.state is ConfigEntryState.NOT_LOADED
async def test_device_info(
+10 -10
View File
@@ -201,7 +201,7 @@ async def test_set_temperature(
{"temperature": {"value": 20}},
assistant=conversation.DOMAIN,
)
assert err.value.result.no_match_reason == intent.MatchFailedReason.MULTIPLE_TARGETS
assert err.value.result.no_match_reason is intent.MatchFailedReason.MULTIPLE_TARGETS
# Select by area explicitly (climate_2)
response = await intent.async_handle(
@@ -211,7 +211,7 @@ async def test_set_temperature(
{"area": {"value": bedroom_area.name}, "temperature": {"value": 20.1}},
assistant=conversation.DOMAIN,
)
assert response.response_type == intent.IntentResponseType.ACTION_DONE
assert response.response_type is intent.IntentResponseType.ACTION_DONE
assert len(response.matched_states) == 1
assert response.matched_states[0].entity_id == climate_2.entity_id
state = hass.states.get(climate_2.entity_id)
@@ -228,7 +228,7 @@ async def test_set_temperature(
},
assistant=conversation.DOMAIN,
)
assert response.response_type == intent.IntentResponseType.ACTION_DONE
assert response.response_type is intent.IntentResponseType.ACTION_DONE
assert response.matched_states
assert response.matched_states[0].entity_id == climate_2.entity_id
state = hass.states.get(climate_2.entity_id)
@@ -242,7 +242,7 @@ async def test_set_temperature(
{"floor": {"value": second_floor.name}, "temperature": {"value": 20.3}},
assistant=conversation.DOMAIN,
)
assert response.response_type == intent.IntentResponseType.ACTION_DONE
assert response.response_type is intent.IntentResponseType.ACTION_DONE
assert response.matched_states
assert response.matched_states[0].entity_id == climate_2.entity_id
state = hass.states.get(climate_2.entity_id)
@@ -259,7 +259,7 @@ async def test_set_temperature(
},
assistant=conversation.DOMAIN,
)
assert response.response_type == intent.IntentResponseType.ACTION_DONE
assert response.response_type is intent.IntentResponseType.ACTION_DONE
assert response.matched_states
assert response.matched_states[0].entity_id == climate_2.entity_id
state = hass.states.get(climate_2.entity_id)
@@ -273,7 +273,7 @@ async def test_set_temperature(
{"name": {"value": "Climate 2"}, "temperature": {"value": 20.5}},
assistant=conversation.DOMAIN,
)
assert response.response_type == intent.IntentResponseType.ACTION_DONE
assert response.response_type is intent.IntentResponseType.ACTION_DONE
assert len(response.matched_states) == 1
assert response.matched_states[0].entity_id == climate_2.entity_id
state = hass.states.get(climate_2.entity_id)
@@ -291,7 +291,7 @@ async def test_set_temperature(
# Exception should contain details of what we tried to match
assert isinstance(error.value, intent.MatchFailedError)
assert error.value.result.no_match_reason == intent.MatchFailedReason.AREA
assert error.value.result.no_match_reason is intent.MatchFailedReason.AREA
constraints = error.value.constraints
assert constraints.name is None
assert constraints.area_name == office_area.name
@@ -310,7 +310,7 @@ async def test_set_temperature(
},
assistant=conversation.DOMAIN,
)
assert err.value.result.no_match_reason == intent.MatchFailedReason.MULTIPLE_TARGETS
assert err.value.result.no_match_reason is intent.MatchFailedReason.MULTIPLE_TARGETS
async def test_set_temperature_no_entities(
@@ -330,7 +330,7 @@ async def test_set_temperature_no_entities(
{"temperature": {"value": 20}},
assistant=conversation.DOMAIN,
)
assert err.value.result.no_match_reason == intent.MatchFailedReason.DOMAIN
assert err.value.result.no_match_reason is intent.MatchFailedReason.DOMAIN
async def test_set_temperature_not_supported(hass: HomeAssistant) -> None:
@@ -357,4 +357,4 @@ async def test_set_temperature_not_supported(hass: HomeAssistant) -> None:
# Exception should contain details of what we tried to match
assert isinstance(error.value, intent.MatchFailedError)
assert error.value.result.no_match_reason == intent.MatchFailedReason.FEATURE
assert error.value.result.no_match_reason is intent.MatchFailedReason.FEATURE
+1 -1
View File
@@ -114,4 +114,4 @@ async def test_migrate_future_version_returns_false(
await setup_integration(hass, config_entry)
assert config_entry.state == ConfigEntryState.MIGRATION_ERROR
assert config_entry.state is ConfigEntryState.MIGRATION_ERROR
@@ -785,7 +785,7 @@ async def test_get_progress_index(
)
for form in (form_hassio, form_user, form_reconfigure):
assert form["type"] == data_entry_flow.FlowResultType.FORM
assert form["type"] is data_entry_flow.FlowResultType.FORM
assert form["step_id"] == "account"
await ws_client.send_json({"id": 5, "type": "config_entries/flow/progress"})
@@ -961,9 +961,9 @@ async def test_get_progress_subscribe(
"test", context=context
)
assert forms["bluetooth"]["type"] == data_entry_flow.FlowResultType.ABORT
assert forms["bluetooth"]["type"] is data_entry_flow.FlowResultType.ABORT
for key in ("hassio", "user", "reauth", "reconfigure"):
assert forms[key]["type"] == data_entry_flow.FlowResultType.FORM
assert forms[key]["type"] is data_entry_flow.FlowResultType.FORM
assert forms[key]["step_id"] == "account"
for key in ("hassio", "user", "reauth", "reconfigure"):
@@ -1100,9 +1100,9 @@ async def test_get_progress_subscribe_in_progress(
"test", context=context
)
assert forms["bluetooth"]["type"] == data_entry_flow.FlowResultType.ABORT
assert forms["bluetooth"]["type"] is data_entry_flow.FlowResultType.ABORT
for key in ("hassio", "user", "reauth", "reconfigure"):
assert forms[key]["type"] == data_entry_flow.FlowResultType.FORM
assert forms[key]["type"] is data_entry_flow.FlowResultType.FORM
assert forms[key]["step_id"] == "account"
await ws_client.send_json({"id": 1, "type": "config_entries/flow/subscribe"})
@@ -1235,16 +1235,16 @@ async def test_get_progress_subscribe_in_progress_bad_flow(
"test", context=context
)
assert forms["bluetooth"]["type"] == data_entry_flow.FlowResultType.ABORT
assert forms["bluetooth"]["type"] is data_entry_flow.FlowResultType.ABORT
for key in ("hassio", "user", "reauth", "reconfigure"):
assert forms[key]["type"] == data_entry_flow.FlowResultType.FORM
assert forms[key]["type"] is data_entry_flow.FlowResultType.FORM
assert forms[key]["step_id"] == "account"
with mock_config_flow("test2", BadFlow):
forms["bad"] = await hass.config_entries.flow.async_init(
"test2", context={"source": core_ce.SOURCE_REAUTH, "entry_id": "1234"}
)
assert forms["bad"]["type"] == data_entry_flow.FlowResultType.FORM
assert forms["bad"]["type"] is data_entry_flow.FlowResultType.FORM
assert forms["bad"]["step_id"] == "account"
await ws_client.send_json({"id": 1, "type": "config_entries/flow/subscribe"})
@@ -116,7 +116,7 @@ async def test_hidden_entities_skipped(
)
assert len(calls) == 0
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -135,13 +135,13 @@ async def test_exposed_domains(hass: HomeAssistant) -> None:
result = await conversation.async_converse(
hass, "unlock front door", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
result = await conversation.async_converse(
hass, "run my script", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -191,7 +191,7 @@ async def test_exposed_areas(
)
# All is well for the exposed kitchen light
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.intent is not None
assert result.response.intent.slots["area"]["value"] == area_kitchen.id
assert result.response.intent.slots["area"]["text"] == area_kitchen.normalized_name
@@ -202,14 +202,14 @@ async def test_exposed_areas(
)
# This should be an error because the lights in that area are not exposed
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
# But we can still ask questions about the bedroom, even with no exposed entities
result = await conversation.async_converse(
hass, "how many lights are on in the bedroom?", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.QUERY_ANSWER
assert result.response.response_type is intent.IntentResponseType.QUERY_ANSWER
@pytest.mark.usefixtures("init_components")
@@ -248,7 +248,7 @@ async def test_punctuation(hass: HomeAssistant) -> None:
assert len(calls) == 1
assert calls[0].data["entity_id"][0] == "light.test_light"
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.intent is not None
assert result.response.intent.slots["name"]["value"] == "test light"
assert result.response.intent.slots["name"]["text"] == "test light"
@@ -326,7 +326,7 @@ async def test_unexposed_entities_skipped(
)
assert len(calls) == 1
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.intent is not None
assert result.response.intent.slots["area"]["value"] == area_kitchen.id
assert result.response.intent.slots["area"]["text"] == area_kitchen.normalized_name
@@ -338,7 +338,7 @@ async def test_unexposed_entities_skipped(
hass, "how many lights are on in the kitchen", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.QUERY_ANSWER
assert result.response.response_type is intent.IntentResponseType.QUERY_ANSWER
assert len(result.response.matched_states) == 1
assert result.response.matched_states[0].entity_id == exposed_light.entity_id
@@ -403,7 +403,7 @@ async def test_duplicated_names_resolved_with_device_area(
assert len(calls) == 1
assert calls[0].data["entity_id"][0] == bedroom_light.entity_id
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.intent is not None
assert result.response.intent.slots.get("name", {}).get("value") == name
assert result.response.intent.slots.get("name", {}).get("text") == name
@@ -421,7 +421,7 @@ async def test_trigger_sentences(hass: HomeAssistant) -> None:
unregister = manager.register_trigger(trigger_sentences, callback)
result = await conversation.async_converse(hass, "Not the trigger", None, Context())
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
# Using different case and including punctuation
test_sentences = ["it's party time!", "IT IS TIME TO PARTY."]
@@ -430,7 +430,7 @@ async def test_trigger_sentences(hass: HomeAssistant) -> None:
result = await conversation.async_converse(hass, sentence, None, Context())
assert callback.call_count == 1
assert callback.call_args[0][0].text == sentence
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE, (
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE, (
sentence
)
assert result.response.speech == {
@@ -443,7 +443,7 @@ async def test_trigger_sentences(hass: HomeAssistant) -> None:
callback.reset_mock()
for sentence in test_sentences:
result = await conversation.async_converse(hass, sentence, None, Context())
assert result.response.response_type == intent.IntentResponseType.ERROR, (
assert result.response.response_type is intent.IntentResponseType.ERROR, (
sentence
)
@@ -479,7 +479,7 @@ async def test_trigger_sentence_response_translation(
result = await conversation.async_converse(
hass, "test sentence", None, Context()
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.speech == {
"plain": {"speech": expected, "extra_data": None}
}
@@ -493,7 +493,7 @@ async def test_shopping_list_add_item(hass: HomeAssistant) -> None:
result = await conversation.async_converse(
hass, "add apples to my shopping list", None, Context()
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.speech == {
"plain": {"speech": "Added apples", "extra_data": None}
}
@@ -506,7 +506,7 @@ async def test_nevermind_intent(hass: HomeAssistant) -> None:
assert result.response.intent is not None
assert result.response.intent.intent_type == intent.INTENT_NEVERMIND
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert not result.response.speech
@@ -517,7 +517,7 @@ async def test_respond_intent(hass: HomeAssistant) -> None:
assert result.response.intent is not None
assert result.response.intent.intent_type == intent.INTENT_RESPOND
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.speech["plain"]["speech"] == "Hello from Home Assistant."
@@ -584,7 +584,7 @@ async def test_satellite_area_context(
satellite_id=kitchen_satellite.entity_id,
)
await hass.async_block_till_done()
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.intent is not None
assert result.response.intent.slots["area"]["value"] == area_kitchen.id
assert result.response.intent.slots["area"]["text"] == area_kitchen.normalized_name
@@ -608,7 +608,7 @@ async def test_satellite_area_context(
satellite_id=kitchen_satellite.entity_id,
)
await hass.async_block_till_done()
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.intent is not None
assert result.response.intent.slots["area"]["value"] == area_bedroom.id
assert result.response.intent.slots["area"]["text"] == area_bedroom.normalized_name
@@ -632,7 +632,7 @@ async def test_satellite_area_context(
device_id=bedroom_satellite.id,
)
await hass.async_block_till_done()
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.intent is not None
assert result.response.intent.slots["area"]["value"] == area_bedroom.id
assert result.response.intent.slots["area"]["text"] == area_bedroom.normalized_name
@@ -652,7 +652,7 @@ async def test_satellite_area_context(
hass, f"turn {command} all lights", None, Context(), None
)
await hass.async_block_till_done()
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
# All lights should have been targeted
assert {s.entity_id for s in result.response.matched_states} == {
@@ -667,7 +667,7 @@ async def test_error_no_device(hass: HomeAssistant) -> None:
hass, "turn on missing entity", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -685,7 +685,7 @@ async def test_error_no_device_exposed(hass: HomeAssistant) -> None:
hass, "turn on kitchen light", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -700,7 +700,7 @@ async def test_error_no_area(hass: HomeAssistant) -> None:
hass, "turn on the lights in missing area", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -715,7 +715,7 @@ async def test_error_no_floor(hass: HomeAssistant) -> None:
hass, "turn on all the lights on missing floor", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -734,7 +734,7 @@ async def test_error_no_device_in_area(
hass, "turn on missing entity in the kitchen", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -754,7 +754,7 @@ async def test_error_no_device_on_floor(
hass, "turn on missing entity on ground floor", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -809,7 +809,7 @@ async def test_error_no_device_on_floor_exposed(
hass, "turn on test light on the ground floor", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -848,7 +848,7 @@ async def test_error_no_device_in_area_exposed(
hass, "turn on test light in the kitchen", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -877,7 +877,7 @@ async def test_error_no_domain(hass: HomeAssistant) -> None:
hass, "turn on the fans", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -912,7 +912,7 @@ async def test_error_no_domain_exposed(hass: HomeAssistant) -> None:
hass, "turn on the fans", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -931,7 +931,7 @@ async def test_error_no_domain_in_area(
hass, "turn on the lights in the kitchen", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -967,7 +967,7 @@ async def test_error_no_domain_in_area_exposed(
hass, "turn on the lights in the kitchen", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -991,7 +991,7 @@ async def test_error_no_domain_on_floor(
hass, "turn on all lights on the ground floor", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -1009,7 +1009,7 @@ async def test_error_no_domain_on_floor(
hass, "turn on all lights upstairs", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -1048,7 +1048,7 @@ async def test_error_no_domain_on_floor_exposed(
hass, "turn on all lights on the ground floor", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -1086,7 +1086,7 @@ async def test_error_no_device_class(hass: HomeAssistant) -> None:
hass, "open the windows", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -1135,7 +1135,7 @@ async def test_error_no_device_class_exposed(hass: HomeAssistant) -> None:
hass, "open all the windows", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -1156,7 +1156,7 @@ async def test_error_no_device_class_in_area(
hass, "open bedroom windows", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -1191,7 +1191,7 @@ async def test_error_no_device_class_in_area_exposed(
hass, "open bedroom windows", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -1246,7 +1246,7 @@ async def test_error_no_device_class_on_floor_exposed(
hass, "open ground floor windows", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -1268,7 +1268,7 @@ async def test_error_no_intent(hass: HomeAssistant) -> None:
hass, "do something", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code == intent.IntentResponseErrorCode.NO_INTENT_MATCH
)
@@ -1305,7 +1305,7 @@ async def test_error_duplicate_names(
result = await conversation.async_converse(
hass, f"turn on {name}", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -1319,7 +1319,7 @@ async def test_error_duplicate_names(
result = await conversation.async_converse(
hass, f"is {name} on?", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -1362,7 +1362,7 @@ async def test_duplicate_names_but_one_is_exposed(
result = await conversation.async_converse(
hass, f"turn on {name}", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.matched_states[0].entity_id == kitchen_light_1.entity_id
@@ -1399,7 +1399,7 @@ async def test_error_duplicate_names_same_area(
result = await conversation.async_converse(
hass, f"turn on {name} in {area_kitchen.name}", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -1414,7 +1414,7 @@ async def test_error_duplicate_names_same_area(
result = await conversation.async_converse(
hass, f"is {name} on in the {area_kitchen.name}?", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -1464,7 +1464,7 @@ async def test_duplicate_names_same_area_but_one_is_exposed(
result = await conversation.async_converse(
hass, f"turn on {name} in {area_kitchen.name}", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.matched_states[0].entity_id == kitchen_light_1.entity_id
@@ -1530,20 +1530,20 @@ async def test_duplicate_names_different_areas(
result = await conversation.async_converse(
hass, f"turn on {name}", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
# Target kitchen light by using kitchen device
result = await conversation.async_converse(
hass, f"turn on {name}", None, Context(), None, device_id=device_kitchen.id
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.matched_states[0].entity_id == kitchen_light.entity_id
# Target bedroom light by using bedroom device
result = await conversation.async_converse(
hass, f"turn on {name}", None, Context(), None, device_id=device_bedroom.id
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.matched_states[0].entity_id == bedroom_light.entity_id
@@ -1562,7 +1562,7 @@ async def test_error_wrong_state(hass: HomeAssistant) -> None:
hass, "pause test player", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert result.response.speech["plain"]["speech"] == "Sorry, no device is playing"
@@ -1583,7 +1583,7 @@ async def test_error_feature_not_supported(hass: HomeAssistant) -> None:
hass, "set test player volume to 100%", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -1615,7 +1615,7 @@ async def test_error_no_timer_support(
hass, "set a 5 minute timer", None, Context(), None, device_id=device_id
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.FAILED_TO_HANDLE
assert (
result.response.speech["plain"]["speech"]
@@ -1639,7 +1639,7 @@ async def test_error_timer_not_found(hass: HomeAssistant) -> None:
hass, "pause timer", None, Context(), None, device_id=device_id
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.FAILED_TO_HANDLE
assert (
result.response.speech["plain"]["speech"] == "Sorry, I couldn't find that timer"
@@ -1677,18 +1677,18 @@ async def test_error_multiple_timers_matched(
result = await conversation.async_converse(
hass, "set a timer for 5 minutes", None, Context(), None, device_id=device_id
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
result = await conversation.async_converse(
hass, "set a timer for 5 minutes", None, Context(), None, device_id=device_id
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
# Cannot target multiple timers
result = await conversation.async_converse(
hass, "cancel timer", None, Context(), None, device_id=device_id
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.FAILED_TO_HANDLE
assert (
result.response.speech["plain"]["speech"]
@@ -1714,7 +1714,7 @@ async def test_no_states_matched_default_error(
hass, "turn on lights in the kitchen", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert (
result.response.error_code
== intent.IntentResponseErrorCode.NO_VALID_TARGETS
@@ -1802,7 +1802,7 @@ async def test_all_domains_loaded(hass: HomeAssistant) -> None:
)
# Invalid target vs. no intent recognized
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
@@ -1856,7 +1856,7 @@ async def test_same_named_entities_in_different_areas(
await hass.async_block_till_done()
assert len(calls) == 1
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.intent is not None
assert (
result.response.intent.slots.get("name", {}).get("value") == kitchen_light.name
@@ -1876,7 +1876,7 @@ async def test_same_named_entities_in_different_areas(
await hass.async_block_till_done()
assert len(calls) == 1
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.intent is not None
assert (
result.response.intent.slots.get("name", {}).get("value") == bedroom_light.name
@@ -1892,19 +1892,19 @@ async def test_same_named_entities_in_different_areas(
result = await conversation.async_converse(
hass, "turn on overhead light", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
# Querying a duplicate name should also fail
result = await conversation.async_converse(
hass, "is the overhead light on?", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
# But we can still ask questions that don't rely on the name
result = await conversation.async_converse(
hass, "how many lights are on?", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.QUERY_ANSWER
assert result.response.response_type is intent.IntentResponseType.QUERY_ANSWER
@pytest.mark.usefixtures("init_components")
@@ -1955,7 +1955,7 @@ async def test_same_aliased_entities_in_different_areas(
await hass.async_block_till_done()
assert len(calls) == 1
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.intent is not None
assert result.response.intent.slots.get("name", {}).get("value") == "overhead light"
assert result.response.intent.slots.get("name", {}).get("text") == "overhead light"
@@ -1971,7 +1971,7 @@ async def test_same_aliased_entities_in_different_areas(
await hass.async_block_till_done()
assert len(calls) == 1
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert result.response.intent is not None
assert result.response.intent.slots.get("name", {}).get("value") == "overhead light"
assert result.response.intent.slots.get("name", {}).get("text") == "overhead light"
@@ -1983,19 +1983,19 @@ async def test_same_aliased_entities_in_different_areas(
result = await conversation.async_converse(
hass, "turn on overhead light", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
# Querying a duplicate alias should also fail
result = await conversation.async_converse(
hass, "is the overhead light on?", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
# But we can still ask questions that don't rely on the alias
result = await conversation.async_converse(
hass, "how many lights are on?", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.QUERY_ANSWER
assert result.response.response_type is intent.IntentResponseType.QUERY_ANSWER
@pytest.mark.usefixtures("init_components")
@@ -2027,7 +2027,7 @@ async def test_device_id_in_handler(hass: HomeAssistant) -> None:
Context(),
device_id=device_id,
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert handler.device_id == device_id
@@ -2070,7 +2070,7 @@ async def test_name_wildcard_lower_priority(hass: HomeAssistant) -> None:
result = await conversation.async_converse(
hass, "I'd like to order a stout please", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert beer_handler.triggered
assert not food_handler.triggered
@@ -2079,7 +2079,7 @@ async def test_name_wildcard_lower_priority(hass: HomeAssistant) -> None:
result = await conversation.async_converse(
hass, "I'd like to order a cookie please", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
assert not beer_handler.triggered
assert food_handler.triggered
@@ -2871,7 +2871,7 @@ async def test_query_same_name_different_areas(
result = await conversation.async_converse(
hass, "is the overhead light on?", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
# Succeeds using area from device (kitchen)
result = await conversation.async_converse(
@@ -2882,7 +2882,7 @@ async def test_query_same_name_different_areas(
None,
device_id=kitchen_device.id,
)
assert result.response.response_type == intent.IntentResponseType.QUERY_ANSWER
assert result.response.response_type is intent.IntentResponseType.QUERY_ANSWER
assert len(result.response.matched_states) == 1
assert result.response.matched_states[0].entity_id == kitchen_light.entity_id
@@ -3059,7 +3059,7 @@ async def test_entities_names_are_not_templates(hass: HomeAssistant) -> None:
)
assert result is not None
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
# Not exposed
expose_entity(hass, "light.test_light", False)
@@ -3072,7 +3072,7 @@ async def test_entities_names_are_not_templates(hass: HomeAssistant) -> None:
)
assert result is not None
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
@pytest.mark.parametrize(
@@ -3339,7 +3339,7 @@ async def test_state_names_are_not_translated(
result = await conversation.async_converse(
hass, "what is the weather like?", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.QUERY_ANSWER
assert result.response.response_type is intent.IntentResponseType.QUERY_ANSWER
mock_async_render.assert_called_once()
assert (
@@ -3396,7 +3396,7 @@ async def test_intent_tool_call_in_chat_log(hass: HomeAssistant) -> None:
hass, "turn on test light", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
with (
chat_session.async_get_chat_session(hass, result.conversation_id) as session,
@@ -3448,7 +3448,7 @@ async def test_trigger_tool_call_in_chat_log(hass: HomeAssistant) -> None:
hass, trigger_sentence, None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
with (
chat_session.async_get_chat_session(hass, result.conversation_id) as session,
@@ -3486,7 +3486,7 @@ async def test_no_tool_call_on_no_intent_match(hass: HomeAssistant) -> None:
hass, "this is a random sentence that should not match", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
with (
chat_session.async_get_chat_session(hass, result.conversation_id) as session,
@@ -3511,7 +3511,7 @@ async def test_intent_tool_call_with_error_response(hass: HomeAssistant) -> None
hass, "turn on the non existent device", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.response_type is intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
with (

Some files were not shown because too many files have changed in this diff Show More