mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
2024.12.1 (#132509)
This commit is contained in:
commit
cf53a9743f
@ -8,6 +8,6 @@
|
|||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["hass_nabucasa"],
|
"loggers": ["hass_nabucasa"],
|
||||||
"requirements": ["hass-nabucasa==0.85.0"],
|
"requirements": ["hass-nabucasa==0.86.0"],
|
||||||
"single_config_entry": true
|
"single_config_entry": true
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pydeako.deako import Deako, DeviceListTimeout, FindDevicesTimeout
|
from pydeako import Deako, DeakoDiscoverer, FindDevicesError
|
||||||
from pydeako.discover import DeakoDiscoverer
|
|
||||||
|
|
||||||
from homeassistant.components import zeroconf
|
from homeassistant.components import zeroconf
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -30,12 +29,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: DeakoConfigEntry) -> boo
|
|||||||
await connection.connect()
|
await connection.connect()
|
||||||
try:
|
try:
|
||||||
await connection.find_devices()
|
await connection.find_devices()
|
||||||
except DeviceListTimeout as exc: # device list never received
|
except FindDevicesError as exc:
|
||||||
_LOGGER.warning("Device not responding to device list")
|
_LOGGER.warning("Error finding devices: %s", exc)
|
||||||
await connection.disconnect()
|
|
||||||
raise ConfigEntryNotReady(exc) from exc
|
|
||||||
except FindDevicesTimeout as exc: # total devices expected not received
|
|
||||||
_LOGGER.warning("Device not responding to device requests")
|
|
||||||
await connection.disconnect()
|
await connection.disconnect()
|
||||||
raise ConfigEntryNotReady(exc) from exc
|
raise ConfigEntryNotReady(exc) from exc
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Config flow for deako."""
|
"""Config flow for deako."""
|
||||||
|
|
||||||
from pydeako.discover import DeakoDiscoverer, DevicesNotFoundException
|
from pydeako import DeakoDiscoverer, DevicesNotFoundException
|
||||||
|
|
||||||
from homeassistant.components import zeroconf
|
from homeassistant.components import zeroconf
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pydeako.deako import Deako
|
from pydeako import Deako
|
||||||
|
|
||||||
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
|
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/deako",
|
"documentation": "https://www.home-assistant.io/integrations/deako",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pydeako"],
|
"loggers": ["pydeako"],
|
||||||
"requirements": ["pydeako==0.5.4"],
|
"requirements": ["pydeako==0.6.0"],
|
||||||
"single_config_entry": true,
|
"single_config_entry": true,
|
||||||
"zeroconf": ["_deako._tcp.local."]
|
"zeroconf": ["_deako._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -99,8 +99,8 @@ class EcovacsController:
|
|||||||
for device_config in devices.not_supported:
|
for device_config in devices.not_supported:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
(
|
(
|
||||||
'Device "%s" not supported. Please add support for it to '
|
'Device "%s" not supported. More information at '
|
||||||
"https://github.com/DeebotUniverse/client.py: %s"
|
"https://github.com/DeebotUniverse/client.py/issues/612: %s"
|
||||||
),
|
),
|
||||||
device_config["deviceName"],
|
device_config["deviceName"],
|
||||||
device_config,
|
device_config,
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
||||||
"requirements": ["py-sucks==0.9.10", "deebot-client==9.1.0"]
|
"requirements": ["py-sucks==0.9.10", "deebot-client==9.2.0"]
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ def check_local_version_supported(api_version: str | None) -> bool:
|
|||||||
class DirectPanel(PanelEntry):
|
class DirectPanel(PanelEntry):
|
||||||
"""Helper class for wrapping a directly accessed Elmax Panel."""
|
"""Helper class for wrapping a directly accessed Elmax Panel."""
|
||||||
|
|
||||||
def __init__(self, panel_uri):
|
def __init__(self, panel_uri) -> None:
|
||||||
"""Construct the object."""
|
"""Construct the object."""
|
||||||
super().__init__(panel_uri, True, {})
|
super().__init__(panel_uri, True, {})
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_direct(self, user_input: dict[str, Any]) -> ConfigFlowResult:
|
async def async_step_direct(self, user_input: dict[str, Any]) -> ConfigFlowResult:
|
||||||
"""Handle the direct setup step."""
|
"""Handle the direct setup step."""
|
||||||
self._selected_mode = CONF_ELMAX_MODE_CLOUD
|
self._selected_mode = CONF_ELMAX_MODE_DIRECT
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id=CONF_ELMAX_MODE_DIRECT,
|
step_id=CONF_ELMAX_MODE_DIRECT,
|
||||||
|
@ -121,13 +121,13 @@ class ElmaxCover(ElmaxEntity, CoverEntity):
|
|||||||
else:
|
else:
|
||||||
_LOGGER.debug("Ignoring stop request as the cover is IDLE")
|
_LOGGER.debug("Ignoring stop request as the cover is IDLE")
|
||||||
|
|
||||||
async def async_open_cover(self, **kwargs):
|
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||||
"""Open the cover."""
|
"""Open the cover."""
|
||||||
await self.coordinator.http_client.execute_command(
|
await self.coordinator.http_client.execute_command(
|
||||||
endpoint_id=self._device.endpoint_id, command=CoverCommand.UP
|
endpoint_id=self._device.endpoint_id, command=CoverCommand.UP
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_close_cover(self, **kwargs):
|
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||||
"""Close the cover."""
|
"""Close the cover."""
|
||||||
await self.coordinator.http_client.execute_command(
|
await self.coordinator.http_client.execute_command(
|
||||||
endpoint_id=self._device.endpoint_id, command=CoverCommand.DOWN
|
endpoint_id=self._device.endpoint_id, command=CoverCommand.DOWN
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/elmax",
|
"documentation": "https://www.home-assistant.io/integrations/elmax",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["elmax_api"],
|
"loggers": ["elmax_api"],
|
||||||
"requirements": ["elmax-api==0.0.6.1"],
|
"requirements": ["elmax-api==0.0.6.3"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_elmax-ssl._tcp.local."
|
"type": "_elmax-ssl._tcp.local."
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
|
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
|
||||||
"mqtt": ["esphome/discover/#"],
|
"mqtt": ["esphome/discover/#"],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aioesphomeapi==27.0.3",
|
"aioesphomeapi==28.0.0",
|
||||||
"esphome-dashboard-api==1.2.3",
|
"esphome-dashboard-api==1.2.3",
|
||||||
"bleak-esphome==1.1.0"
|
"bleak-esphome==1.1.0"
|
||||||
],
|
],
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["home-assistant-frontend==20241127.4"]
|
"requirements": ["home-assistant-frontend==20241127.6"]
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import UTC, date, datetime, timedelta
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from homeassistant.components.todo import (
|
from homeassistant.components.todo import (
|
||||||
@ -39,8 +39,10 @@ def _convert_todo_item(item: TodoItem) -> dict[str, str | None]:
|
|||||||
else:
|
else:
|
||||||
result["status"] = TodoItemStatus.NEEDS_ACTION
|
result["status"] = TodoItemStatus.NEEDS_ACTION
|
||||||
if (due := item.due) is not None:
|
if (due := item.due) is not None:
|
||||||
# due API field is a timestamp string, but with only date resolution
|
# due API field is a timestamp string, but with only date resolution.
|
||||||
result["due"] = dt_util.start_of_local_day(due).isoformat()
|
# The time portion of the date is always discarded by the API, so we
|
||||||
|
# always set to UTC.
|
||||||
|
result["due"] = dt_util.start_of_local_day(due).replace(tzinfo=UTC).isoformat()
|
||||||
else:
|
else:
|
||||||
result["due"] = None
|
result["due"] = None
|
||||||
result["notes"] = item.description
|
result["notes"] = item.description
|
||||||
@ -51,6 +53,8 @@ def _convert_api_item(item: dict[str, str]) -> TodoItem:
|
|||||||
"""Convert tasks API items into a TodoItem."""
|
"""Convert tasks API items into a TodoItem."""
|
||||||
due: date | None = None
|
due: date | None = None
|
||||||
if (due_str := item.get("due")) is not None:
|
if (due_str := item.get("due")) is not None:
|
||||||
|
# Due dates are returned always in UTC so we only need to
|
||||||
|
# parse the date portion which will be interpreted as a a local date.
|
||||||
due = datetime.fromisoformat(due_str).date()
|
due = datetime.fromisoformat(due_str).date()
|
||||||
return TodoItem(
|
return TodoItem(
|
||||||
summary=item["title"],
|
summary=item["title"],
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from aiohttp.hdrs import CACHE_CONTROL, CONTENT_TYPE
|
from aiohttp.hdrs import CACHE_CONTROL, CONTENT_TYPE
|
||||||
@ -17,6 +18,15 @@ CACHE_HEADER = f"public, max-age={CACHE_TIME}"
|
|||||||
CACHE_HEADERS: Mapping[str, str] = {CACHE_CONTROL: CACHE_HEADER}
|
CACHE_HEADERS: Mapping[str, str] = {CACHE_CONTROL: CACHE_HEADER}
|
||||||
RESPONSE_CACHE: LRU[tuple[str, Path], tuple[Path, str]] = LRU(512)
|
RESPONSE_CACHE: LRU[tuple[str, Path], tuple[Path, str]] = LRU(512)
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 13):
|
||||||
|
# guess_type is soft-deprecated in 3.13
|
||||||
|
# for paths and should only be used for
|
||||||
|
# URLs. guess_file_type should be used
|
||||||
|
# for paths instead.
|
||||||
|
_GUESSER = CONTENT_TYPES.guess_file_type
|
||||||
|
else:
|
||||||
|
_GUESSER = CONTENT_TYPES.guess_type
|
||||||
|
|
||||||
|
|
||||||
class CachingStaticResource(StaticResource):
|
class CachingStaticResource(StaticResource):
|
||||||
"""Static Resource handler that will add cache headers."""
|
"""Static Resource handler that will add cache headers."""
|
||||||
@ -37,9 +47,7 @@ class CachingStaticResource(StaticResource):
|
|||||||
# Must be directory index; ignore caching
|
# Must be directory index; ignore caching
|
||||||
return response
|
return response
|
||||||
file_path = response._path # noqa: SLF001
|
file_path = response._path # noqa: SLF001
|
||||||
response.content_type = (
|
response.content_type = _GUESSER(file_path)[0] or FALLBACK_CONTENT_TYPE
|
||||||
CONTENT_TYPES.guess_type(file_path)[0] or FALLBACK_CONTENT_TYPE
|
|
||||||
)
|
|
||||||
# Cache actual header after setter construction.
|
# Cache actual header after setter construction.
|
||||||
content_type = response.headers[CONTENT_TYPE]
|
content_type = response.headers[CONTENT_TYPE]
|
||||||
RESPONSE_CACHE[key] = (file_path, content_type)
|
RESPONSE_CACHE[key] = (file_path, content_type)
|
||||||
|
@ -27,7 +27,9 @@ from .entity import NordpoolBaseEntity
|
|||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
|
|
||||||
def get_prices(data: DeliveryPeriodData) -> dict[str, tuple[float, float, float]]:
|
def get_prices(
|
||||||
|
data: DeliveryPeriodData,
|
||||||
|
) -> dict[str, tuple[float | None, float, float | None]]:
|
||||||
"""Return previous, current and next prices.
|
"""Return previous, current and next prices.
|
||||||
|
|
||||||
Output: {"SE3": (10.0, 10.5, 12.1)}
|
Output: {"SE3": (10.0, 10.5, 12.1)}
|
||||||
@ -39,6 +41,7 @@ def get_prices(data: DeliveryPeriodData) -> dict[str, tuple[float, float, float]
|
|||||||
previous_time = current_time - timedelta(hours=1)
|
previous_time = current_time - timedelta(hours=1)
|
||||||
next_time = current_time + timedelta(hours=1)
|
next_time = current_time + timedelta(hours=1)
|
||||||
price_data = data.entries
|
price_data = data.entries
|
||||||
|
LOGGER.debug("Price data: %s", price_data)
|
||||||
for entry in price_data:
|
for entry in price_data:
|
||||||
if entry.start <= current_time <= entry.end:
|
if entry.start <= current_time <= entry.end:
|
||||||
current_price_entries = entry.entry
|
current_price_entries = entry.entry
|
||||||
@ -46,10 +49,20 @@ def get_prices(data: DeliveryPeriodData) -> dict[str, tuple[float, float, float]
|
|||||||
last_price_entries = entry.entry
|
last_price_entries = entry.entry
|
||||||
if entry.start <= next_time <= entry.end:
|
if entry.start <= next_time <= entry.end:
|
||||||
next_price_entries = entry.entry
|
next_price_entries = entry.entry
|
||||||
|
LOGGER.debug(
|
||||||
|
"Last price %s, current price %s, next price %s",
|
||||||
|
last_price_entries,
|
||||||
|
current_price_entries,
|
||||||
|
next_price_entries,
|
||||||
|
)
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
for area, price in current_price_entries.items():
|
for area, price in current_price_entries.items():
|
||||||
result[area] = (last_price_entries[area], price, next_price_entries[area])
|
result[area] = (
|
||||||
|
last_price_entries.get(area),
|
||||||
|
price,
|
||||||
|
next_price_entries.get(area),
|
||||||
|
)
|
||||||
LOGGER.debug("Prices: %s", result)
|
LOGGER.debug("Prices: %s", result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -90,7 +103,7 @@ class NordpoolDefaultSensorEntityDescription(SensorEntityDescription):
|
|||||||
class NordpoolPricesSensorEntityDescription(SensorEntityDescription):
|
class NordpoolPricesSensorEntityDescription(SensorEntityDescription):
|
||||||
"""Describes Nord Pool prices sensor entity."""
|
"""Describes Nord Pool prices sensor entity."""
|
||||||
|
|
||||||
value_fn: Callable[[tuple[float, float, float]], float | None]
|
value_fn: Callable[[tuple[float | None, float, float | None]], float | None]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
@ -136,13 +149,13 @@ PRICES_SENSOR_TYPES: tuple[NordpoolPricesSensorEntityDescription, ...] = (
|
|||||||
NordpoolPricesSensorEntityDescription(
|
NordpoolPricesSensorEntityDescription(
|
||||||
key="last_price",
|
key="last_price",
|
||||||
translation_key="last_price",
|
translation_key="last_price",
|
||||||
value_fn=lambda data: data[0] / 1000,
|
value_fn=lambda data: data[0] / 1000 if data[0] else None,
|
||||||
suggested_display_precision=2,
|
suggested_display_precision=2,
|
||||||
),
|
),
|
||||||
NordpoolPricesSensorEntityDescription(
|
NordpoolPricesSensorEntityDescription(
|
||||||
key="next_price",
|
key="next_price",
|
||||||
translation_key="next_price",
|
translation_key="next_price",
|
||||||
value_fn=lambda data: data[2] / 1000,
|
value_fn=lambda data: data[2] / 1000 if data[2] else None,
|
||||||
suggested_display_precision=2,
|
suggested_display_precision=2,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -480,7 +480,13 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = {
|
|||||||
NumberDeviceClass.PM10: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
NumberDeviceClass.PM10: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
||||||
NumberDeviceClass.PM25: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
NumberDeviceClass.PM25: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
||||||
NumberDeviceClass.POWER_FACTOR: {PERCENTAGE, None},
|
NumberDeviceClass.POWER_FACTOR: {PERCENTAGE, None},
|
||||||
NumberDeviceClass.POWER: {UnitOfPower.WATT, UnitOfPower.KILO_WATT},
|
NumberDeviceClass.POWER: {
|
||||||
|
UnitOfPower.WATT,
|
||||||
|
UnitOfPower.KILO_WATT,
|
||||||
|
UnitOfPower.MEGA_WATT,
|
||||||
|
UnitOfPower.GIGA_WATT,
|
||||||
|
UnitOfPower.TERA_WATT,
|
||||||
|
},
|
||||||
NumberDeviceClass.PRECIPITATION: set(UnitOfPrecipitationDepth),
|
NumberDeviceClass.PRECIPITATION: set(UnitOfPrecipitationDepth),
|
||||||
NumberDeviceClass.PRECIPITATION_INTENSITY: set(UnitOfVolumetricFlux),
|
NumberDeviceClass.PRECIPITATION_INTENSITY: set(UnitOfVolumetricFlux),
|
||||||
NumberDeviceClass.PRESSURE: set(UnitOfPressure),
|
NumberDeviceClass.PRESSURE: set(UnitOfPressure),
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
"requirements": [
|
"requirements": [
|
||||||
"getmac==0.9.4",
|
"getmac==0.9.4",
|
||||||
"samsungctl[websocket]==0.7.1",
|
"samsungctl[websocket]==0.7.1",
|
||||||
"samsungtvws[async,encrypted]==2.7.1",
|
"samsungtvws[async,encrypted]==2.7.2",
|
||||||
"wakeonlan==2.1.0",
|
"wakeonlan==2.1.0",
|
||||||
"async-upnp-client==0.41.0"
|
"async-upnp-client==0.41.0"
|
||||||
],
|
],
|
||||||
|
@ -579,7 +579,13 @@ DEVICE_CLASS_UNITS: dict[SensorDeviceClass, set[type[StrEnum] | str | None]] = {
|
|||||||
SensorDeviceClass.PM10: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
SensorDeviceClass.PM10: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
||||||
SensorDeviceClass.PM25: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
SensorDeviceClass.PM25: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
||||||
SensorDeviceClass.POWER_FACTOR: {PERCENTAGE, None},
|
SensorDeviceClass.POWER_FACTOR: {PERCENTAGE, None},
|
||||||
SensorDeviceClass.POWER: {UnitOfPower.WATT, UnitOfPower.KILO_WATT},
|
SensorDeviceClass.POWER: {
|
||||||
|
UnitOfPower.WATT,
|
||||||
|
UnitOfPower.KILO_WATT,
|
||||||
|
UnitOfPower.MEGA_WATT,
|
||||||
|
UnitOfPower.GIGA_WATT,
|
||||||
|
UnitOfPower.TERA_WATT,
|
||||||
|
},
|
||||||
SensorDeviceClass.PRECIPITATION: set(UnitOfPrecipitationDepth),
|
SensorDeviceClass.PRECIPITATION: set(UnitOfPrecipitationDepth),
|
||||||
SensorDeviceClass.PRECIPITATION_INTENSITY: set(UnitOfVolumetricFlux),
|
SensorDeviceClass.PRECIPITATION_INTENSITY: set(UnitOfVolumetricFlux),
|
||||||
SensorDeviceClass.PRESSURE: set(UnitOfPressure),
|
SensorDeviceClass.PRESSURE: set(UnitOfPressure),
|
||||||
|
@ -21,6 +21,7 @@ SCOPES = [
|
|||||||
Scope.OPENID,
|
Scope.OPENID,
|
||||||
Scope.OFFLINE_ACCESS,
|
Scope.OFFLINE_ACCESS,
|
||||||
Scope.VEHICLE_DEVICE_DATA,
|
Scope.VEHICLE_DEVICE_DATA,
|
||||||
|
Scope.VEHICLE_LOCATION,
|
||||||
Scope.VEHICLE_CMDS,
|
Scope.VEHICLE_CMDS,
|
||||||
Scope.VEHICLE_CHARGING_CMDS,
|
Scope.VEHICLE_CHARGING_CMDS,
|
||||||
Scope.ENERGY_DEVICE_DATA,
|
Scope.ENERGY_DEVICE_DATA,
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/tesla_fleet",
|
"documentation": "https://www.home-assistant.io/integrations/tesla_fleet",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["tesla-fleet-api"],
|
"loggers": ["tesla-fleet-api"],
|
||||||
"requirements": ["tesla-fleet-api==0.8.4"]
|
"requirements": ["tesla-fleet-api==0.8.5"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/teslemetry",
|
"documentation": "https://www.home-assistant.io/integrations/teslemetry",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["tesla-fleet-api"],
|
"loggers": ["tesla-fleet-api"],
|
||||||
"requirements": ["tesla-fleet-api==0.8.4", "teslemetry-stream==0.4.2"]
|
"requirements": ["tesla-fleet-api==0.8.5", "teslemetry-stream==0.4.2"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/tessie",
|
"documentation": "https://www.home-assistant.io/integrations/tessie",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["tessie", "tesla-fleet-api"],
|
"loggers": ["tessie", "tesla-fleet-api"],
|
||||||
"requirements": ["tessie-api==0.1.1", "tesla-fleet-api==0.8.4"]
|
"requirements": ["tessie-api==0.1.1", "tesla-fleet-api==0.8.5"]
|
||||||
}
|
}
|
||||||
|
@ -300,5 +300,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/tplink",
|
"documentation": "https://www.home-assistant.io/integrations/tplink",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["kasa"],
|
"loggers": ["kasa"],
|
||||||
"requirements": ["python-kasa[speedups]==0.8.0"]
|
"requirements": ["python-kasa[speedups]==0.8.1"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/upb",
|
"documentation": "https://www.home-assistant.io/integrations/upb",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["upb_lib"],
|
"loggers": ["upb_lib"],
|
||||||
"requirements": ["upb-lib==0.5.8"]
|
"requirements": ["upb-lib==0.5.9"]
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"""Support for tracking consumption over given periods of time."""
|
"""Support for tracking consumption over given periods of time."""
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from croniter import croniter
|
from cronsim import CronSim, CronSimError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
||||||
@ -47,9 +47,12 @@ DEFAULT_OFFSET = timedelta(hours=0)
|
|||||||
|
|
||||||
def validate_cron_pattern(pattern):
|
def validate_cron_pattern(pattern):
|
||||||
"""Check that the pattern is well-formed."""
|
"""Check that the pattern is well-formed."""
|
||||||
if croniter.is_valid(pattern):
|
try:
|
||||||
return pattern
|
CronSim(pattern, datetime(2020, 1, 1)) # any date will do
|
||||||
raise vol.Invalid("Invalid pattern")
|
except CronSimError as err:
|
||||||
|
_LOGGER.error("Invalid cron pattern %s: %s", pattern, err)
|
||||||
|
raise vol.Invalid("Invalid pattern") from err
|
||||||
|
return pattern
|
||||||
|
|
||||||
|
|
||||||
def period_or_cron(config):
|
def period_or_cron(config):
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/utility_meter",
|
"documentation": "https://www.home-assistant.io/integrations/utility_meter",
|
||||||
"integration_type": "helper",
|
"integration_type": "helper",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["croniter"],
|
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["cronsim==2.6"]
|
"requirements": ["cronsim==2.6"]
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2024
|
MAJOR_VERSION: Final = 2024
|
||||||
MINOR_VERSION: Final = 12
|
MINOR_VERSION: Final = 12
|
||||||
PATCH_VERSION: Final = "0"
|
PATCH_VERSION: Final = "1"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||||
|
@ -71,7 +71,10 @@ async def async_get_system_info(hass: HomeAssistant) -> dict[str, Any]:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
info_object["user"] = cached_get_user()
|
info_object["user"] = cached_get_user()
|
||||||
except KeyError:
|
except (KeyError, OSError):
|
||||||
|
# OSError on python >= 3.13, KeyError on python < 3.13
|
||||||
|
# KeyError can be removed when 3.12 support is dropped
|
||||||
|
# see https://docs.python.org/3/whatsnew/3.13.html
|
||||||
info_object["user"] = None
|
info_object["user"] = None
|
||||||
|
|
||||||
if platform.system() == "Darwin":
|
if platform.system() == "Darwin":
|
||||||
|
@ -5,7 +5,7 @@ aiodiscover==2.1.0
|
|||||||
aiodns==3.2.0
|
aiodns==3.2.0
|
||||||
aiohasupervisor==0.2.1
|
aiohasupervisor==0.2.1
|
||||||
aiohttp-fast-zlib==0.2.0
|
aiohttp-fast-zlib==0.2.0
|
||||||
aiohttp==3.11.9
|
aiohttp==3.11.10
|
||||||
aiohttp_cors==0.7.0
|
aiohttp_cors==0.7.0
|
||||||
aiozoneinfo==0.2.1
|
aiozoneinfo==0.2.1
|
||||||
astral==2.2
|
astral==2.2
|
||||||
@ -31,10 +31,10 @@ fnv-hash-fast==1.0.2
|
|||||||
go2rtc-client==0.1.1
|
go2rtc-client==0.1.1
|
||||||
ha-ffmpeg==3.2.2
|
ha-ffmpeg==3.2.2
|
||||||
habluetooth==3.6.0
|
habluetooth==3.6.0
|
||||||
hass-nabucasa==0.85.0
|
hass-nabucasa==0.86.0
|
||||||
hassil==2.0.5
|
hassil==2.0.5
|
||||||
home-assistant-bluetooth==1.13.0
|
home-assistant-bluetooth==1.13.0
|
||||||
home-assistant-frontend==20241127.4
|
home-assistant-frontend==20241127.6
|
||||||
home-assistant-intents==2024.12.4
|
home-assistant-intents==2024.12.4
|
||||||
httpx==0.27.2
|
httpx==0.27.2
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2024.12.0"
|
version = "2024.12.1"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
@ -29,7 +29,7 @@ dependencies = [
|
|||||||
# change behavior based on presence of supervisor. Deprecated with #127228
|
# change behavior based on presence of supervisor. Deprecated with #127228
|
||||||
# Lib can be removed with 2025.11
|
# Lib can be removed with 2025.11
|
||||||
"aiohasupervisor==0.2.1",
|
"aiohasupervisor==0.2.1",
|
||||||
"aiohttp==3.11.9",
|
"aiohttp==3.11.10",
|
||||||
"aiohttp_cors==0.7.0",
|
"aiohttp_cors==0.7.0",
|
||||||
"aiohttp-fast-zlib==0.2.0",
|
"aiohttp-fast-zlib==0.2.0",
|
||||||
"aiozoneinfo==0.2.1",
|
"aiozoneinfo==0.2.1",
|
||||||
@ -45,7 +45,7 @@ dependencies = [
|
|||||||
"fnv-hash-fast==1.0.2",
|
"fnv-hash-fast==1.0.2",
|
||||||
# hass-nabucasa is imported by helpers which don't depend on the cloud
|
# hass-nabucasa is imported by helpers which don't depend on the cloud
|
||||||
# integration
|
# integration
|
||||||
"hass-nabucasa==0.85.0",
|
"hass-nabucasa==0.86.0",
|
||||||
# When bumping httpx, please check the version pins of
|
# When bumping httpx, please check the version pins of
|
||||||
# httpcore, anyio, and h11 in gen_requirements_all
|
# httpcore, anyio, and h11 in gen_requirements_all
|
||||||
"httpx==0.27.2",
|
"httpx==0.27.2",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
# Home Assistant Core
|
# Home Assistant Core
|
||||||
aiodns==3.2.0
|
aiodns==3.2.0
|
||||||
aiohasupervisor==0.2.1
|
aiohasupervisor==0.2.1
|
||||||
aiohttp==3.11.9
|
aiohttp==3.11.10
|
||||||
aiohttp_cors==0.7.0
|
aiohttp_cors==0.7.0
|
||||||
aiohttp-fast-zlib==0.2.0
|
aiohttp-fast-zlib==0.2.0
|
||||||
aiozoneinfo==0.2.1
|
aiozoneinfo==0.2.1
|
||||||
@ -19,7 +19,7 @@ bcrypt==4.2.0
|
|||||||
certifi>=2021.5.30
|
certifi>=2021.5.30
|
||||||
ciso8601==2.3.1
|
ciso8601==2.3.1
|
||||||
fnv-hash-fast==1.0.2
|
fnv-hash-fast==1.0.2
|
||||||
hass-nabucasa==0.85.0
|
hass-nabucasa==0.86.0
|
||||||
httpx==0.27.2
|
httpx==0.27.2
|
||||||
home-assistant-bluetooth==1.13.0
|
home-assistant-bluetooth==1.13.0
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
|
@ -243,7 +243,7 @@ aioelectricitymaps==0.4.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==27.0.3
|
aioesphomeapi==28.0.0
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
@ -738,7 +738,7 @@ debugpy==1.8.6
|
|||||||
# decora==0.6
|
# decora==0.6
|
||||||
|
|
||||||
# homeassistant.components.ecovacs
|
# homeassistant.components.ecovacs
|
||||||
deebot-client==9.1.0
|
deebot-client==9.2.0
|
||||||
|
|
||||||
# homeassistant.components.ihc
|
# homeassistant.components.ihc
|
||||||
# homeassistant.components.namecheapdns
|
# homeassistant.components.namecheapdns
|
||||||
@ -824,7 +824,7 @@ eliqonline==1.2.2
|
|||||||
elkm1-lib==2.2.10
|
elkm1-lib==2.2.10
|
||||||
|
|
||||||
# homeassistant.components.elmax
|
# homeassistant.components.elmax
|
||||||
elmax-api==0.0.6.1
|
elmax-api==0.0.6.3
|
||||||
|
|
||||||
# homeassistant.components.elvia
|
# homeassistant.components.elvia
|
||||||
elvia==0.1.0
|
elvia==0.1.0
|
||||||
@ -1090,7 +1090,7 @@ habitipy==0.3.3
|
|||||||
habluetooth==3.6.0
|
habluetooth==3.6.0
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.85.0
|
hass-nabucasa==0.86.0
|
||||||
|
|
||||||
# homeassistant.components.splunk
|
# homeassistant.components.splunk
|
||||||
hass-splunk==0.1.1
|
hass-splunk==0.1.1
|
||||||
@ -1130,7 +1130,7 @@ hole==0.8.0
|
|||||||
holidays==0.62
|
holidays==0.62
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20241127.4
|
home-assistant-frontend==20241127.6
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2024.12.4
|
home-assistant-intents==2024.12.4
|
||||||
@ -1841,7 +1841,7 @@ pydaikin==2.13.7
|
|||||||
pydanfossair==0.1.0
|
pydanfossair==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.deako
|
# homeassistant.components.deako
|
||||||
pydeako==0.5.4
|
pydeako==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.deconz
|
# homeassistant.components.deconz
|
||||||
pydeconz==118
|
pydeconz==118
|
||||||
@ -2362,7 +2362,7 @@ python-join-api==0.0.9
|
|||||||
python-juicenet==1.1.0
|
python-juicenet==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.tplink
|
# homeassistant.components.tplink
|
||||||
python-kasa[speedups]==0.8.0
|
python-kasa[speedups]==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.linkplay
|
# homeassistant.components.linkplay
|
||||||
python-linkplay==0.0.20
|
python-linkplay==0.0.20
|
||||||
@ -2610,7 +2610,7 @@ rxv==0.7.0
|
|||||||
samsungctl[websocket]==0.7.1
|
samsungctl[websocket]==0.7.1
|
||||||
|
|
||||||
# homeassistant.components.samsungtv
|
# homeassistant.components.samsungtv
|
||||||
samsungtvws[async,encrypted]==2.7.1
|
samsungtvws[async,encrypted]==2.7.2
|
||||||
|
|
||||||
# homeassistant.components.sanix
|
# homeassistant.components.sanix
|
||||||
sanix==1.0.6
|
sanix==1.0.6
|
||||||
@ -2810,7 +2810,7 @@ temperusb==1.6.1
|
|||||||
# homeassistant.components.tesla_fleet
|
# homeassistant.components.tesla_fleet
|
||||||
# homeassistant.components.teslemetry
|
# homeassistant.components.teslemetry
|
||||||
# homeassistant.components.tessie
|
# homeassistant.components.tessie
|
||||||
tesla-fleet-api==0.8.4
|
tesla-fleet-api==0.8.5
|
||||||
|
|
||||||
# homeassistant.components.powerwall
|
# homeassistant.components.powerwall
|
||||||
tesla-powerwall==0.5.2
|
tesla-powerwall==0.5.2
|
||||||
@ -2915,7 +2915,7 @@ unifiled==0.11
|
|||||||
universal-silabs-flasher==0.0.25
|
universal-silabs-flasher==0.0.25
|
||||||
|
|
||||||
# homeassistant.components.upb
|
# homeassistant.components.upb
|
||||||
upb-lib==0.5.8
|
upb-lib==0.5.9
|
||||||
|
|
||||||
# homeassistant.components.upcloud
|
# homeassistant.components.upcloud
|
||||||
upcloud-api==2.6.0
|
upcloud-api==2.6.0
|
||||||
|
@ -231,7 +231,7 @@ aioelectricitymaps==0.4.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==27.0.3
|
aioesphomeapi==28.0.0
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
@ -628,7 +628,7 @@ dbus-fast==2.24.3
|
|||||||
debugpy==1.8.6
|
debugpy==1.8.6
|
||||||
|
|
||||||
# homeassistant.components.ecovacs
|
# homeassistant.components.ecovacs
|
||||||
deebot-client==9.1.0
|
deebot-client==9.2.0
|
||||||
|
|
||||||
# homeassistant.components.ihc
|
# homeassistant.components.ihc
|
||||||
# homeassistant.components.namecheapdns
|
# homeassistant.components.namecheapdns
|
||||||
@ -699,7 +699,7 @@ elgato==5.1.2
|
|||||||
elkm1-lib==2.2.10
|
elkm1-lib==2.2.10
|
||||||
|
|
||||||
# homeassistant.components.elmax
|
# homeassistant.components.elmax
|
||||||
elmax-api==0.0.6.1
|
elmax-api==0.0.6.3
|
||||||
|
|
||||||
# homeassistant.components.elvia
|
# homeassistant.components.elvia
|
||||||
elvia==0.1.0
|
elvia==0.1.0
|
||||||
@ -928,7 +928,7 @@ habitipy==0.3.3
|
|||||||
habluetooth==3.6.0
|
habluetooth==3.6.0
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.85.0
|
hass-nabucasa==0.86.0
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
hassil==2.0.5
|
hassil==2.0.5
|
||||||
@ -956,7 +956,7 @@ hole==0.8.0
|
|||||||
holidays==0.62
|
holidays==0.62
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20241127.4
|
home-assistant-frontend==20241127.6
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2024.12.4
|
home-assistant-intents==2024.12.4
|
||||||
@ -1488,7 +1488,7 @@ pycsspeechtts==1.0.8
|
|||||||
pydaikin==2.13.7
|
pydaikin==2.13.7
|
||||||
|
|
||||||
# homeassistant.components.deako
|
# homeassistant.components.deako
|
||||||
pydeako==0.5.4
|
pydeako==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.deconz
|
# homeassistant.components.deconz
|
||||||
pydeconz==118
|
pydeconz==118
|
||||||
@ -1889,7 +1889,7 @@ python-izone==1.2.9
|
|||||||
python-juicenet==1.1.0
|
python-juicenet==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.tplink
|
# homeassistant.components.tplink
|
||||||
python-kasa[speedups]==0.8.0
|
python-kasa[speedups]==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.linkplay
|
# homeassistant.components.linkplay
|
||||||
python-linkplay==0.0.20
|
python-linkplay==0.0.20
|
||||||
@ -2086,7 +2086,7 @@ rxv==0.7.0
|
|||||||
samsungctl[websocket]==0.7.1
|
samsungctl[websocket]==0.7.1
|
||||||
|
|
||||||
# homeassistant.components.samsungtv
|
# homeassistant.components.samsungtv
|
||||||
samsungtvws[async,encrypted]==2.7.1
|
samsungtvws[async,encrypted]==2.7.2
|
||||||
|
|
||||||
# homeassistant.components.sanix
|
# homeassistant.components.sanix
|
||||||
sanix==1.0.6
|
sanix==1.0.6
|
||||||
@ -2238,7 +2238,7 @@ temperusb==1.6.1
|
|||||||
# homeassistant.components.tesla_fleet
|
# homeassistant.components.tesla_fleet
|
||||||
# homeassistant.components.teslemetry
|
# homeassistant.components.teslemetry
|
||||||
# homeassistant.components.tessie
|
# homeassistant.components.tessie
|
||||||
tesla-fleet-api==0.8.4
|
tesla-fleet-api==0.8.5
|
||||||
|
|
||||||
# homeassistant.components.powerwall
|
# homeassistant.components.powerwall
|
||||||
tesla-powerwall==0.5.2
|
tesla-powerwall==0.5.2
|
||||||
@ -2322,7 +2322,7 @@ unifi-discovery==1.2.0
|
|||||||
universal-silabs-flasher==0.0.25
|
universal-silabs-flasher==0.0.25
|
||||||
|
|
||||||
# homeassistant.components.upb
|
# homeassistant.components.upb
|
||||||
upb-lib==0.5.8
|
upb-lib==0.5.9
|
||||||
|
|
||||||
# homeassistant.components.upcloud
|
# homeassistant.components.upcloud
|
||||||
upcloud-api==2.6.0
|
upcloud-api==2.6.0
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from pydeako.deako import DeviceListTimeout, FindDevicesTimeout
|
from pydeako import FindDevicesError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -37,7 +37,7 @@ async def test_deako_async_setup_entry(
|
|||||||
assert mock_config_entry.runtime_data == pydeako_deako_mock.return_value
|
assert mock_config_entry.runtime_data == pydeako_deako_mock.return_value
|
||||||
|
|
||||||
|
|
||||||
async def test_deako_async_setup_entry_device_list_timeout(
|
async def test_deako_async_setup_entry_devices_error(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
pydeako_deako_mock: MagicMock,
|
pydeako_deako_mock: MagicMock,
|
||||||
@ -47,32 +47,7 @@ async def test_deako_async_setup_entry_device_list_timeout(
|
|||||||
|
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
pydeako_deako_mock.return_value.find_devices.side_effect = DeviceListTimeout()
|
pydeako_deako_mock.return_value.find_devices.side_effect = FindDevicesError()
|
||||||
|
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
pydeako_deako_mock.assert_called_once_with(
|
|
||||||
pydeako_discoverer_mock.return_value.get_address
|
|
||||||
)
|
|
||||||
pydeako_deako_mock.return_value.connect.assert_called_once()
|
|
||||||
pydeako_deako_mock.return_value.find_devices.assert_called_once()
|
|
||||||
pydeako_deako_mock.return_value.disconnect.assert_called_once()
|
|
||||||
|
|
||||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
|
||||||
|
|
||||||
|
|
||||||
async def test_deako_async_setup_entry_find_devices_timeout(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
mock_config_entry: MockConfigEntry,
|
|
||||||
pydeako_deako_mock: MagicMock,
|
|
||||||
pydeako_discoverer_mock: MagicMock,
|
|
||||||
) -> None:
|
|
||||||
"""Test async_setup_entry raises ConfigEntryNotReady when pydeako raises FindDevicesTimeout."""
|
|
||||||
|
|
||||||
mock_config_entry.add_to_hass(hass)
|
|
||||||
|
|
||||||
pydeako_deako_mock.return_value.find_devices.side_effect = FindDevicesTimeout()
|
|
||||||
|
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Configuration for Elmax tests."""
|
"""Configuration for Elmax tests."""
|
||||||
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
from datetime import datetime, timedelta
|
||||||
import json
|
import json
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ from elmax_api.constants import (
|
|||||||
ENDPOINT_LOGIN,
|
ENDPOINT_LOGIN,
|
||||||
)
|
)
|
||||||
from httpx import Response
|
from httpx import Response
|
||||||
|
import jwt
|
||||||
import pytest
|
import pytest
|
||||||
import respx
|
import respx
|
||||||
|
|
||||||
@ -64,9 +66,20 @@ def httpx_mock_direct_fixture() -> Generator[respx.MockRouter]:
|
|||||||
) as respx_mock:
|
) as respx_mock:
|
||||||
# Mock Login POST.
|
# Mock Login POST.
|
||||||
login_route = respx_mock.post(f"/api/v2/{ENDPOINT_LOGIN}", name="login")
|
login_route = respx_mock.post(f"/api/v2/{ENDPOINT_LOGIN}", name="login")
|
||||||
login_route.return_value = Response(
|
|
||||||
200, json=json.loads(load_fixture("direct/login.json", "elmax"))
|
login_json = json.loads(load_fixture("direct/login.json", "elmax"))
|
||||||
|
decoded_jwt = jwt.decode_complete(
|
||||||
|
login_json["token"].split(" ")[1],
|
||||||
|
algorithms="HS256",
|
||||||
|
options={"verify_signature": False},
|
||||||
)
|
)
|
||||||
|
expiration = datetime.now() + timedelta(hours=1)
|
||||||
|
decoded_jwt["payload"]["exp"] = int(expiration.timestamp())
|
||||||
|
jws_string = jwt.encode(
|
||||||
|
payload=decoded_jwt["payload"], algorithm="HS256", key=""
|
||||||
|
)
|
||||||
|
login_json["token"] = f"JWT {jws_string}"
|
||||||
|
login_route.return_value = Response(200, json=login_json)
|
||||||
|
|
||||||
# Mock Device list GET.
|
# Mock Device list GET.
|
||||||
list_devices_route = respx_mock.get(
|
list_devices_route = respx_mock.get(
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
)
|
)
|
||||||
# ---
|
# ---
|
||||||
# name: test_create_todo_list_item[due].1
|
# name: test_create_todo_list_item[due].1
|
||||||
'{"title": "Soda", "status": "needsAction", "due": "2023-11-18T00:00:00-08:00", "notes": null}'
|
'{"title": "Soda", "status": "needsAction", "due": "2023-11-18T00:00:00+00:00", "notes": null}'
|
||||||
# ---
|
# ---
|
||||||
# name: test_create_todo_list_item[summary]
|
# name: test_create_todo_list_item[summary]
|
||||||
tuple(
|
tuple(
|
||||||
@ -137,7 +137,7 @@
|
|||||||
)
|
)
|
||||||
# ---
|
# ---
|
||||||
# name: test_partial_update[due_date].1
|
# name: test_partial_update[due_date].1
|
||||||
'{"title": "Water", "status": "needsAction", "due": "2023-11-18T00:00:00-08:00", "notes": null}'
|
'{"title": "Water", "status": "needsAction", "due": "2023-11-18T00:00:00+00:00", "notes": null}'
|
||||||
# ---
|
# ---
|
||||||
# name: test_partial_update[empty_description]
|
# name: test_partial_update[empty_description]
|
||||||
tuple(
|
tuple(
|
||||||
@ -166,6 +166,33 @@
|
|||||||
# name: test_partial_update_status[api_responses0].1
|
# name: test_partial_update_status[api_responses0].1
|
||||||
'{"title": "Water", "status": "needsAction", "due": null, "notes": null}'
|
'{"title": "Water", "status": "needsAction", "due": null, "notes": null}'
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_update_due_date[api_responses0-America/Regina]
|
||||||
|
tuple(
|
||||||
|
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||||
|
'PATCH',
|
||||||
|
)
|
||||||
|
# ---
|
||||||
|
# name: test_update_due_date[api_responses0-America/Regina].1
|
||||||
|
'{"title": "Water", "status": "needsAction", "due": "2024-12-05T00:00:00+00:00", "notes": null}'
|
||||||
|
# ---
|
||||||
|
# name: test_update_due_date[api_responses0-Asia/Tokyo]
|
||||||
|
tuple(
|
||||||
|
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||||
|
'PATCH',
|
||||||
|
)
|
||||||
|
# ---
|
||||||
|
# name: test_update_due_date[api_responses0-Asia/Tokyo].1
|
||||||
|
'{"title": "Water", "status": "needsAction", "due": "2024-12-05T00:00:00+00:00", "notes": null}'
|
||||||
|
# ---
|
||||||
|
# name: test_update_due_date[api_responses0-UTC]
|
||||||
|
tuple(
|
||||||
|
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||||
|
'PATCH',
|
||||||
|
)
|
||||||
|
# ---
|
||||||
|
# name: test_update_due_date[api_responses0-UTC].1
|
||||||
|
'{"title": "Water", "status": "needsAction", "due": "2024-12-05T00:00:00+00:00", "notes": null}'
|
||||||
|
# ---
|
||||||
# name: test_update_todo_list_item[api_responses0]
|
# name: test_update_todo_list_item[api_responses0]
|
||||||
tuple(
|
tuple(
|
||||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||||
|
@ -239,6 +239,7 @@ def mock_http_response(response_handler: list | Callable) -> Mock:
|
|||||||
yield mock_response
|
yield mock_response
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("timezone", ["America/Regina", "UTC", "Asia/Tokyo"])
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"api_responses",
|
"api_responses",
|
||||||
[
|
[
|
||||||
@ -251,7 +252,7 @@ def mock_http_response(response_handler: list | Callable) -> Mock:
|
|||||||
"title": "Task 1",
|
"title": "Task 1",
|
||||||
"status": "needsAction",
|
"status": "needsAction",
|
||||||
"position": "0000000000000001",
|
"position": "0000000000000001",
|
||||||
"due": "2023-11-18T00:00:00+00:00",
|
"due": "2023-11-18T00:00:00Z",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "task-2",
|
"id": "task-2",
|
||||||
@ -271,8 +272,10 @@ async def test_get_items(
|
|||||||
integration_setup: Callable[[], Awaitable[bool]],
|
integration_setup: Callable[[], Awaitable[bool]],
|
||||||
hass_ws_client: WebSocketGenerator,
|
hass_ws_client: WebSocketGenerator,
|
||||||
ws_get_items: Callable[[], Awaitable[dict[str, str]]],
|
ws_get_items: Callable[[], Awaitable[dict[str, str]]],
|
||||||
|
timezone: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test getting todo list items."""
|
"""Test getting todo list items."""
|
||||||
|
await hass.config.async_set_time_zone(timezone)
|
||||||
|
|
||||||
assert await integration_setup()
|
assert await integration_setup()
|
||||||
|
|
||||||
@ -484,6 +487,39 @@ async def test_update_todo_list_item(
|
|||||||
assert call.kwargs.get("body") == snapshot
|
assert call.kwargs.get("body") == snapshot
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("timezone", ["America/Regina", "UTC", "Asia/Tokyo"])
|
||||||
|
@pytest.mark.parametrize("api_responses", [UPDATE_API_RESPONSES])
|
||||||
|
async def test_update_due_date(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
setup_credentials: None,
|
||||||
|
integration_setup: Callable[[], Awaitable[bool]],
|
||||||
|
mock_http_response: Any,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
timezone: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test for updating the due date of a To-do item and timezone."""
|
||||||
|
await hass.config.async_set_time_zone(timezone)
|
||||||
|
|
||||||
|
assert await integration_setup()
|
||||||
|
|
||||||
|
state = hass.states.get("todo.my_tasks")
|
||||||
|
assert state
|
||||||
|
assert state.state == "1"
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
TODO_DOMAIN,
|
||||||
|
TodoServices.UPDATE_ITEM,
|
||||||
|
{ATTR_ITEM: "some-task-id", ATTR_DUE_DATE: "2024-12-5"},
|
||||||
|
target={ATTR_ENTITY_ID: "todo.my_tasks"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_http_response.call_args_list) == 4
|
||||||
|
call = mock_http_response.call_args_list[2]
|
||||||
|
assert call
|
||||||
|
assert call.args == snapshot
|
||||||
|
assert call.kwargs.get("body") == snapshot
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"api_responses",
|
"api_responses",
|
||||||
[
|
[
|
||||||
|
@ -165,6 +165,7 @@
|
|||||||
'openid',
|
'openid',
|
||||||
'offline_access',
|
'offline_access',
|
||||||
'vehicle_device_data',
|
'vehicle_device_data',
|
||||||
|
'vehicle_location',
|
||||||
'vehicle_cmds',
|
'vehicle_cmds',
|
||||||
'vehicle_charging_cmds',
|
'vehicle_charging_cmds',
|
||||||
'energy_device_data',
|
'energy_device_data',
|
||||||
|
@ -93,10 +93,9 @@ async def test_container_installationtype(hass: HomeAssistant) -> None:
|
|||||||
assert info["installation_type"] == "Unsupported Third Party Container"
|
assert info["installation_type"] == "Unsupported Third Party Container"
|
||||||
|
|
||||||
|
|
||||||
async def test_getuser_keyerror(hass: HomeAssistant) -> None:
|
@pytest.mark.parametrize("error", [KeyError, OSError])
|
||||||
"""Test getuser keyerror."""
|
async def test_getuser_oserror(hass: HomeAssistant, error: Exception) -> None:
|
||||||
with patch(
|
"""Test getuser oserror."""
|
||||||
"homeassistant.helpers.system_info.cached_get_user", side_effect=KeyError
|
with patch("homeassistant.helpers.system_info.cached_get_user", side_effect=error):
|
||||||
):
|
|
||||||
info = await async_get_system_info(hass)
|
info = await async_get_system_info(hass)
|
||||||
assert info["user"] is None
|
assert info["user"] is None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user