Compare commits

..

1 Commits

Author SHA1 Message Date
Franck Nijhof 1096c8af13 Bump version to 2026.7.0b0 2026-06-24 16:36:39 +00:00
43 changed files with 112 additions and 447 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ on:
env:
CACHE_VERSION: 3
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2026.8"
HA_SHORT_VERSION: "2026.7"
ADDITIONAL_PYTHON_VERSIONS: "[]"
# 10.3 is the oldest supported version
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
Generated
+2 -2
View File
@@ -790,8 +790,8 @@ CLAUDE.md @home-assistant/core
/tests/components/html5/ @alexyao2015 @tr4nt0r
/homeassistant/components/http/ @home-assistant/core
/tests/components/http/ @home-assistant/core
/homeassistant/components/huawei_lte/ @fphammerle
/tests/components/huawei_lte/ @fphammerle
/homeassistant/components/huawei_lte/ @scop @fphammerle
/tests/components/huawei_lte/ @scop @fphammerle
/homeassistant/components/hue/ @marcelveldt
/tests/components/hue/ @marcelveldt
/homeassistant/components/hue_ble/ @flip-dots
+8 -7
View File
@@ -15,15 +15,16 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
UnitOfApparentPower,
UnitOfDensity,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfFrequency,
UnitOfPower,
UnitOfRatio,
UnitOfReactiveEnergy,
UnitOfReactivePower,
UnitOfSpeed,
@@ -52,19 +53,19 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
BleBoxSensorEntityDescription(
key="pm1",
device_class=SensorDeviceClass.PM1,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
BleBoxSensorEntityDescription(
key="pm2_5",
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
BleBoxSensorEntityDescription(
key="pm10",
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
BleBoxSensorEntityDescription(
@@ -83,7 +84,7 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
BleBoxSensorEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
BleBoxSensorEntityDescription(
@@ -178,7 +179,7 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
BleBoxSensorEntityDescription(
key="co2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
BleBoxSensorEntityDescription(
@@ -21,6 +21,6 @@
"bluetooth-auto-recovery==1.6.4",
"bluetooth-data-tools==1.29.18",
"dbus-fast==5.0.22",
"habluetooth==6.25.1"
"habluetooth==6.23.1"
]
}
+1 -14
View File
@@ -17,7 +17,7 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
)
from .auth import DropboxConfigEntryAuth
from .const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN, OAUTH2_SCOPES
from .const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN
type DropboxConfigEntry = ConfigEntry[DropboxAPIClient]
@@ -31,19 +31,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: DropboxConfigEntry) -> b
translation_domain=DOMAIN,
translation_key="oauth2_implementation_unavailable",
) from err
token = entry.data["token"]
if not set(token.get("scope", "").split()).issuperset(OAUTH2_SCOPES):
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="missing_scopes",
)
if "refresh_token" not in token:
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="missing_refresh_token",
)
oauth2_session = OAuth2Session(hass, entry, oauth2_implementation)
auth = DropboxConfigEntryAuth(
@@ -1,5 +1,7 @@
"""Application credentials platform for the Dropbox integration."""
from typing import override
from homeassistant.components.application_credentials import ClientCredential
from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_entry_oauth2_flow import (
@@ -7,14 +9,14 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
LocalOAuth2ImplementationWithPkce,
)
from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN
from .const import OAUTH2_AUTHORIZE, OAUTH2_SCOPES, OAUTH2_TOKEN
async def async_get_auth_implementation(
hass: HomeAssistant, auth_domain: str, credential: ClientCredential
) -> AbstractOAuth2Implementation:
"""Return auth implementation."""
return LocalOAuth2ImplementationWithPkce(
"""Return custom auth implementation."""
return DropboxOAuth2Implementation(
hass,
auth_domain,
credential.client_id,
@@ -22,3 +24,21 @@ async def async_get_auth_implementation(
OAUTH2_TOKEN,
credential.client_secret,
)
class DropboxOAuth2Implementation(LocalOAuth2ImplementationWithPkce):
"""Custom Dropbox OAuth2 implementation.
Adds the necessary authorize url parameters.
"""
@property
@override
def extra_authorize_data(self) -> dict:
"""Extra data that needs to be appended to the authorize url."""
data: dict = {
"token_access_type": "offline",
"scope": " ".join(OAUTH2_SCOPES),
}
data.update(super().extra_authorize_data)
return data
@@ -12,7 +12,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler
from .auth import DropboxConfigFlowAuth
from .const import DOMAIN, OAUTH2_SCOPES
from .const import DOMAIN
class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
@@ -26,15 +26,6 @@ class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
"""Return logger."""
return logging.getLogger(__name__)
@property
@override
def extra_authorize_data(self) -> dict:
"""Extra data that needs to be appended to the authorize url."""
return {
"token_access_type": "offline",
"scope": " ".join(OAUTH2_SCOPES),
}
@override
async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult:
"""Create an entry for the flow, or update existing entry."""
@@ -60,9 +51,6 @@ class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Perform reauth upon an API authentication error."""
token = entry_data[CONF_TOKEN]
if not set(token.get("scope", "").split()).issuperset(OAUTH2_SCOPES):
return await self.async_step_reauth_permissions()
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
@@ -72,11 +60,3 @@ class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
if user_input is None:
return self.async_show_form(step_id="reauth_confirm")
return await self.async_step_user()
async def async_step_reauth_permissions(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Dialog that informs the user that additional permissions are required."""
if user_input is None:
return self.async_show_form(step_id="reauth_permissions")
return await self.async_step_user()
@@ -12,7 +12,6 @@ OAUTH2_SCOPES = [
"account_info.read",
"files.content.read",
"files.content.write",
"files.metadata.read",
]
DATA_BACKUP_AGENT_LISTENERS: HassKey[list[Callable[[], None]]] = HassKey(
@@ -8,6 +8,6 @@
"documentation": "https://www.home-assistant.io/integrations/dropbox",
"integration_type": "service",
"iot_class": "cloud_polling",
"quality_scale": "silver",
"quality_scale": "bronze",
"requirements": ["python-dropbox-api==0.1.4"]
}
@@ -52,9 +52,7 @@ rules:
status: exempt
comment: Integration does not have any entities.
integration-owner: done
log-when-unavailable:
status: exempt
comment: Integration does not have any entities.
log-when-unavailable: todo
parallel-updates:
status: exempt
comment: Integration does not make any entity updates.
@@ -24,20 +24,10 @@
"reauth_confirm": {
"description": "The Dropbox integration needs to re-authenticate your account.",
"title": "[%key:common::config_flow::title::reauth%]"
},
"reauth_permissions": {
"description": "The Dropbox integration requires additional permissions to function correctly.",
"title": "[%key:common::config_flow::title::reauth%]"
}
}
},
"exceptions": {
"missing_refresh_token": {
"message": "[%key:component::dropbox::config::step::reauth_confirm::description%]"
},
"missing_scopes": {
"message": "[%key:component::dropbox::config::step::reauth_permissions::description%]"
},
"oauth2_implementation_unavailable": {
"message": "[%key:common::exceptions::oauth2_implementation_unavailable::message%]"
}
+7 -6
View File
@@ -15,9 +15,10 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
UnitOfRatio,
UnitOfTime,
)
from homeassistant.core import HomeAssistant, callback
@@ -71,7 +72,7 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
key="target_flow_level",
translation_key="target_flow_level",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=0,
value_fn=lambda node: (
node.ventilation.flow_lvl_tgt if node.ventilation else None
@@ -95,7 +96,7 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
key="co2",
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
value_fn=lambda node: node.sensor.co2 if node.sensor else None,
node_types=(
NodeType.BSCO2,
@@ -107,7 +108,7 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
DucoSensorEntityDescription(
key="iaq_co2",
translation_key="iaq_co2",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
value_fn=lambda node: node.sensor.iaq_co2 if node.sensor else None,
@@ -122,14 +123,14 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda node: node.sensor.rh if node.sensor else None,
node_types=(NodeType.BSRH, NodeType.UCRH, NodeType.VLVRH, NodeType.VLVCO2RH),
),
DucoSensorEntityDescription(
key="iaq_rh",
translation_key="iaq_rh",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
value_fn=lambda node: node.sensor.iaq_rh if node.sensor else None,
@@ -52,9 +52,7 @@ __all__ = [
"DoorbellEventType",
"EventDeviceClass",
"EventEntity",
"EventEntityCapabilityAttribute",
"EventEntityDescription",
"EventEntityStateAttribute",
]
# mypy: disallow-any-generics
@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["homematicip"],
"requirements": ["homematicip==2.13.2"]
"requirements": ["homematicip==2.13.1"]
}
@@ -245,14 +245,11 @@ class HuaweiLteConfigFlow(ConfigFlow, domain=DOMAIN):
)
assert conn
def _get_info_and_disconnect() -> tuple[dict, dict]:
result = get_device_info(conn)
self._disconnect(conn)
return result
info, wlan_settings = await self.hass.async_add_executor_job(
_get_info_and_disconnect
get_device_info, conn
)
# pylint: disable-next=home-assistant-sequential-executor-jobs
await self.hass.async_add_executor_job(self._disconnect, conn)
user_input.update(
{
@@ -1,7 +1,7 @@
{
"domain": "huawei_lte",
"name": "Huawei LTE",
"codeowners": ["@fphammerle"],
"codeowners": ["@scop", "@fphammerle"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/huawei_lte",
"integration_type": "device",
@@ -1,7 +1,6 @@
"""Event parser and human readable log generator."""
from homeassistant.components.automation import EVENT_AUTOMATION_TRIGGERED
from homeassistant.components.event import EventEntityStateAttribute
from homeassistant.components.script import EVENT_SCRIPT_STARTED
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import EVENT_CALL_SERVICE, EVENT_LOGBOOK_ENTRY
@@ -40,12 +39,8 @@ LOGBOOK_ENTRY_SOURCE = "source"
LOGBOOK_ENTRY_MESSAGE = "message"
LOGBOOK_ENTRY_NAME = "name"
LOGBOOK_ENTRY_STATE = "state"
LOGBOOK_ENTRY_ATTRIBUTES = "attributes"
LOGBOOK_ENTRY_WHEN = "when"
# State attributes surfaced in logbook entries; extend as needed.
EXPOSED_STATE_ATTRIBUTES = {EventEntityStateAttribute.EVENT_TYPE}
# Automation events that can affect an entity_id or device_id
AUTOMATION_EVENTS = {EVENT_AUTOMATION_TRIGGERED, EVENT_SCRIPT_STARTED}
+3 -7
View File
@@ -106,11 +106,10 @@ CONTEXT_PARENT_ID_BIN_POS: Final = 6
STATE_POS: Final = 7
ENTITY_ID_POS: Final = 8
ICON_POS: Final = 9
ATTRIBUTES_POS: Final = 10
CONTEXT_ONLY_POS: Final = 11
CONTEXT_ONLY_POS: Final = 10
# - For EventAsRow, additional fields are:
DATA_POS: Final = 12
CONTEXT_POS: Final = 13
DATA_POS: Final = 11
CONTEXT_POS: Final = 12
@final # Final to allow direct checking of the type instead of using isinstance
@@ -130,7 +129,6 @@ class EventAsRow(NamedTuple):
state: str | None
entity_id: str | None
icon: str | None
attributes: Mapping[str, Any] | None
context_only: bool | None
# Additional fields for EventAsRow
@@ -154,7 +152,6 @@ def async_event_to_row(event: Event) -> EventAsRow:
state=None,
entity_id=None,
icon=None,
attributes=None,
context_only=None,
data=event.data,
context=context,
@@ -178,7 +175,6 @@ def async_event_to_row(event: Event) -> EventAsRow:
state=new_state.state,
entity_id=new_state.entity_id,
icon=new_state.attributes.get(ATTR_ICON),
attributes=new_state.attributes,
context_only=None,
data=event.data,
context=context,
+1 -27
View File
@@ -1,6 +1,6 @@
"""Event parser and human readable log generator."""
from collections.abc import Callable, Collection, Generator, Mapping, Sequence
from collections.abc import Callable, Collection, Generator, Sequence
from dataclasses import dataclass, field
from datetime import datetime as dt
import logging
@@ -16,7 +16,6 @@ from homeassistant.components.recorder import get_instance
from homeassistant.components.recorder.filters import Filters
from homeassistant.components.recorder.models import (
bytes_to_uuid_hex_or_none,
decode_attributes_from_source,
extract_event_type_ids,
extract_metadata_ids,
process_timestamp_to_utc_isoformat,
@@ -54,8 +53,6 @@ from .const import (
CONTEXT_STATE,
CONTEXT_USER_ID,
DOMAIN,
EXPOSED_STATE_ATTRIBUTES,
LOGBOOK_ENTRY_ATTRIBUTES,
LOGBOOK_ENTRY_DOMAIN,
LOGBOOK_ENTRY_ENTITY_ID,
LOGBOOK_ENTRY_ICON,
@@ -67,7 +64,6 @@ from .const import (
)
from .helpers import is_sensor_continuous
from .models import (
ATTRIBUTES_POS,
CONTEXT_ID_BIN_POS,
CONTEXT_ONLY_POS,
CONTEXT_PARENT_ID_BIN_POS,
@@ -277,24 +273,6 @@ class EventProcessor:
)
def _exposed_state_attributes(
row: Row | EventAsRow, attr_cache: dict[str, dict[str, Any]]
) -> dict[str, Any]:
"""Return the allowlisted state attributes for a state change row."""
attributes: Mapping[str, Any] | None
if type(row) is EventAsRow:
attributes = row[ATTRIBUTES_POS]
else:
attributes = decode_attributes_from_source(row[ATTRIBUTES_POS], attr_cache)
if not attributes:
return {}
return {
name: attributes[name]
for name in EXPOSED_STATE_ATTRIBUTES
if name in attributes
}
def _humanify(
hass: HomeAssistant,
rows: Generator[EventAsRow] | Sequence[Row] | Result,
@@ -316,8 +294,6 @@ def _humanify(
get_context = context_augmenter.get_context
context_id_bin: bytes
data: dict[str, Any]
# Decode each shared attribute set only once per run.
attr_cache: dict[str, dict[str, Any]] = {}
context_user_ids = logbook_run.context_user_ids
# Skip the LRU write on one-shot runs — the LogbookRun is discarded.
@@ -361,8 +337,6 @@ def _humanify(
data[LOGBOOK_ENTRY_NAME] = entity_name_cache_get(entity_id)
if icon := row[ICON_POS]:
data[LOGBOOK_ENTRY_ICON] = icon
if exposed := _exposed_state_attributes(row, attr_cache):
data[LOGBOOK_ENTRY_ATTRIBUTES] = exposed
elif event_type in external_events:
domain, describe_event = external_events[event_type]
@@ -14,7 +14,6 @@ from homeassistant.components.recorder.db_schema import (
EVENTS_CONTEXT_ID_BIN_INDEX,
OLD_FORMAT_ATTRS_JSON,
OLD_STATE,
SHARED_ATTR_OR_LEGACY_ATTRIBUTES,
SHARED_ATTRS_JSON,
SHARED_DATA_OR_LEGACY_EVENT_DATA,
STATES_CONTEXT_ID_BIN_INDEX,
@@ -66,14 +65,12 @@ STATE_COLUMNS = (
States.state.label("state"),
StatesMeta.entity_id.label("entity_id"),
ICON_OR_OLD_FORMAT_ICON_JSON,
SHARED_ATTR_OR_LEGACY_ATTRIBUTES,
)
STATE_CONTEXT_ONLY_COLUMNS = (
States.state.label("state"),
StatesMeta.entity_id.label("entity_id"),
literal(value=None, type_=sqlalchemy.String).label("icon"),
literal(value=None, type_=sqlalchemy.String).label("attributes"),
)
EVENT_COLUMNS_FOR_STATE_SELECT = (
@@ -96,7 +93,6 @@ EMPTY_STATE_COLUMNS = (
literal(value=None, type_=sqlalchemy.String).label("state"),
literal(value=None, type_=sqlalchemy.String).label("entity_id"),
literal(value=None, type_=sqlalchemy.String).label("icon"),
literal(value=None, type_=sqlalchemy.String).label("attributes"),
)
@@ -15,29 +15,16 @@ from homeassistant.const import (
CONF_PASSWORD,
CONF_USERNAME,
)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
TextSelectorType,
)
from homeassistant.helpers.service_info import zeroconf
from .const import CONF_SERIAL, DOMAIN
USER_SCHEMA = vol.Schema({vol.Required(CONF_HOST): TextSelector()})
USER_SCHEMA = vol.Schema({vol.Required(CONF_HOST): cv.string})
AUTH_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): TextSelector(
TextSelectorConfig(autocomplete="username")
),
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(
type=TextSelectorType.PASSWORD, autocomplete="current-password"
)
),
}
{vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string}
)
@@ -9,7 +9,6 @@ from .context import (
from .database import DatabaseEngine, DatabaseOptimizer, UnsupportedDialect
from .event import extract_event_type_ids
from .state import LazyState, extract_metadata_ids, row_to_compressed_state
from .state_attributes import decode_attributes_from_source
from .statistics import (
CalendarStatisticPeriod,
FixedStatisticPeriod,
@@ -45,7 +44,6 @@ __all__ = [
"bytes_to_ulid_or_none",
"bytes_to_uuid_hex_or_none",
"datetime_to_timestamp_or_none",
"decode_attributes_from_source",
"extract_event_type_ids",
"extract_metadata_ids",
"process_timestamp",
@@ -321,7 +321,6 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = (
options=[
"always",
"delayed",
"delegated",
"scheduled",
],
value_lambda=_get_charging_settings_mode_formatted,
@@ -157,7 +157,6 @@
"state": {
"always": "Always",
"delayed": "Delayed",
"delegated": "Delegated",
"scheduled": "Scheduled"
}
},
@@ -42,5 +42,5 @@
"iot_class": "local_push",
"loggers": ["switchbot"],
"quality_scale": "gold",
"requirements": ["PySwitchbot==2.3.0"]
"requirements": ["PySwitchbot==2.2.0"]
}
@@ -14,5 +14,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["switchbot_api"],
"requirements": ["switchbot-api==2.12.0"]
"requirements": ["switchbot-api==2.11.1"]
}
@@ -67,13 +67,12 @@ class Tami4ConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None:
otp = user_input["otp"]
try:
def _submit_otp_and_create_api() -> tuple[str, Tami4EdgeAPI]:
refresh_token = Tami4EdgeAPI.submit_otp(self.phone, otp)
return refresh_token, Tami4EdgeAPI(refresh_token)
refresh_token, api = await self.hass.async_add_executor_job(
_submit_otp_and_create_api
refresh_token = await self.hass.async_add_executor_job(
Tami4EdgeAPI.submit_otp, self.phone, otp
)
# pylint: disable-next=home-assistant-sequential-executor-jobs
api = await self.hass.async_add_executor_job(
Tami4EdgeAPI, refresh_token
)
except exceptions.OTPFailedException:
errors["base"] = "invalid_auth"
@@ -9,5 +9,5 @@
"iot_class": "local_push",
"loggers": ["uiprotect"],
"quality_scale": "platinum",
"requirements": ["uiprotect==15.0.0"]
"requirements": ["uiprotect==14.0.0"]
}
+3 -7
View File
@@ -62,14 +62,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: VeraConfigEntry) -> bool
controller = veraApi.VeraController(base_url, subscription_registry)
try:
all_devices = await hass.async_add_executor_job(controller.get_devices)
def _get_devices_and_scenes():
"""Get devices and scenes from the Vera controller."""
return controller.get_devices(), controller.get_scenes()
all_devices, all_scenes = await hass.async_add_executor_job(
_get_devices_and_scenes
)
# pylint: disable-next=home-assistant-sequential-executor-jobs
all_scenes = await hass.async_add_executor_job(controller.get_scenes)
except RequestException as exception:
# There was a network related error connecting to the Vera controller.
_LOGGER.exception("Error communicating with Vera API")
+1 -1
View File
@@ -23,7 +23,7 @@
"universal_silabs_flasher",
"serialx"
],
"requirements": ["zha==2.0.0", "zha-quirks==2.1.0"],
"requirements": ["zha==2.0.0", "zha-quirks==2.0.0"],
"usb": [
{
"description": "*2652*",
-15
View File
@@ -407,9 +407,6 @@
"reset_alarm": {
"name": "Reset alarm"
},
"reset_energy": {
"name": "Reset energy"
},
"reset_frost_lock": {
"name": "Frost lock reset"
},
@@ -1390,12 +1387,6 @@
"status_indication": {
"name": "Status indication"
},
"switch_action_l1": {
"name": "Switch action L1"
},
"switch_action_l2": {
"name": "Switch action L2"
},
"switch_actions": {
"name": "Switch actions"
},
@@ -1408,12 +1399,6 @@
"switch_type": {
"name": "Switch type"
},
"switch_type_l1": {
"name": "Switch type L1"
},
"switch_type_l2": {
"name": "Switch type L2"
},
"temperature_display_mode": {
"name": "Temperature display mode"
},
+2 -2
View File
@@ -21,8 +21,8 @@ if TYPE_CHECKING:
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2026
MINOR_VERSION: Final = 8
PATCH_VERSION: Final = "0.dev0"
MINOR_VERSION: Final = 7
PATCH_VERSION: Final = "0b0"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 14, 2)
+1 -1
View File
@@ -35,7 +35,7 @@ file-read-backwards==2.0.0
fnv-hash-fast==2.0.3
go2rtc-client==0.4.0
ha-ffmpeg==3.2.2
habluetooth==6.25.1
habluetooth==6.23.1
hass-nabucasa==2.2.0
hassil==3.8.0
home-assistant-bluetooth==2.0.0
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2026.8.0.dev0"
version = "2026.7.0b0"
license = "Apache-2.0"
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
description = "Open-source home automation platform running on Python 3."
+6 -6
View File
@@ -83,7 +83,7 @@ PyRMVtransport==0.3.3
PySrDaliGateway==0.21.0
# homeassistant.components.switchbot
PySwitchbot==2.3.0
PySwitchbot==2.2.0
# homeassistant.components.switchmate
PySwitchmate==0.5.1
@@ -1219,7 +1219,7 @@ ha-xthings-cloud==1.0.5
habiticalib==0.4.7
# homeassistant.components.bluetooth
habluetooth==6.25.1
habluetooth==6.23.1
# homeassistant.components.hanna
hanna-cloud==0.0.7
@@ -1284,7 +1284,7 @@ homekit-audio-proxy==1.2.1
homelink-integration-api==0.0.5
# homeassistant.components.homematicip_cloud
homematicip==2.13.2
homematicip==2.13.1
# homeassistant.components.homevolt
homevolt==0.5.0
@@ -3114,7 +3114,7 @@ surepy==0.9.0
swisshydrodata==0.1.0
# homeassistant.components.switchbot_cloud
switchbot-api==2.12.0
switchbot-api==2.11.1
# homeassistant.components.synology_srm
synology-srm==0.2.0
@@ -3245,7 +3245,7 @@ uasiren==0.0.1
uhooapi==1.2.8
# homeassistant.components.unifiprotect
uiprotect==15.0.0
uiprotect==14.0.0
# homeassistant.components.landisgyr_heat_meter
ultraheat-api==0.6.1
@@ -3453,7 +3453,7 @@ zeroconf==0.150.0
zeversolar==0.3.2
# homeassistant.components.zha
zha-quirks==2.1.0
zha-quirks==2.0.0
# homeassistant.components.zha
zha==2.0.0
@@ -128,54 +128,6 @@ async def test_already_configured(
assert result["reason"] == "already_configured"
@pytest.mark.usefixtures("current_request_with_host")
@pytest.mark.parametrize(
("token", "expected_step"),
[
(
{
"access_token": "mock-access-token",
"expires_at": 9_999_999_999,
"scope": " ".join(OAUTH2_SCOPES),
},
"reauth_confirm",
),
(
{
"access_token": "mock-access-token",
"refresh_token": "mock-refresh-token",
"expires_at": 9_999_999_999,
"scope": "account_info.read files.content.read files.content.write",
},
"reauth_permissions",
),
],
ids=["missing_refresh_token", "missing_scope"],
)
async def test_reauth_confirm_step(
hass: HomeAssistant,
mock_config_entry,
token: dict[str, object],
expected_step: str,
) -> None:
"""Test reauth shows the correct confirmation step for the broken token."""
mock_config_entry.add_to_hass(hass)
hass.config_entries.async_update_entry(
mock_config_entry, data={**mock_config_entry.data, "token": token}
)
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == expected_step
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] is FlowResultType.EXTERNAL_STEP
assert result["step_id"] == "auth"
@pytest.mark.usefixtures("current_request_with_host")
@pytest.mark.parametrize(
(
+1 -43
View File
@@ -1,13 +1,11 @@
"""Test the Dropbox integration setup."""
from typing import Any
from unittest.mock import AsyncMock, patch
import pytest
from python_dropbox_api import DropboxAuthException, DropboxUnknownException
from homeassistant.components.dropbox.const import DOMAIN, OAUTH2_SCOPES
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_entry_oauth2_flow import (
ImplementationUnavailableError,
@@ -82,46 +80,6 @@ async def test_setup_entry_implementation_unavailable(
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
@pytest.mark.usefixtures("mock_dropbox_client")
@pytest.mark.parametrize(
"token",
[
{
"access_token": "mock-access-token",
"expires_at": 9_999_999_999,
"scope": " ".join(OAUTH2_SCOPES),
},
{
"access_token": "mock-access-token",
"refresh_token": "mock-refresh-token",
"expires_at": 9_999_999_999,
"scope": "account_info.read files.content.read files.content.write",
},
],
ids=["missing_refresh_token", "missing_scope"],
)
async def test_setup_entry_triggers_reauth(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
token: dict[str, Any],
) -> None:
"""Test that a broken token triggers a reauth flow during setup."""
mock_config_entry.add_to_hass(hass)
hass.config_entries.async_update_entry(
mock_config_entry, data={**mock_config_entry.data, "token": token}
)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN)
assert len(flows) == 1
assert flows[0]["context"]["source"] == SOURCE_REAUTH
assert flows[0]["context"]["entry_id"] == mock_config_entry.entry_id
@pytest.mark.usefixtures("mock_dropbox_client")
async def test_unload_entry(
hass: HomeAssistant,
@@ -35,7 +35,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'aa:bb:cc:dd:ee:ff_113_humidity',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.bathroom_rh_humidity-state]
@@ -44,7 +44,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bathroom RH Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.bathroom_rh_humidity',
@@ -90,7 +90,7 @@
'supported_features': 0,
'translation_key': 'iaq_rh',
'unique_id': 'aa:bb:cc:dd:ee:ff_113_iaq_rh',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.bathroom_rh_humidity_air_quality_index-state]
@@ -98,7 +98,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bathroom RH Humidity air quality index',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.bathroom_rh_humidity_air_quality_index',
@@ -144,7 +144,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'aa:bb:cc:dd:ee:ff_60_humidity',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.bedroom_valve_humidity-state]
@@ -153,7 +153,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom valve Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.bedroom_valve_humidity',
@@ -199,7 +199,7 @@
'supported_features': 0,
'translation_key': 'iaq_rh',
'unique_id': 'aa:bb:cc:dd:ee:ff_60_iaq_rh',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.bedroom_valve_humidity_air_quality_index-state]
@@ -207,7 +207,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom valve Humidity air quality index',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.bedroom_valve_humidity_air_quality_index',
@@ -253,7 +253,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'aa:bb:cc:dd:ee:ff_61_co2',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensor_entities_state[sensor.hall_valve_carbon_dioxide-state]
@@ -262,7 +262,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Hall valve Carbon dioxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.hall_valve_carbon_dioxide',
@@ -308,7 +308,7 @@
'supported_features': 0,
'translation_key': 'iaq_co2',
'unique_id': 'aa:bb:cc:dd:ee:ff_61_iaq_co2',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.hall_valve_co2_air_quality_index-state]
@@ -316,7 +316,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Hall valve CO2 air quality index',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.hall_valve_co2_air_quality_index',
@@ -362,7 +362,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'aa:bb:cc:dd:ee:ff_50_humidity',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.kitchen_rh_humidity-state]
@@ -371,7 +371,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Kitchen RH Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.kitchen_rh_humidity',
@@ -417,7 +417,7 @@
'supported_features': 0,
'translation_key': 'iaq_rh',
'unique_id': 'aa:bb:cc:dd:ee:ff_50_iaq_rh',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.kitchen_rh_humidity_air_quality_index-state]
@@ -425,7 +425,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Kitchen RH Humidity air quality index',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.kitchen_rh_humidity_air_quality_index',
@@ -635,7 +635,7 @@
'supported_features': 0,
'translation_key': 'target_flow_level',
'unique_id': 'aa:bb:cc:dd:ee:ff_1_target_flow_level',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.living_target_flow_level-state]
@@ -643,7 +643,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Living Target flow level',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.living_target_flow_level',
@@ -781,7 +781,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'aa:bb:cc:dd:ee:ff_2_co2',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensor_entities_state[sensor.office_co2_carbon_dioxide-state]
@@ -790,7 +790,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Office CO2 Carbon dioxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.office_co2_carbon_dioxide',
@@ -836,7 +836,7 @@
'supported_features': 0,
'translation_key': 'iaq_co2',
'unique_id': 'aa:bb:cc:dd:ee:ff_2_iaq_co2',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.office_co2_co2_air_quality_index-state]
@@ -844,7 +844,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Office CO2 CO2 air quality index',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.office_co2_co2_air_quality_index',
@@ -890,7 +890,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'aa:bb:cc:dd:ee:ff_62_co2',
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
'unit_of_measurement': 'ppm',
})
# ---
# name: test_sensor_entities_state[sensor.study_valve_carbon_dioxide-state]
@@ -899,7 +899,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Study valve Carbon dioxide',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
}),
'context': <ANY>,
'entity_id': 'sensor.study_valve_carbon_dioxide',
@@ -945,7 +945,7 @@
'supported_features': 0,
'translation_key': 'iaq_co2',
'unique_id': 'aa:bb:cc:dd:ee:ff_62_iaq_co2',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.study_valve_co2_air_quality_index-state]
@@ -953,7 +953,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Study valve CO2 air quality index',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.study_valve_co2_air_quality_index',
@@ -999,7 +999,7 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'aa:bb:cc:dd:ee:ff_62_humidity',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.study_valve_humidity-state]
@@ -1008,7 +1008,7 @@
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Study valve Humidity',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.study_valve_humidity',
@@ -1054,7 +1054,7 @@
'supported_features': 0,
'translation_key': 'iaq_rh',
'unique_id': 'aa:bb:cc:dd:ee:ff_62_iaq_rh',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
'unit_of_measurement': '%',
})
# ---
# name: test_sensor_entities_state[sensor.study_valve_humidity_air_quality_index-state]
@@ -1062,7 +1062,7 @@
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Study valve Humidity air quality index',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
}),
'context': <ANY>,
'entity_id': 'sensor.study_valve_humidity_air_quality_index',
-43
View File
@@ -42,7 +42,6 @@ from homeassistant.const import (
EVENT_LOGBOOK_ENTRY,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
)
from homeassistant.core import Context, Event, HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
@@ -346,7 +345,6 @@ def create_state_changed_event_from_old_new(
state=new_state and new_state.get("state"),
entity_id=entity_id,
icon=None,
attributes=None,
context_only=False,
data=None,
context=None,
@@ -1859,46 +1857,6 @@ async def test_icon_and_state(
assert response_json[2]["state"] == STATE_OFF
@pytest.mark.usefixtures("recorder_mock")
async def test_state_attributes_in_logbook(
hass: HomeAssistant, hass_client: ClientSessionGenerator
) -> None:
"""Test state attributes are exposed in the logbook like the history.
The recorded subset is surfaced, so attributes the recorder excludes
(such as supported_features) must not appear.
"""
await asyncio.gather(
*[
async_setup_component(hass, domain, {})
for domain in ("homeassistant", "logbook")
]
)
await async_recorder_block_till_done(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
hass.states.async_set("event.doorbell", STATE_UNKNOWN, {"event_type": None})
hass.states.async_set(
"event.doorbell",
"2024-01-01T00:00:00.000+00:00",
{"event_type": "ring", "supported_features": 1},
)
hass.states.async_set(
"event.doorbell", "2024-01-01T00:01:00.000+00:00", {"event_type": "motion"}
)
await async_wait_recording_done(hass)
client = await hass_client()
response_json = await _async_fetch_logbook(client)
entries = [e for e in response_json if e.get("entity_id") == "event.doorbell"]
assert len(entries) == 2
assert entries[0]["attributes"] == {"event_type": "ring"}
assert entries[1]["attributes"] == {"event_type": "motion"}
@pytest.mark.usefixtures("recorder_mock")
async def test_fire_logbook_entries(
hass: HomeAssistant, hass_client: ClientSessionGenerator
@@ -3377,7 +3335,6 @@ async def test_parent_user_attribution_does_not_use_origin_event_fallback(
state=STATE_ON,
entity_id="switch.heater",
icon=None,
attributes=None,
context_only=False,
data={},
context=child_context,
-1
View File
@@ -19,7 +19,6 @@ def test_lazy_event_partial_state_context() -> None:
state="state",
entity_id="entity_id",
icon="icon",
attributes=None,
context_only=False,
data={},
context=Mock(),
@@ -35,7 +35,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_START,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
)
from homeassistant.core import Event, HomeAssistant, State, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
@@ -1480,64 +1479,6 @@ async def test_subscribe_unsubscribe_logbook_stream(
) == listeners_without_writes(init_listeners)
@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0)
async def test_subscribe_logbook_stream_state_attributes(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""Test the live logbook stream exposes allowlisted state attributes."""
now = dt_util.utcnow()
await asyncio.gather(
*[
async_setup_component(hass, domain, {})
for domain in ("homeassistant", "logbook")
]
)
await hass.async_block_till_done()
hass.states.async_set("binary_sensor.is_light", STATE_ON)
hass.states.async_set("binary_sensor.is_light", STATE_OFF)
await hass.async_block_till_done()
await async_wait_recording_done(hass)
websocket_client = await hass_ws_client()
await websocket_client.send_json(
{"id": 7, "type": "logbook/event_stream", "start_time": now.isoformat()}
)
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["id"] == 7
assert msg["type"] == TYPE_RESULT
assert msg["success"]
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["event"]["partial"] is True
await hass.async_block_till_done()
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert "partial" not in msg["event"]
assert msg["event"]["events"] == []
hass.states.async_set("event.doorbell", STATE_UNKNOWN, {"event_type": None})
hass.states.async_set(
"event.doorbell",
"2024-01-01T00:00:00.000+00:00",
{"event_type": "ring", "supported_features": 1},
)
await hass.async_block_till_done()
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["id"] == 7
assert msg["type"] == "event"
assert msg["event"]["events"] == [
{
"entity_id": "event.doorbell",
"state": "2024-01-01T00:00:00.000+00:00",
"attributes": {"event_type": "ring"},
"when": ANY,
}
]
@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0)
async def test_subscribe_unsubscribe_logbook_stream_entities(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator
-23
View File
@@ -80,29 +80,6 @@ async def test_missing_sensor_graceful_handling(
assert state.state == "Charging"
async def test_websocket_callback_updates_entities(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_charger: MagicMock,
) -> None:
"""Test the websocket callback pushes updates to entity state."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("sensor.openevse_mock_config_charging_status")
assert state
assert state.state == "Charging"
mock_charger.status = "Sleeping"
await mock_charger.callback()
await hass.async_block_till_done()
state = hass.states.get("sensor.openevse_mock_config_charging_status")
assert state
assert state.state == "Sleeping"
async def test_sensor_unavailable_on_coordinator_timeout(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
@@ -318,7 +318,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -360,7 +359,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -1145,7 +1143,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -1187,7 +1184,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -2415,7 +2411,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -2457,7 +2452,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -3406,7 +3400,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -3448,7 +3441,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -4281,7 +4273,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -4323,7 +4314,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -5098,7 +5088,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -5140,7 +5129,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -5983,7 +5971,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),
@@ -6025,7 +6012,6 @@
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'always',
'delayed',
'delegated',
'scheduled',
]),
}),