forked from home-assistant/core
Compare commits
15 Commits
2023.8.0b1
...
2023.8.0b2
| Author | SHA1 | Date | |
|---|---|---|---|
| 99634e22bd | |||
| 4bd4c5666d | |||
| 93c536882b | |||
| 3764c2e9de | |||
| b23286ce6f | |||
| 734c16b816 | |||
| f1fc09cb1d | |||
| 364e7b838a | |||
| 7f9db40390 | |||
| 38e22f5614 | |||
| 945959827d | |||
| 1a0593fc9a | |||
| f54c36ec16 | |||
| 3beffb5103 | |||
| 78dad22fb3 |
@@ -19,6 +19,6 @@
|
||||
"bluetooth-adapters==0.16.0",
|
||||
"bluetooth-auto-recovery==1.2.1",
|
||||
"bluetooth-data-tools==1.6.1",
|
||||
"dbus-fast==1.87.2"
|
||||
"dbus-fast==1.87.5"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -140,7 +140,6 @@ class EsphomeEntity(Entity, Generic[_InfoT, _StateT]):
|
||||
"""Define a base esphome entity."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
_static_info: _InfoT
|
||||
_state: _StateT
|
||||
_has_state: bool
|
||||
@@ -169,6 +168,7 @@ class EsphomeEntity(Entity, Generic[_InfoT, _StateT]):
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, device_info.mac_address)}
|
||||
)
|
||||
self._entry_id = entry_data.entry_id
|
||||
self._attr_has_entity_name = bool(device_info.friendly_name)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
|
||||
@@ -45,7 +45,6 @@ __all__ = [
|
||||
"EventDeviceClass",
|
||||
"EventEntity",
|
||||
"EventEntityDescription",
|
||||
"EventEntityFeature",
|
||||
]
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
@@ -104,7 +103,7 @@ class EventExtraStoredData(ExtraStoredData):
|
||||
|
||||
|
||||
class EventEntity(RestoreEntity):
|
||||
"""Representation of a Event entity."""
|
||||
"""Representation of an Event entity."""
|
||||
|
||||
entity_description: EventEntityDescription
|
||||
_attr_device_class: EventDeviceClass | None
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"dependencies": ["http"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/generic",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["ha-av==10.1.0", "Pillow==10.0.0"]
|
||||
"requirements": ["ha-av==10.1.1", "Pillow==10.0.0"]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import Any
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .client_wrapper import CannotConnect, InvalidAuth, create_client, validate_input
|
||||
from .const import CONF_CLIENT_DEVICE_ID, DOMAIN, LOGGER, PLATFORMS
|
||||
@@ -60,3 +61,18 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_remove_config_entry_device(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
|
||||
) -> bool:
|
||||
"""Remove device from a config entry."""
|
||||
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = data.coordinators["sessions"]
|
||||
|
||||
return not device_entry.identifiers.intersection(
|
||||
(
|
||||
(DOMAIN, coordinator.server_id),
|
||||
*((DOMAIN, id) for id in coordinator.device_ids),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -47,6 +47,7 @@ class JellyfinDataUpdateCoordinator(DataUpdateCoordinator[JellyfinDataT], ABC):
|
||||
self.user_id: str = user_id
|
||||
|
||||
self.session_ids: set[str] = set()
|
||||
self.device_ids: set[str] = set()
|
||||
|
||||
async def _async_update_data(self) -> JellyfinDataT:
|
||||
"""Get the latest data from Jellyfin."""
|
||||
@@ -75,4 +76,6 @@ class SessionsDataUpdateCoordinator(
|
||||
and session["Client"] != USER_APP_NAME
|
||||
}
|
||||
|
||||
self.device_ids = {session["DeviceId"] for session in sessions_by_id.values()}
|
||||
|
||||
return sessions_by_id
|
||||
|
||||
@@ -127,7 +127,12 @@ class MediaExtractor:
|
||||
_LOGGER.error("Could not extract stream for the query: %s", query)
|
||||
raise MEQueryException() from err
|
||||
|
||||
return requested_stream["webpage_url"]
|
||||
if "formats" in requested_stream:
|
||||
best_stream = requested_stream["formats"][
|
||||
len(requested_stream["formats"]) - 1
|
||||
]
|
||||
return best_stream["url"]
|
||||
return requested_stream["url"]
|
||||
|
||||
return stream_selector
|
||||
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/nexia",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["nexia"],
|
||||
"requirements": ["nexia==2.0.6"]
|
||||
"requirements": ["nexia==2.0.7"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"dependencies": ["recorder"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/opower",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["opower==0.0.15"]
|
||||
"requirements": ["opower==0.0.16"]
|
||||
}
|
||||
|
||||
@@ -267,7 +267,19 @@ class ReolinkHost:
|
||||
async def _async_start_long_polling(self):
|
||||
"""Start ONVIF long polling task."""
|
||||
if self._long_poll_task is None:
|
||||
await self._api.subscribe(sub_type=SubType.long_poll)
|
||||
try:
|
||||
await self._api.subscribe(sub_type=SubType.long_poll)
|
||||
except ReolinkError as err:
|
||||
# make sure the long_poll_task is always created to try again later
|
||||
if not self._lost_subscription:
|
||||
self._lost_subscription = True
|
||||
_LOGGER.error(
|
||||
"Reolink %s event long polling subscription lost: %s",
|
||||
self._api.nvr_name,
|
||||
str(err),
|
||||
)
|
||||
else:
|
||||
self._lost_subscription = False
|
||||
self._long_poll_task = asyncio.create_task(self._async_long_polling())
|
||||
|
||||
async def _async_stop_long_polling(self):
|
||||
@@ -319,7 +331,13 @@ class ReolinkHost:
|
||||
try:
|
||||
await self._renew(SubType.push)
|
||||
if self._long_poll_task is not None:
|
||||
await self._renew(SubType.long_poll)
|
||||
if not self._api.subscribed(SubType.long_poll):
|
||||
_LOGGER.debug("restarting long polling task")
|
||||
# To prevent 5 minute request timeout
|
||||
await self._async_stop_long_polling()
|
||||
await self._async_start_long_polling()
|
||||
else:
|
||||
await self._renew(SubType.long_poll)
|
||||
except SubscriptionError as err:
|
||||
if not self._lost_subscription:
|
||||
self._lost_subscription = True
|
||||
@@ -457,7 +475,7 @@ class ReolinkHost:
|
||||
|
||||
self._long_poll_error = False
|
||||
|
||||
if not self._long_poll_received and channels != []:
|
||||
if not self._long_poll_received:
|
||||
self._long_poll_received = True
|
||||
ir.async_delete_issue(self._hass, DOMAIN, "webhook_url")
|
||||
|
||||
|
||||
@@ -18,5 +18,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["reolink_aio"],
|
||||
"requirements": ["reolink-aio==0.7.3"]
|
||||
"requirements": ["reolink-aio==0.7.5"]
|
||||
}
|
||||
|
||||
@@ -157,7 +157,6 @@ class ScrapeSensor(
|
||||
"""Initialize a web scrape sensor."""
|
||||
CoordinatorEntity.__init__(self, coordinator)
|
||||
ManualTriggerEntity.__init__(self, hass, trigger_entity_config)
|
||||
self._attr_name = trigger_entity_config[CONF_NAME].template
|
||||
self._attr_native_unit_of_measurement = unit_of_measurement
|
||||
self._attr_state_class = state_class
|
||||
self._select = select
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pysensibo"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pysensibo==1.0.31"]
|
||||
"requirements": ["pysensibo==1.0.32"]
|
||||
}
|
||||
|
||||
@@ -344,16 +344,15 @@ class SQLSensor(ManualTriggerEntity, SensorEntity):
|
||||
self._attr_extra_state_attributes = {}
|
||||
self._use_database_executor = use_database_executor
|
||||
self._lambda_stmt = _generate_lambda_stmt(query)
|
||||
self._attr_name = (
|
||||
None if not yaml else trigger_entity_config[CONF_NAME].template
|
||||
)
|
||||
self._attr_has_entity_name = not yaml
|
||||
if not yaml:
|
||||
self._attr_name = None
|
||||
self._attr_has_entity_name = True
|
||||
if not yaml and trigger_entity_config.get(CONF_UNIQUE_ID):
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, trigger_entity_config[CONF_UNIQUE_ID])},
|
||||
manufacturer="SQL",
|
||||
name=trigger_entity_config[CONF_NAME].template,
|
||||
name=self.name,
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["PyTurboJPEG==1.7.1", "ha-av==10.1.0", "numpy==1.23.2"]
|
||||
"requirements": ["PyTurboJPEG==1.7.1", "ha-av==10.1.1", "numpy==1.23.2"]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ from time import sleep
|
||||
from verisure import (
|
||||
Error as VerisureError,
|
||||
LoginError as VerisureLoginError,
|
||||
ResponseError as VerisureResponseError,
|
||||
Session as Verisure,
|
||||
)
|
||||
|
||||
@@ -50,7 +49,7 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
except VerisureLoginError as ex:
|
||||
LOGGER.error("Could not log in to verisure, %s", ex)
|
||||
raise ConfigEntryAuthFailed("Credentials expired for Verisure") from ex
|
||||
except VerisureResponseError as ex:
|
||||
except VerisureError as ex:
|
||||
LOGGER.error("Could not log in to verisure, %s", ex)
|
||||
return False
|
||||
|
||||
@@ -65,11 +64,9 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
try:
|
||||
await self.hass.async_add_executor_job(self.verisure.update_cookie)
|
||||
except VerisureLoginError as ex:
|
||||
LOGGER.error("Credentials expired for Verisure, %s", ex)
|
||||
raise ConfigEntryAuthFailed("Credentials expired for Verisure") from ex
|
||||
except VerisureResponseError as ex:
|
||||
LOGGER.error("Could not log in to verisure, %s", ex)
|
||||
raise ConfigEntryAuthFailed("Could not log in to verisure") from ex
|
||||
except VerisureError as ex:
|
||||
raise UpdateFailed("Unable to update cookie") from ex
|
||||
try:
|
||||
overview = await self.hass.async_add_executor_job(
|
||||
self.verisure.request,
|
||||
@@ -81,13 +78,6 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
self.verisure.smart_lock(),
|
||||
self.verisure.smartplugs(),
|
||||
)
|
||||
except VerisureResponseError as err:
|
||||
LOGGER.debug("Cookie expired or service unavailable, %s", err)
|
||||
overview = self._overview
|
||||
try:
|
||||
await self.hass.async_add_executor_job(self.verisure.update_cookie)
|
||||
except VerisureResponseError as ex:
|
||||
raise ConfigEntryAuthFailed("Credentials for Verisure expired.") from ex
|
||||
except VerisureError as err:
|
||||
LOGGER.error("Could not read overview, %s", err)
|
||||
raise UpdateFailed("Could not read overview") from err
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["verisure"],
|
||||
"requirements": ["vsure==2.6.1"]
|
||||
"requirements": ["vsure==2.6.4"]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Final
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2023
|
||||
MINOR_VERSION: Final = 8
|
||||
PATCH_VERSION: Final = "0b1"
|
||||
PATCH_VERSION: Final = "0b2"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||
|
||||
@@ -626,6 +626,11 @@ class ManualTriggerEntity(TriggerBaseEntity):
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
TriggerBaseEntity.__init__(self, hass, config)
|
||||
# Need initial rendering on `name` as it influence the `entity_id`
|
||||
self._rendered[CONF_NAME] = config[CONF_NAME].async_render(
|
||||
{},
|
||||
parse_result=CONF_NAME in self._parse_result,
|
||||
)
|
||||
|
||||
@callback
|
||||
def _process_manual_data(self, value: Any | None = None) -> None:
|
||||
|
||||
@@ -16,9 +16,9 @@ bluetooth-data-tools==1.6.1
|
||||
certifi>=2021.5.30
|
||||
ciso8601==2.3.0
|
||||
cryptography==41.0.2
|
||||
dbus-fast==1.87.2
|
||||
dbus-fast==1.87.5
|
||||
fnv-hash-fast==0.4.0
|
||||
ha-av==10.1.0
|
||||
ha-av==10.1.1
|
||||
hass-nabucasa==0.69.0
|
||||
hassil==1.2.5
|
||||
home-assistant-bluetooth==1.10.2
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2023.8.0b1"
|
||||
version = "2023.8.0b2"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
||||
@@ -632,7 +632,7 @@ datadog==0.15.0
|
||||
datapoint==0.9.8
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
dbus-fast==1.87.2
|
||||
dbus-fast==1.87.5
|
||||
|
||||
# homeassistant.components.debugpy
|
||||
debugpy==1.6.7
|
||||
@@ -937,7 +937,7 @@ h2==4.1.0
|
||||
|
||||
# homeassistant.components.generic
|
||||
# homeassistant.components.stream
|
||||
ha-av==10.1.0
|
||||
ha-av==10.1.1
|
||||
|
||||
# homeassistant.components.ffmpeg
|
||||
ha-ffmpeg==3.1.0
|
||||
@@ -1258,7 +1258,7 @@ nettigo-air-monitor==2.1.0
|
||||
neurio==0.3.1
|
||||
|
||||
# homeassistant.components.nexia
|
||||
nexia==2.0.6
|
||||
nexia==2.0.7
|
||||
|
||||
# homeassistant.components.nextcloud
|
||||
nextcloudmonitor==1.4.0
|
||||
@@ -1368,7 +1368,7 @@ openwrt-luci-rpc==1.1.16
|
||||
openwrt-ubus-rpc==0.0.2
|
||||
|
||||
# homeassistant.components.opower
|
||||
opower==0.0.15
|
||||
opower==0.0.16
|
||||
|
||||
# homeassistant.components.oralb
|
||||
oralb-ble==0.17.6
|
||||
@@ -1982,7 +1982,7 @@ pysabnzbd==1.1.1
|
||||
pysaj==0.0.16
|
||||
|
||||
# homeassistant.components.sensibo
|
||||
pysensibo==1.0.31
|
||||
pysensibo==1.0.32
|
||||
|
||||
# homeassistant.components.serial
|
||||
# homeassistant.components.zha
|
||||
@@ -2278,7 +2278,7 @@ renault-api==0.1.13
|
||||
renson-endura-delta==1.5.0
|
||||
|
||||
# homeassistant.components.reolink
|
||||
reolink-aio==0.7.3
|
||||
reolink-aio==0.7.5
|
||||
|
||||
# homeassistant.components.idteck_prox
|
||||
rfk101py==0.0.1
|
||||
@@ -2634,7 +2634,7 @@ volkszaehler==0.4.0
|
||||
volvooncall==0.10.3
|
||||
|
||||
# homeassistant.components.verisure
|
||||
vsure==2.6.1
|
||||
vsure==2.6.4
|
||||
|
||||
# homeassistant.components.vasttrafik
|
||||
vtjp==0.1.14
|
||||
|
||||
@@ -515,7 +515,7 @@ datadog==0.15.0
|
||||
datapoint==0.9.8
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
dbus-fast==1.87.2
|
||||
dbus-fast==1.87.5
|
||||
|
||||
# homeassistant.components.debugpy
|
||||
debugpy==1.6.7
|
||||
@@ -735,7 +735,7 @@ h2==4.1.0
|
||||
|
||||
# homeassistant.components.generic
|
||||
# homeassistant.components.stream
|
||||
ha-av==10.1.0
|
||||
ha-av==10.1.1
|
||||
|
||||
# homeassistant.components.ffmpeg
|
||||
ha-ffmpeg==3.1.0
|
||||
@@ -966,7 +966,7 @@ netmap==0.7.0.2
|
||||
nettigo-air-monitor==2.1.0
|
||||
|
||||
# homeassistant.components.nexia
|
||||
nexia==2.0.6
|
||||
nexia==2.0.7
|
||||
|
||||
# homeassistant.components.nextcloud
|
||||
nextcloudmonitor==1.4.0
|
||||
@@ -1037,7 +1037,7 @@ openerz-api==0.2.0
|
||||
openhomedevice==2.2.0
|
||||
|
||||
# homeassistant.components.opower
|
||||
opower==0.0.15
|
||||
opower==0.0.16
|
||||
|
||||
# homeassistant.components.oralb
|
||||
oralb-ble==0.17.6
|
||||
@@ -1474,7 +1474,7 @@ pyrympro==0.0.7
|
||||
pysabnzbd==1.1.1
|
||||
|
||||
# homeassistant.components.sensibo
|
||||
pysensibo==1.0.31
|
||||
pysensibo==1.0.32
|
||||
|
||||
# homeassistant.components.serial
|
||||
# homeassistant.components.zha
|
||||
@@ -1674,7 +1674,7 @@ renault-api==0.1.13
|
||||
renson-endura-delta==1.5.0
|
||||
|
||||
# homeassistant.components.reolink
|
||||
reolink-aio==0.7.3
|
||||
reolink-aio==0.7.5
|
||||
|
||||
# homeassistant.components.rflink
|
||||
rflink==0.0.65
|
||||
@@ -1934,7 +1934,7 @@ voip-utils==0.1.0
|
||||
volvooncall==0.10.3
|
||||
|
||||
# homeassistant.components.verisure
|
||||
vsure==2.6.1
|
||||
vsure==2.6.4
|
||||
|
||||
# homeassistant.components.vulcan
|
||||
vulcan-api==2.3.0
|
||||
|
||||
@@ -4,10 +4,29 @@ from unittest.mock import MagicMock
|
||||
from homeassistant.components.jellyfin.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import async_load_json_fixture
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.typing import MockHAClientWebSocket, WebSocketGenerator
|
||||
|
||||
|
||||
async def remove_device(
|
||||
ws_client: MockHAClientWebSocket, device_id: str, config_entry_id: str
|
||||
) -> bool:
|
||||
"""Remove config entry from a device."""
|
||||
await ws_client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": "config/device_registry/remove_config_entry",
|
||||
"config_entry_id": config_entry_id,
|
||||
"device_id": device_id,
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
return response["success"]
|
||||
|
||||
|
||||
async def test_config_entry_not_ready(
|
||||
@@ -66,3 +85,44 @@ async def test_load_unload_config_entry(
|
||||
await hass.async_block_till_done()
|
||||
assert mock_config_entry.entry_id not in hass.data[DOMAIN]
|
||||
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_device_remove_devices(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_jellyfin: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test we can only remove a device that no longer exists."""
|
||||
assert await async_setup_component(hass, "config", {})
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device_entry = device_registry.async_get_device(
|
||||
identifiers={
|
||||
(
|
||||
DOMAIN,
|
||||
"DEVICE-UUID",
|
||||
)
|
||||
},
|
||||
)
|
||||
assert (
|
||||
await remove_device(
|
||||
await hass_ws_client(hass), device_entry.id, mock_config_entry.entry_id
|
||||
)
|
||||
is False
|
||||
)
|
||||
old_device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=mock_config_entry.entry_id,
|
||||
identifiers={(DOMAIN, "OLD-DEVICE-UUID")},
|
||||
)
|
||||
assert (
|
||||
await remove_device(
|
||||
await hass_ws_client(hass), old_device_entry.id, mock_config_entry.entry_id
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user