mirror of
https://github.com/home-assistant/core.git
synced 2026-05-22 17:00:50 +02:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2257caeb7 | |||
| 0ec0ea30ac | |||
| 584b32c8b3 | |||
| 4033a8b83a | |||
| add8a5f799 | |||
| 40c0d79d1d | |||
| 7c137b5c73 | |||
| bef8632d78 | |||
| f00decfaa3 | |||
| 42e7add026 | |||
| 263aa3f16e | |||
| 03b364dcf0 | |||
| 3b1aaf39af | |||
| 4a6c5b5a22 | |||
| b82ba43fa4 | |||
| d81ef5593c | |||
| 5c5e50f024 | |||
| e796d9c467 | |||
| 342f23526f | |||
| 814ec697cf | |||
| 120f1446d4 | |||
| 170af75b7d | |||
| 5432d29489 | |||
| 8098f4f6bc | |||
| 6a70077687 | |||
| 5dbb0464ba | |||
| 1df165ea02 | |||
| 62542eb911 | |||
| a842cac34c | |||
| 2460f688e3 | |||
| a868ea443c | |||
| 1d8565483b | |||
| 1ef3301253 | |||
| 525952f016 | |||
| 3257275c5a | |||
| cb54fd4921 | |||
| b391fc61ea | |||
| 1009ce4180 | |||
| 22fb68b7a1 | |||
| 81e06539e6 | |||
| 7c18b67b2e | |||
| a8bc244a7a | |||
| 5975f4b179 | |||
| 9ed16b63a3 | |||
| 8dadaa2f9e | |||
| 4f98c71586 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -917,12 +917,38 @@ jobs:
|
||||
key: >-
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore pytest test counts cache
|
||||
id: cache-pytest-counts
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: pytest_test_counts.json
|
||||
key: >-
|
||||
pytest-counts-${{ runner.os }}-${{ runner.arch }}-${{
|
||||
steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
pytest-counts-${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }}-
|
||||
- name: Run split_tests.py
|
||||
env:
|
||||
TEST_GROUP_COUNT: ${{ needs.info.outputs.test_group_count }}
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python -m script.split_tests ${TEST_GROUP_COUNT} tests
|
||||
python -m script.split_tests \
|
||||
--cache pytest_test_counts.json \
|
||||
${TEST_GROUP_COUNT} tests
|
||||
- name: Save pytest test counts cache
|
||||
# Only the canonical dev push writes the cache, otherwise every PR
|
||||
# build would create a new entry and the actions/cache quota fills
|
||||
# up with near-duplicate snapshots. PRs and feature branches still
|
||||
# restore from dev's most recent cache via restore-keys.
|
||||
if: |
|
||||
github.event_name == 'push'
|
||||
&& github.ref == 'refs/heads/dev'
|
||||
&& steps.cache-pytest-counts.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: pytest_test_counts.json
|
||||
key: ${{ steps.cache-pytest-counts.outputs.cache-primary-key }}
|
||||
- name: Upload pytest_buckets
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}"
|
||||
},
|
||||
|
||||
@@ -130,25 +130,17 @@ def async_register_callback(
|
||||
callback: BluetoothCallback,
|
||||
match_dict: BluetoothCallbackMatcher | None,
|
||||
mode: BluetoothScanningMode,
|
||||
*,
|
||||
scan_interval: float | None = None,
|
||||
scan_duration: float | None = None,
|
||||
) -> Callable[[], None]:
|
||||
"""Register to receive a callback on bluetooth change.
|
||||
|
||||
When ``mode`` is ACTIVE and ``scan_interval`` is provided, the
|
||||
matched address (taken from ``match_dict["address"]``) is registered
|
||||
with habluetooth's active-scan scheduler so AUTO-mode scanners flip
|
||||
on demand every ``scan_interval`` seconds for ``scan_duration``
|
||||
seconds, instead of forcing continuous active scanning. Without an
|
||||
address in the matcher the active-scan request is skipped; the
|
||||
callback itself still fires normally.
|
||||
mode is currently not used as we only support active scanning.
|
||||
Passive scanning will be available in the future. The flag
|
||||
is required to be present to avoid a future breaking change
|
||||
when we support passive scanning.
|
||||
|
||||
Returns a callback that can be used to cancel the registration.
|
||||
"""
|
||||
return _get_manager(hass).async_register_callback(
|
||||
callback, match_dict, mode, scan_interval, scan_duration
|
||||
)
|
||||
return _get_manager(hass).async_register_callback(callback, match_dict)
|
||||
|
||||
|
||||
async def async_process_advertisements(
|
||||
|
||||
@@ -202,9 +202,6 @@ class HomeAssistantBluetoothManager(BluetoothManager):
|
||||
self,
|
||||
callback: BluetoothCallback,
|
||||
matcher: BluetoothCallbackMatcher | None,
|
||||
mode: BluetoothScanningMode = BluetoothScanningMode.ACTIVE,
|
||||
scan_interval: float | None = None,
|
||||
scan_duration: float | None = None,
|
||||
) -> Callable[[], None]:
|
||||
"""Register a callback."""
|
||||
callback_matcher = BluetoothCallbackMatcherWithCallback(callback=callback)
|
||||
@@ -219,31 +216,15 @@ class HomeAssistantBluetoothManager(BluetoothManager):
|
||||
connectable = callback_matcher[CONNECTABLE]
|
||||
self._callback_index.add_callback_matcher(callback_matcher)
|
||||
|
||||
# If the caller declared an active-scan cadence and the matcher
|
||||
# targets a specific address, also wire it into habluetooth's
|
||||
# active-scan scheduler so AUTO-mode scanners flip active on
|
||||
# demand for this device instead of forcing continuous scanning.
|
||||
cancel_active_scan: Callable[[], None] | None = None
|
||||
if (
|
||||
scan_interval is not None
|
||||
and mode is not BluetoothScanningMode.PASSIVE
|
||||
and (address := callback_matcher.get(ADDRESS)) is not None
|
||||
):
|
||||
cancel_active_scan = self.async_register_active_scan(
|
||||
address, scan_interval, scan_duration
|
||||
)
|
||||
|
||||
def _async_remove_callback() -> None:
|
||||
self._callback_index.remove_callback_matcher(callback_matcher)
|
||||
if cancel_active_scan is not None:
|
||||
cancel_active_scan()
|
||||
|
||||
# If we have history for the subscriber, we can trigger the callback
|
||||
# immediately with the last packet so the subscriber can see the
|
||||
# device.
|
||||
history = self._connectable_history if connectable else self._all_history
|
||||
service_infos: Iterable[BluetoothServiceInfoBleak] = []
|
||||
if (address := callback_matcher.get(ADDRESS)) is not None:
|
||||
if address := callback_matcher.get(ADDRESS):
|
||||
if service_info := history.get(address):
|
||||
service_infos = [service_info]
|
||||
else:
|
||||
|
||||
@@ -21,6 +21,6 @@
|
||||
"bluetooth-auto-recovery==1.5.3",
|
||||
"bluetooth-data-tools==1.29.11",
|
||||
"dbus-fast==5.0.3",
|
||||
"habluetooth==6.3.0"
|
||||
"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."""
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"service": "mdi:refresh"
|
||||
},
|
||||
"set_dhw_override": {
|
||||
"service": "mdi:water-heater"
|
||||
"service": "mdi:water-boiler"
|
||||
},
|
||||
"set_system_mode": {
|
||||
"service": "mdi:pencil"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"entity": {
|
||||
"button": {
|
||||
"sync_clock": {
|
||||
"default": "mdi:clock-sync"
|
||||
"default": "mdi:clock-check"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."]
|
||||
}
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" }
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"service": "mdi:lock-open"
|
||||
},
|
||||
"disable": {
|
||||
"service": "mdi:fash-off"
|
||||
"service": "mdi:flash-off"
|
||||
},
|
||||
"enable": {
|
||||
"service": "mdi:flash"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -35,7 +35,7 @@ file-read-backwards==2.0.0
|
||||
fnv-hash-fast==2.0.2
|
||||
go2rtc-client==0.4.0
|
||||
ha-ffmpeg==3.2.2
|
||||
habluetooth==6.3.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."""
|
||||
+7458
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
|
||||
Generated
+3
-3
@@ -1210,7 +1210,7 @@ ha-xthings-cloud==1.0.5
|
||||
habiticalib==0.4.7
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
habluetooth==6.3.0
|
||||
habluetooth==6.2.0
|
||||
|
||||
# homeassistant.components.hanna
|
||||
hanna-cloud==0.0.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
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
|
||||
@@ -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"])
|
||||
+316
-35
@@ -4,6 +4,8 @@
|
||||
import argparse
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from dataclasses import dataclass, field
|
||||
import hashlib
|
||||
import json
|
||||
from math import ceil
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -15,13 +17,15 @@ from typing import Final
|
||||
# place to subdivide to keep each pytest invocation roughly equal in size.
|
||||
_FAN_OUT_DIRS: Final = frozenset({"components"})
|
||||
|
||||
# Cache file format version; bump on any incompatible schema change so old
|
||||
# caches are ignored rather than misread.
|
||||
_CACHE_VERSION: Final = 2
|
||||
|
||||
|
||||
class Bucket:
|
||||
"""Class to hold bucket."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
):
|
||||
def __init__(self) -> None:
|
||||
"""Initialize bucket."""
|
||||
self.total_tests = 0
|
||||
self._paths: list[str] = []
|
||||
@@ -81,9 +85,9 @@ class BucketHolder:
|
||||
if not test_folder.added_to_bucket:
|
||||
raise ValueError("Not all tests are added to a bucket")
|
||||
|
||||
def create_ouput_file(self) -> None:
|
||||
def create_output_file(self) -> None:
|
||||
"""Create output file."""
|
||||
with Path("pytest_buckets.txt").open("w") as file:
|
||||
with Path("pytest_buckets.txt").open("w", encoding="utf-8") as file:
|
||||
for idx, bucket in enumerate(self._buckets):
|
||||
print(f"Bucket {idx + 1} has {bucket.total_tests} tests")
|
||||
file.write(bucket.get_paths_line())
|
||||
@@ -184,9 +188,10 @@ def _collect_batch(paths: list[Path]) -> tuple[str, str, int]:
|
||||
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.
|
||||
Skips entries whose name starts with ``.`` or ``_`` (hidden dirs,
|
||||
``__pycache__``, private helpers), and non-``test_*.py`` files (so
|
||||
helper modules like ``conftest.py`` and ``common.py`` are not passed
|
||||
as explicit collection targets).
|
||||
"""
|
||||
children: list[Path] = []
|
||||
for entry in sorted(path.iterdir()):
|
||||
@@ -216,44 +221,314 @@ def _enumerate_batch_paths(path: Path) -> list[Path]:
|
||||
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)]
|
||||
def _hash_file(path: Path) -> str:
|
||||
"""Return a short content hash for ``path``."""
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()[:16]
|
||||
|
||||
|
||||
def _walk_test_tree(root: Path) -> tuple[list[Path], list[Path]]:
|
||||
"""Walk ``root`` once and return (test files, fixture files).
|
||||
|
||||
Test files are the ``test_*.py`` modules that pytest will collect.
|
||||
Fixture files are every other ``.py`` under ``root`` — ``conftest.py``
|
||||
plus helper modules like ``common.py``. Helpers go into the
|
||||
invalidation hash because they often hold the ``VALUES`` lists that
|
||||
test files import for ``@pytest.mark.parametrize``: editing one
|
||||
changes a test's collected count even though the test file itself is
|
||||
untouched.
|
||||
|
||||
Uses ``os.walk`` rather than ``Path.rglob`` because it's ~2x faster on
|
||||
a 5000-file tree, and subdirectories whose names start with ``.`` or
|
||||
``_`` are pruned instead of visited (hidden dirs, ``__pycache__``,
|
||||
private helpers). Doing both walks in one pass keeps total tree I/O
|
||||
down.
|
||||
"""
|
||||
test_files: list[Path] = []
|
||||
fixtures: list[Path] = []
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
dirnames[:] = [d for d in dirnames if not d.startswith((".", "_"))]
|
||||
base = Path(dirpath)
|
||||
for name in filenames:
|
||||
if not name.endswith(".py"):
|
||||
continue
|
||||
if name.startswith("test_"):
|
||||
test_files.append(base / name)
|
||||
else:
|
||||
fixtures.append(base / name)
|
||||
test_files.sort()
|
||||
fixtures.sort()
|
||||
return test_files, fixtures
|
||||
|
||||
|
||||
def _find_ancestor_conftests(root: Path) -> list[Path]:
|
||||
"""Return ancestor ``conftest.py`` files that pytest would still apply.
|
||||
|
||||
Pytest walks up from each test file looking for conftests; when
|
||||
``root`` is a subtree (eg ``tests/components``) the conftests above
|
||||
it (eg ``tests/conftest.py``) still affect parametrization, so they
|
||||
must contribute to the invalidation hash too. Stops at the first
|
||||
ancestor without a ``conftest.py``.
|
||||
"""
|
||||
ancestors: list[Path] = []
|
||||
current = root.resolve().parent
|
||||
while True:
|
||||
conftest = current / "conftest.py"
|
||||
if not conftest.is_file():
|
||||
break
|
||||
ancestors.append(conftest)
|
||||
if current == current.parent:
|
||||
break
|
||||
current = current.parent
|
||||
return ancestors
|
||||
|
||||
|
||||
def _compute_invalidation_hash(root: Path, fixtures: list[Path]) -> str:
|
||||
"""Return a hash that changes whenever any file in ``fixtures`` changes.
|
||||
|
||||
Any change to a fixture file (conftests, helper modules like
|
||||
``common.py``, ancestor conftests) invalidates the entire test-count
|
||||
cache. This is coarse but safe: any of these can shift fixture
|
||||
parametrization in ways the cache cannot otherwise detect, so we
|
||||
just re-collect everything.
|
||||
|
||||
Paths are encoded with ``os.path.relpath`` so the hash stays stable
|
||||
across machines and also covers ancestor conftests above ``root``
|
||||
(whose ``relative_to(root)`` would fail).
|
||||
"""
|
||||
digest = hashlib.sha256()
|
||||
for fixture in fixtures:
|
||||
digest.update(os.path.relpath(fixture, root).encode())
|
||||
digest.update(b"\0")
|
||||
digest.update(fixture.read_bytes())
|
||||
digest.update(b"\0")
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
@dataclass
|
||||
class _CacheEntry:
|
||||
"""Cached test count for a single file."""
|
||||
|
||||
hash: str
|
||||
count: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Cache:
|
||||
"""Mapping of test file path → cached entry, plus invalidation key."""
|
||||
|
||||
invalidation_hash: str
|
||||
entries: dict[str, _CacheEntry]
|
||||
|
||||
@classmethod
|
||||
def empty(cls, invalidation_hash: str = "") -> _Cache:
|
||||
"""Return a new empty cache."""
|
||||
return cls(invalidation_hash=invalidation_hash, entries={})
|
||||
|
||||
@classmethod
|
||||
def load(cls, path: Path, current_invalidation_hash: str) -> _Cache:
|
||||
"""Load cache from ``path`` and invalidate it on schema/fixture drift.
|
||||
|
||||
Any failure (missing file, bad JSON, version drift, fixture drift)
|
||||
returns an empty cache so the script just falls back to a full
|
||||
collection. This is the self-healing path.
|
||||
"""
|
||||
try:
|
||||
raw = json.loads(path.read_bytes())
|
||||
except OSError, ValueError:
|
||||
return cls.empty(current_invalidation_hash)
|
||||
if not isinstance(raw, dict) or raw.get("version") != _CACHE_VERSION:
|
||||
return cls.empty(current_invalidation_hash)
|
||||
if raw.get("invalidation_hash") != current_invalidation_hash:
|
||||
return cls.empty(current_invalidation_hash)
|
||||
files = raw.get("files")
|
||||
if not isinstance(files, dict):
|
||||
return cls.empty(current_invalidation_hash)
|
||||
entries: dict[str, _CacheEntry] = {}
|
||||
for key, value in files.items():
|
||||
if (
|
||||
not isinstance(value, dict)
|
||||
or not isinstance(value.get("hash"), str)
|
||||
or not isinstance(value.get("count"), int)
|
||||
):
|
||||
# Skip malformed entries instead of discarding the whole cache.
|
||||
continue
|
||||
entries[key] = _CacheEntry(hash=value["hash"], count=value["count"])
|
||||
return cls(invalidation_hash=current_invalidation_hash, entries=entries)
|
||||
|
||||
def save(self, path: Path) -> None:
|
||||
"""Write the cache to ``path``."""
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"version": _CACHE_VERSION,
|
||||
"invalidation_hash": self.invalidation_hash,
|
||||
"files": {
|
||||
key: {"hash": entry.hash, "count": entry.count}
|
||||
for key, entry in sorted(self.entries.items())
|
||||
},
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _resolve_from_cache(
|
||||
test_files: list[Path],
|
||||
cache: _Cache,
|
||||
root: Path,
|
||||
) -> tuple[dict[Path, _CacheEntry], dict[Path, str]]:
|
||||
"""Split ``test_files`` into ``(cached_entries, miss_hashes)``.
|
||||
|
||||
A file is served from cache when its content hash matches what we
|
||||
previously stored; otherwise it is queued for re-collection. Each
|
||||
file is hashed exactly once: hits carry the stored hash forward,
|
||||
misses carry the just-computed hash so the rebuild step doesn't
|
||||
re-read the same bytes a second time.
|
||||
"""
|
||||
hits: dict[Path, _CacheEntry] = {}
|
||||
miss_hashes: dict[Path, str] = {}
|
||||
for file in test_files:
|
||||
file_hash = _hash_file(file)
|
||||
entry = cache.entries.get(str(file.relative_to(root)))
|
||||
if entry is not None and entry.hash == file_hash:
|
||||
hits[file] = entry
|
||||
else:
|
||||
miss_hashes[file] = file_hash
|
||||
return hits, miss_hashes
|
||||
|
||||
|
||||
def _run_collect_batches(paths: list[Path]) -> list[tuple[str, str, int]]:
|
||||
"""Run pytest --collect-only across ``paths`` using a process pool."""
|
||||
workers = min(len(paths), os.cpu_count() or 1) or 1
|
||||
batches = [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))
|
||||
return [_collect_batch(batches[0])]
|
||||
with ProcessPoolExecutor(max_workers=workers) as executor:
|
||||
return list(executor.map(_collect_batch, batches))
|
||||
|
||||
folder = TestFolder(path)
|
||||
for stdout, stderr, returncode in results:
|
||||
|
||||
def _parse_collect_output(stdout: str) -> dict[Path, int]:
|
||||
"""Parse ``pytest --collect-only -qq`` output into ``{path: count}``."""
|
||||
counts: dict[Path, int] = {}
|
||||
for line in stdout.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
file_path, _, total_tests = line.partition(": ")
|
||||
if not file_path or not total_tests:
|
||||
raise ValueError(f"Unexpected line: {line}")
|
||||
counts[Path(file_path)] = int(total_tests)
|
||||
return counts
|
||||
|
||||
|
||||
def _run_pytest_collect(paths: list[Path]) -> dict[Path, int]:
|
||||
"""Run pytest --collect-only across ``paths`` and parse the output."""
|
||||
counts: dict[Path, int] = {}
|
||||
for stdout, stderr, returncode in _run_collect_batches(paths):
|
||||
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)
|
||||
try:
|
||||
counts.update(_parse_collect_output(stdout))
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
sys.exit(1)
|
||||
return counts
|
||||
|
||||
file = TestFile(int(total_tests), Path(file_path))
|
||||
folder.add_test_file(file)
|
||||
|
||||
def _build_folder(root: Path, counts: dict[Path, int]) -> TestFolder:
|
||||
"""Build a ``TestFolder`` from a flat ``{path: count}`` mapping.
|
||||
|
||||
Files reported with zero tests are skipped so they don't enter
|
||||
bucketing (helper modules named ``test_*.py`` with no test functions
|
||||
look like test files to the walker but pytest returns nothing for
|
||||
them).
|
||||
"""
|
||||
folder = TestFolder(root)
|
||||
for file_path, count in counts.items():
|
||||
if count:
|
||||
folder.add_test_file(TestFile(count, file_path))
|
||||
return folder
|
||||
|
||||
|
||||
def _exit_if_empty(paths: list[Path], root: Path) -> None:
|
||||
"""Exit with a clear message when no eligible test paths were found."""
|
||||
if not paths:
|
||||
print(f"No eligible test paths found under {root}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _collect_tests_uncached(path: Path) -> TestFolder:
|
||||
"""Collect tests by handing pytest the top-level directories.
|
||||
|
||||
Skips the tree walk and per-file hashing; used when no cache file is
|
||||
requested so the script behaves like the pre-cache implementation.
|
||||
"""
|
||||
batch_paths = _enumerate_batch_paths(path)
|
||||
_exit_if_empty(batch_paths, path)
|
||||
return _build_folder(path, _run_pytest_collect(batch_paths))
|
||||
|
||||
|
||||
def _collect_tests_cached(path: Path, cache_path: Path) -> TestFolder:
|
||||
"""Collect tests using an on-disk cache for incremental updates."""
|
||||
all_test_files, fixtures = _walk_test_tree(path)
|
||||
_exit_if_empty(all_test_files, path)
|
||||
|
||||
# Include ancestor conftests so a subtree run (eg tests/components)
|
||||
# still invalidates when tests/conftest.py changes.
|
||||
all_fixtures = _find_ancestor_conftests(path) + fixtures
|
||||
invalidation_hash = _compute_invalidation_hash(path, all_fixtures)
|
||||
cache = _Cache.load(cache_path, invalidation_hash)
|
||||
|
||||
hits, miss_hashes = _resolve_from_cache(all_test_files, cache, path)
|
||||
print(
|
||||
f"Cache: {len(hits)} hits / {len(miss_hashes)} misses"
|
||||
f" / {len(all_test_files)} total"
|
||||
)
|
||||
|
||||
new_counts: dict[Path, int] = {}
|
||||
if miss_hashes:
|
||||
# On a full cold-cache run, hand pytest the top-level directories
|
||||
# instead of 5000+ individual file paths: pytest walks dirs much
|
||||
# faster than it resolves each file argument. Once any cache hits
|
||||
# exist, use file-level collection so we only re-collect the diff.
|
||||
collect_paths = _enumerate_batch_paths(path) if not hits else list(miss_hashes)
|
||||
new_counts = _run_pytest_collect(collect_paths)
|
||||
|
||||
# Walk the full set of test files once and decide each file's entry:
|
||||
# hits keep their stored entry (and verified hash), misses build a
|
||||
# fresh entry from the resolve-time hash plus the freshly collected
|
||||
# count. Files in misses that pytest returned no count for are
|
||||
# stored as 0 so they stop re-collecting on the next run.
|
||||
entries: dict[str, _CacheEntry] = {}
|
||||
counts: dict[Path, int] = {}
|
||||
for file in all_test_files:
|
||||
if (entry := hits.get(file)) is None:
|
||||
entry = _CacheEntry(hash=miss_hashes[file], count=new_counts.get(file, 0))
|
||||
entries[str(file.relative_to(path))] = entry
|
||||
counts[file] = entry.count
|
||||
_Cache(invalidation_hash=invalidation_hash, entries=entries).save(cache_path)
|
||||
return _build_folder(path, counts)
|
||||
|
||||
|
||||
def collect_tests(path: Path, cache_path: Path | None = None) -> TestFolder:
|
||||
"""Collect all tests, using an on-disk cache when ``cache_path`` is set."""
|
||||
if cache_path is None:
|
||||
return _collect_tests_uncached(path)
|
||||
if path.is_file():
|
||||
# The cache keys on conftest_hash, but a single file root has no
|
||||
# ancestor conftests to walk and the hash would always be empty,
|
||||
# which would let stale counts survive conftest edits. Skip the
|
||||
# cache for the file-root case rather than silently mis-caching.
|
||||
print(f"--cache ignored: {path} is a single file")
|
||||
return _collect_tests_uncached(path)
|
||||
return _collect_tests_cached(path, cache_path)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Execute script."""
|
||||
parser = argparse.ArgumentParser(description="Split tests into n buckets.")
|
||||
@@ -276,11 +551,17 @@ def main() -> None:
|
||||
help="Path to the test files to split into buckets",
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache",
|
||||
help="Path to a JSON file used to cache per-file test counts",
|
||||
type=Path,
|
||||
default=None,
|
||||
)
|
||||
|
||||
arguments = parser.parse_args()
|
||||
|
||||
print("Collecting tests...")
|
||||
tests = collect_tests(arguments.path)
|
||||
tests = collect_tests(arguments.path, arguments.cache)
|
||||
tests_per_bucket = ceil(tests.total_tests / arguments.bucket_count)
|
||||
|
||||
bucket_holder = BucketHolder(tests_per_bucket, arguments.bucket_count)
|
||||
@@ -290,7 +571,7 @@ def main() -> None:
|
||||
print(f"Total tests: {tests.total_tests}")
|
||||
print(f"Estimated tests per bucket: {tests_per_bucket}")
|
||||
|
||||
bucket_holder.create_ouput_file()
|
||||
bucket_holder.create_output_file()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -1642,108 +1642,6 @@ async def test_register_callback_by_address(
|
||||
assert service_info.manufacturer_id == 89
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_bluetooth")
|
||||
async def test_register_callback_with_scan_interval_registers_active_scan(
|
||||
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock
|
||||
) -> None:
|
||||
"""scan_interval + address forwards to habluetooth's active-scan scheduler."""
|
||||
mock_bt: list[Any] = []
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||
):
|
||||
await async_setup_with_default_adapter(hass)
|
||||
|
||||
with patch.object(hass.config_entries.flow, "async_init"):
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
manager = bluetooth.api._get_manager(hass)
|
||||
sched = manager._auto_scheduler
|
||||
address = "44:44:33:11:23:45"
|
||||
|
||||
def _cb(_si: BluetoothServiceInfo, _ch: BluetoothChange) -> None:
|
||||
return None
|
||||
|
||||
cancel = bluetooth.async_register_callback(
|
||||
hass,
|
||||
_cb,
|
||||
{"address": address},
|
||||
BluetoothScanningMode.ACTIVE,
|
||||
scan_interval=300.0,
|
||||
scan_duration=5.0,
|
||||
)
|
||||
assert address in sched._requests_by_address
|
||||
request = next(iter(sched._requests_by_address[address]))
|
||||
assert request.scan_interval == 300.0
|
||||
assert request.scan_duration == 5.0
|
||||
cancel()
|
||||
assert address not in sched._requests_by_address
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_bluetooth")
|
||||
async def test_register_callback_passive_mode_skips_active_scan(
|
||||
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock
|
||||
) -> None:
|
||||
"""PASSIVE mode never registers an active-scan request, even with cadence."""
|
||||
mock_bt: list[Any] = []
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||
):
|
||||
await async_setup_with_default_adapter(hass)
|
||||
|
||||
with patch.object(hass.config_entries.flow, "async_init"):
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
manager = bluetooth.api._get_manager(hass)
|
||||
sched = manager._auto_scheduler
|
||||
|
||||
def _cb(_si: BluetoothServiceInfo, _ch: BluetoothChange) -> None:
|
||||
return None
|
||||
|
||||
cancel = bluetooth.async_register_callback(
|
||||
hass,
|
||||
_cb,
|
||||
{"address": "44:44:33:11:23:45"},
|
||||
BluetoothScanningMode.PASSIVE,
|
||||
scan_interval=300.0,
|
||||
)
|
||||
assert sched._requests_by_address == {}
|
||||
cancel()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_bluetooth")
|
||||
async def test_register_callback_without_address_skips_active_scan(
|
||||
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock
|
||||
) -> None:
|
||||
"""A scan_interval without an address in the matcher is a no-op for the scheduler."""
|
||||
mock_bt: list[Any] = []
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||
):
|
||||
await async_setup_with_default_adapter(hass)
|
||||
|
||||
with patch.object(hass.config_entries.flow, "async_init"):
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
manager = bluetooth.api._get_manager(hass)
|
||||
sched = manager._auto_scheduler
|
||||
|
||||
def _cb(_si: BluetoothServiceInfo, _ch: BluetoothChange) -> None:
|
||||
return None
|
||||
|
||||
cancel = bluetooth.async_register_callback(
|
||||
hass,
|
||||
_cb,
|
||||
{SERVICE_UUID: "cba20d00-224d-11e6-9fb8-0002a5d5c51b"},
|
||||
BluetoothScanningMode.ACTIVE,
|
||||
scan_interval=300.0,
|
||||
)
|
||||
assert sched._requests_by_address == {}
|
||||
cancel()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_bluetooth")
|
||||
async def test_register_callback_by_address_connectable_only(
|
||||
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -88,7 +88,7 @@ async def test_cover_set_position(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Opening"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -102,7 +102,7 @@ async def test_cover_set_position(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Closing"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -116,7 +116,7 @@ async def test_cover_set_position(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Position set"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -144,7 +144,7 @@ async def test_cover_device_class(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Opening the garage"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -168,7 +168,7 @@ async def test_valve_intents(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Opening"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -182,7 +182,7 @@ async def test_valve_intents(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Closing"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -196,7 +196,7 @@ async def test_valve_intents(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Position set"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -229,7 +229,7 @@ async def test_vacuum_intents(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Started"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -243,7 +243,7 @@ async def test_vacuum_intents(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Returning"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -275,7 +275,7 @@ async def test_media_player_intents(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Paused"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -294,7 +294,7 @@ async def test_media_player_intents(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Resumed"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -313,7 +313,7 @@ async def test_media_player_intents(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Playing next"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -329,7 +329,7 @@ async def test_media_player_intents(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "Volume set"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
@@ -399,7 +399,7 @@ async def test_turn_floor_lights_on_off(
|
||||
)
|
||||
|
||||
assert len(on_calls) == 2
|
||||
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert {s.entity_id for s in result.response.matched_states} == {
|
||||
kitchen_light.entity_id,
|
||||
living_room_light.entity_id,
|
||||
@@ -411,7 +411,7 @@ async def test_turn_floor_lights_on_off(
|
||||
)
|
||||
|
||||
assert len(on_calls) == 1
|
||||
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert {s.entity_id for s in result.response.matched_states} == {
|
||||
bedroom_light.entity_id
|
||||
}
|
||||
@@ -422,7 +422,7 @@ async def test_turn_floor_lights_on_off(
|
||||
)
|
||||
|
||||
assert len(off_calls) == 1
|
||||
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert {s.entity_id for s in result.response.matched_states} == {
|
||||
bedroom_light.entity_id
|
||||
}
|
||||
@@ -474,7 +474,7 @@ async def test_date_time(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "September 17th, 2013"
|
||||
|
||||
result = await conversation.async_converse(
|
||||
@@ -483,5 +483,5 @@ async def test_date_time(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = result.response
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech["plain"]["speech"] == "1:02 AM"
|
||||
|
||||
@@ -57,7 +57,7 @@ async def test_init_failure(
|
||||
"""Test an initialization error on integration load."""
|
||||
mock_cookidoo_client.login.side_effect = exception
|
||||
await setup_integration(hass, cookidoo_config_entry)
|
||||
assert cookidoo_config_entry.state == status
|
||||
assert cookidoo_config_entry.state is status
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -43,7 +43,7 @@ async def test_open_cover_intent(hass: HomeAssistant, slots: dict[str, Any]) ->
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
@@ -75,7 +75,7 @@ async def test_close_cover_intent(hass: HomeAssistant, slots: dict[str, Any]) ->
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
@@ -110,7 +110,7 @@ async def test_set_cover_position(hass: HomeAssistant, slots: dict[str, Any]) ->
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
|
||||
@@ -29,7 +29,7 @@ async def test_set_speed_intent(hass: HomeAssistant) -> None:
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == DOMAIN
|
||||
|
||||
@@ -35,4 +35,4 @@ async def test_setup_exceptions(
|
||||
"""Test the _async_setup."""
|
||||
mock_firefly_client.get_about.side_effect = exception
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
assert mock_config_entry.state == expected_state
|
||||
assert mock_config_entry.state is expected_state
|
||||
|
||||
@@ -679,7 +679,7 @@ async def test_setup_with_retryable_setup_entry_error_custom_server(
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(config_entries) == 1
|
||||
assert config_entries[0].state == expected_config_entry_state
|
||||
assert config_entries[0].state is expected_config_entry_state
|
||||
assert expected_log_message in caplog.text
|
||||
|
||||
|
||||
@@ -716,7 +716,7 @@ async def test_setup_with_retryable_setup_entry_error_default_server(
|
||||
config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(config_entries) == has_go2rtc_entry
|
||||
for config_entry in config_entries:
|
||||
assert config_entry.state == expected_config_entry_state
|
||||
assert config_entry.state is expected_config_entry_state
|
||||
assert expected_log_message in caplog.text
|
||||
|
||||
|
||||
@@ -750,7 +750,7 @@ async def test_setup_with_version_error(
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(config_entries) == 1
|
||||
assert config_entries[0].state == expected_config_entry_state
|
||||
assert config_entries[0].state is expected_config_entry_state
|
||||
assert expected_log_message in caplog.text
|
||||
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ async def test_error_handling(
|
||||
Context(),
|
||||
agent_id="conversation.google_ai_conversation",
|
||||
)
|
||||
assert result.response.response_type == intent.IntentResponseType.ERROR, result
|
||||
assert result.response.response_type is intent.IntentResponseType.ERROR, result
|
||||
assert result.response.error_code == "unknown", result
|
||||
assert (
|
||||
result.response.as_dict()["speech"]["plain"]["speech"] == ERROR_GETTING_RESPONSE
|
||||
@@ -249,7 +249,7 @@ async def test_function_call(
|
||||
agent_id=agent_id,
|
||||
device_id="test_device",
|
||||
)
|
||||
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert (
|
||||
result.response.as_dict()["speech"]["plain"]["speech"]
|
||||
== "I've called the test function with the provided parameters."
|
||||
@@ -356,7 +356,7 @@ async def test_google_search_tool_is_sent(
|
||||
agent_id=agent_id,
|
||||
device_id="test_device",
|
||||
)
|
||||
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert result.response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert (
|
||||
result.response.as_dict()["speech"]["plain"]["speech"]
|
||||
== "The last winner of the 2024 FIFA World Cup was Argentina."
|
||||
@@ -406,7 +406,7 @@ async def test_blocked_response(
|
||||
device_id="test_device",
|
||||
)
|
||||
|
||||
assert result.response.response_type == intent.IntentResponseType.ERROR, result
|
||||
assert result.response.response_type is intent.IntentResponseType.ERROR, result
|
||||
assert result.response.error_code == "unknown", result
|
||||
assert result.response.as_dict()["speech"]["plain"]["speech"] == (
|
||||
"The message got blocked due to content violations, reason: SAFETY"
|
||||
@@ -450,7 +450,7 @@ async def test_empty_response(
|
||||
agent_id=agent_id,
|
||||
device_id="test_device",
|
||||
)
|
||||
assert result.response.response_type == intent.IntentResponseType.ERROR, result
|
||||
assert result.response.response_type is intent.IntentResponseType.ERROR, result
|
||||
assert result.response.error_code == "unknown", result
|
||||
assert result.response.as_dict()["speech"]["plain"]["speech"] == (
|
||||
"Unable to get response"
|
||||
@@ -485,7 +485,7 @@ async def test_none_response(
|
||||
device_id="test_device",
|
||||
)
|
||||
|
||||
assert result.response.response_type == intent.IntentResponseType.ERROR, result
|
||||
assert result.response.response_type is intent.IntentResponseType.ERROR, result
|
||||
assert result.response.error_code == "unknown", result
|
||||
assert result.response.as_dict()["speech"]["plain"]["speech"] == (
|
||||
"The message got blocked due to content violations, reason: unknown"
|
||||
@@ -514,7 +514,7 @@ async def test_converse_error(
|
||||
agent_id="conversation.google_ai_conversation",
|
||||
)
|
||||
|
||||
assert result.response.response_type == intent.IntentResponseType.ERROR, result
|
||||
assert result.response.response_type is intent.IntentResponseType.ERROR, result
|
||||
assert result.response.error_code == "unknown", result
|
||||
assert result.response.as_dict()["speech"]["plain"]["speech"] == (
|
||||
"Error preparing LLM API"
|
||||
|
||||
@@ -178,7 +178,7 @@ async def test_token_refresh_error(
|
||||
assert not await integration_setup(client)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state == expected_config_entry_state
|
||||
assert config_entry.state is expected_config_entry_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -199,7 +199,7 @@ async def test_client_error(
|
||||
client_with_exception.get_home_appliances.return_value = None
|
||||
client_with_exception.get_home_appliances.side_effect = exception
|
||||
assert not await integration_setup(client_with_exception)
|
||||
assert config_entry.state == expected_state
|
||||
assert config_entry.state is expected_state
|
||||
assert client_with_exception.get_home_appliances.call_count == 1
|
||||
|
||||
|
||||
|
||||
@@ -357,7 +357,7 @@ async def consume_progress_flow(
|
||||
result = await hass.config_entries.flow.async_configure(flow_id)
|
||||
flow_id = result["flow_id"]
|
||||
|
||||
if result["type"] != FlowResultType.SHOW_PROGRESS:
|
||||
if result["type"] is not FlowResultType.SHOW_PROGRESS:
|
||||
break
|
||||
|
||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user