Merge pull request #75147 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2022-07-13 15:25:35 -07:00 committed by GitHub
commit 60e170c863
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 257 additions and 125 deletions

View File

@ -2,7 +2,7 @@
"domain": "aladdin_connect",
"name": "Aladdin Connect",
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
"requirements": ["AIOAladdinConnect==0.1.21"],
"requirements": ["AIOAladdinConnect==0.1.23"],
"codeowners": ["@mkmer"],
"iot_class": "cloud_polling",
"loggers": ["aladdin_connect"],

View File

@ -3,7 +3,6 @@ from http import HTTPStatus
import json
import logging
from aiohttp.hdrs import CONTENT_TYPE
import requests
import voluptuous as vol
@ -20,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
BASE_API_URL = "https://rest.clicksend.com/v3"
HEADERS = {CONTENT_TYPE: CONTENT_TYPE_JSON}
HEADERS = {"Content-Type": CONTENT_TYPE_JSON}
CONF_LANGUAGE = "language"
CONF_VOICE = "voice"

View File

@ -7,20 +7,24 @@ from pyecobee.const import ECOBEE_STATE_UNKNOWN
from homeassistant.components.weather import (
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_NATIVE_TEMP,
ATTR_FORECAST_NATIVE_TEMP_LOW,
ATTR_FORECAST_NATIVE_WIND_SPEED,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_WIND_SPEED,
WeatherEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PRESSURE_HPA, PRESSURE_INHG, TEMP_FAHRENHEIT
from homeassistant.const import (
LENGTH_METERS,
PRESSURE_HPA,
SPEED_METERS_PER_SECOND,
TEMP_FAHRENHEIT,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import dt as dt_util
from homeassistant.util.pressure import convert as pressure_convert
from .const import (
DOMAIN,
@ -49,6 +53,11 @@ async def async_setup_entry(
class EcobeeWeather(WeatherEntity):
"""Representation of Ecobee weather data."""
_attr_native_pressure_unit = PRESSURE_HPA
_attr_native_temperature_unit = TEMP_FAHRENHEIT
_attr_native_visibility_unit = LENGTH_METERS
_attr_native_wind_speed_unit = SPEED_METERS_PER_SECOND
def __init__(self, data, name, index):
"""Initialize the Ecobee weather platform."""
self.data = data
@ -101,7 +110,7 @@ class EcobeeWeather(WeatherEntity):
return None
@property
def temperature(self):
def native_temperature(self):
"""Return the temperature."""
try:
return float(self.get_forecast(0, "temperature")) / 10
@ -109,18 +118,10 @@ class EcobeeWeather(WeatherEntity):
return None
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT
@property
def pressure(self):
def native_pressure(self):
"""Return the pressure."""
try:
pressure = self.get_forecast(0, "pressure")
if not self.hass.config.units.is_metric:
pressure = pressure_convert(pressure, PRESSURE_HPA, PRESSURE_INHG)
return round(pressure, 2)
return round(pressure)
except ValueError:
return None
@ -134,15 +135,15 @@ class EcobeeWeather(WeatherEntity):
return None
@property
def visibility(self):
def native_visibility(self):
"""Return the visibility."""
try:
return int(self.get_forecast(0, "visibility")) / 1000
return int(self.get_forecast(0, "visibility"))
except ValueError:
return None
@property
def wind_speed(self):
def native_wind_speed(self):
"""Return the wind speed."""
try:
return int(self.get_forecast(0, "windSpeed"))
@ -202,13 +203,13 @@ def _process_forecast(json):
json["weatherSymbol"]
]
if json["tempHigh"] != ECOBEE_STATE_UNKNOWN:
forecast[ATTR_FORECAST_TEMP] = float(json["tempHigh"]) / 10
forecast[ATTR_FORECAST_NATIVE_TEMP] = float(json["tempHigh"]) / 10
if json["tempLow"] != ECOBEE_STATE_UNKNOWN:
forecast[ATTR_FORECAST_TEMP_LOW] = float(json["tempLow"]) / 10
forecast[ATTR_FORECAST_NATIVE_TEMP_LOW] = float(json["tempLow"]) / 10
if json["windBearing"] != ECOBEE_STATE_UNKNOWN:
forecast[ATTR_FORECAST_WIND_BEARING] = int(json["windBearing"])
if json["windSpeed"] != ECOBEE_STATE_UNKNOWN:
forecast[ATTR_FORECAST_WIND_SPEED] = int(json["windSpeed"])
forecast[ATTR_FORECAST_NATIVE_WIND_SPEED] = int(json["windSpeed"])
except (ValueError, IndexError, KeyError):
return None

View File

@ -2,7 +2,7 @@
"domain": "frontier_silicon",
"name": "Frontier Silicon",
"documentation": "https://www.home-assistant.io/integrations/frontier_silicon",
"requirements": ["afsapi==0.2.5"],
"requirements": ["afsapi==0.2.6"],
"codeowners": ["@wlcrs"],
"iot_class": "local_polling"
}

View File

@ -179,11 +179,14 @@ class AFSAPIDevice(MediaPlayerEntity):
self._attr_media_artist = await afsapi.get_play_artist()
self._attr_media_album_name = await afsapi.get_play_album()
self._attr_source = (await afsapi.get_mode()).label
radio_mode = await afsapi.get_mode()
self._attr_source = radio_mode.label if radio_mode is not None else None
self._attr_is_volume_muted = await afsapi.get_mute()
self._attr_media_image_url = await afsapi.get_play_graphic()
self._attr_sound_mode = (await afsapi.get_eq_preset()).label
eq_preset = await afsapi.get_eq_preset()
self._attr_sound_mode = eq_preset.label if eq_preset is not None else None
volume = await self.fs_device.get_volume()

View File

@ -3,7 +3,7 @@
"name": "HomematicIP Cloud",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/homematicip_cloud",
"requirements": ["homematicip==1.0.3"],
"requirements": ["homematicip==1.0.4"],
"codeowners": [],
"quality_scale": "platinum",
"iot_class": "cloud_push",

View File

@ -22,7 +22,7 @@ from homeassistant.components.weather import (
WeatherEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import TEMP_CELSIUS
from homeassistant.const import SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -71,6 +71,9 @@ async def async_setup_entry(
class HomematicipWeatherSensor(HomematicipGenericEntity, WeatherEntity):
"""Representation of the HomematicIP weather sensor plus & basic."""
_attr_native_temperature_unit = TEMP_CELSIUS
_attr_native_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the weather sensor."""
super().__init__(hap, device)
@ -81,22 +84,17 @@ class HomematicipWeatherSensor(HomematicipGenericEntity, WeatherEntity):
return self._device.label
@property
def temperature(self) -> float:
def native_temperature(self) -> float:
"""Return the platform temperature."""
return self._device.actualTemperature
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def humidity(self) -> int:
"""Return the humidity."""
return self._device.humidity
@property
def wind_speed(self) -> float:
def native_wind_speed(self) -> float:
"""Return the wind speed."""
return self._device.windSpeed
@ -129,6 +127,9 @@ class HomematicipWeatherSensorPro(HomematicipWeatherSensor):
class HomematicipHomeWeather(HomematicipGenericEntity, WeatherEntity):
"""Representation of the HomematicIP home weather."""
_attr_native_temperature_unit = TEMP_CELSIUS
_attr_native_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR
def __init__(self, hap: HomematicipHAP) -> None:
"""Initialize the home weather."""
hap.home.modelType = "HmIP-Home-Weather"
@ -145,22 +146,17 @@ class HomematicipHomeWeather(HomematicipGenericEntity, WeatherEntity):
return f"Weather {self._home.location.city}"
@property
def temperature(self) -> float:
def native_temperature(self) -> float:
"""Return the temperature."""
return self._device.weather.temperature
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def humidity(self) -> int:
"""Return the humidity."""
return self._device.weather.humidity
@property
def wind_speed(self) -> float:
def native_wind_speed(self) -> float:
"""Return the wind speed."""
return round(self._device.weather.windSpeed, 1)

View File

@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/huawei_lte",
"requirements": [
"huawei-lte-api==1.6.0",
"huawei-lte-api==1.6.1",
"stringcase==1.2.0",
"url-normalize==1.4.1"
],

View File

@ -455,14 +455,6 @@ class PowerViewShadeTDBUTop(PowerViewShadeTDBU):
super().__init__(coordinator, device_info, room_name, shade, name)
self._attr_unique_id = f"{self._shade.id}_top"
self._attr_name = f"{self._shade_name} Top"
# these shades share a class in parent API
# override open position for top shade
self._shade.open_position = {
ATTR_POSITION1: MIN_POSITION,
ATTR_POSITION2: MAX_POSITION,
ATTR_POSKIND1: POS_KIND_PRIMARY,
ATTR_POSKIND2: POS_KIND_SECONDARY,
}
@property
def should_poll(self) -> bool:
@ -485,6 +477,21 @@ class PowerViewShadeTDBUTop(PowerViewShadeTDBU):
# these need to be inverted to report state correctly in HA
return hd_position_to_hass(self.positions.secondary, MAX_POSITION)
@property
def open_position(self) -> PowerviewShadeMove:
"""Return the open position and required additional positions."""
# these shades share a class in parent API
# override open position for top shade
return PowerviewShadeMove(
{
ATTR_POSITION1: MIN_POSITION,
ATTR_POSITION2: MAX_POSITION,
ATTR_POSKIND1: POS_KIND_PRIMARY,
ATTR_POSKIND2: POS_KIND_SECONDARY,
},
{},
)
@callback
def _clamp_cover_limit(self, target_hass_position: int) -> int:
"""Dont allow a cover to go into an impossbile position."""

View File

@ -2,7 +2,7 @@
"domain": "ialarm",
"name": "Antifurto365 iAlarm",
"documentation": "https://www.home-assistant.io/integrations/ialarm",
"requirements": ["pyialarm==1.9.0"],
"requirements": ["pyialarm==2.2.0"],
"codeowners": ["@RyuzakiKK"],
"config_flow": true,
"iot_class": "local_polling",

View File

@ -86,7 +86,7 @@ class InsteonClimateEntity(InsteonEntity, ClimateEntity):
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
if self._insteon_device.properties[CELSIUS].value:
if self._insteon_device.configuration[CELSIUS].value:
return TEMP_CELSIUS
return TEMP_FAHRENHEIT

View File

@ -4,8 +4,8 @@
"documentation": "https://www.home-assistant.io/integrations/insteon",
"dependencies": ["http", "websocket_api"],
"requirements": [
"pyinsteon==1.1.1",
"insteon-frontend-home-assistant==0.1.1"
"pyinsteon==1.1.3",
"insteon-frontend-home-assistant==0.2.0"
],
"codeowners": ["@teharris1"],
"dhcp": [

View File

@ -37,7 +37,7 @@ from .const import (
SHOW_DRIVING,
SHOW_MOVING,
)
from .coordinator import Life360DataUpdateCoordinator
from .coordinator import Life360DataUpdateCoordinator, MissingLocReason
PLATFORMS = [Platform.DEVICE_TRACKER]
@ -128,6 +128,10 @@ class IntegData:
coordinators: dict[str, Life360DataUpdateCoordinator] = field(
init=False, default_factory=dict
)
# member_id: missing location reason
missing_loc_reason: dict[str, MissingLocReason] = field(
init=False, default_factory=dict
)
# member_id: ConfigEntry.entry_id
tracked_members: dict[str, str] = field(init=False, default_factory=dict)
logged_circles: list[str] = field(init=False, default_factory=list)

View File

@ -2,8 +2,10 @@
from __future__ import annotations
from contextlib import suppress
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any
from life360 import Life360, Life360Error, LoginError
@ -33,6 +35,13 @@ from .const import (
)
class MissingLocReason(Enum):
"""Reason member location information is missing."""
VAGUE_ERROR_REASON = "vague error reason"
EXPLICIT_ERROR_REASON = "explicit error reason"
@dataclass
class Life360Place:
"""Life360 Place data."""
@ -99,6 +108,7 @@ class Life360DataUpdateCoordinator(DataUpdateCoordinator):
max_retries=COMM_MAX_RETRIES,
authorization=entry.data[CONF_AUTHORIZATION],
)
self._missing_loc_reason = hass.data[DOMAIN].missing_loc_reason
async def _retrieve_data(self, func: str, *args: Any) -> list[dict[str, Any]]:
"""Get data from Life360."""
@ -141,10 +151,7 @@ class Life360DataUpdateCoordinator(DataUpdateCoordinator):
if not int(member["features"]["shareLocation"]):
continue
# Note that member may be in more than one circle. If that's the case just
# go ahead and process the newly retrieved data (overwriting the older
# data), since it might be slightly newer than what was retrieved while
# processing another circle.
member_id = member["id"]
first = member["firstName"]
last = member["lastName"]
@ -153,16 +160,45 @@ class Life360DataUpdateCoordinator(DataUpdateCoordinator):
else:
name = first or last
loc = member["location"]
if not loc:
if err_msg := member["issues"]["title"]:
if member["issues"]["dialog"]:
err_msg += f": {member['issues']['dialog']}"
cur_missing_reason = self._missing_loc_reason.get(member_id)
# Check if location information is missing. This can happen if server
# has not heard from member's device in a long time (e.g., has been off
# for a long time, or has lost service, etc.)
if loc := member["location"]:
with suppress(KeyError):
del self._missing_loc_reason[member_id]
else:
if explicit_reason := member["issues"]["title"]:
if extended_reason := member["issues"]["dialog"]:
explicit_reason += f": {extended_reason}"
# Note that different Circles can report missing location in
# different ways. E.g., one might report an explicit reason and
# another does not. If a vague reason has already been logged but a
# more explicit reason is now available, log that, too.
if (
cur_missing_reason is None
or cur_missing_reason == MissingLocReason.VAGUE_ERROR_REASON
and explicit_reason
):
if explicit_reason:
self._missing_loc_reason[
member_id
] = MissingLocReason.EXPLICIT_ERROR_REASON
err_msg = explicit_reason
else:
self._missing_loc_reason[
member_id
] = MissingLocReason.VAGUE_ERROR_REASON
err_msg = "Location information missing"
LOGGER.error("%s: %s", name, err_msg)
continue
# Note that member may be in more than one circle. If that's the case
# just go ahead and process the newly retrieved data (overwriting the
# older data), since it might be slightly newer than what was retrieved
# while processing another circle.
place = loc["name"] or None
if place:
@ -179,7 +215,7 @@ class Life360DataUpdateCoordinator(DataUpdateCoordinator):
if self._hass.config.units.is_metric:
speed = convert(speed, LENGTH_MILES, LENGTH_KILOMETERS)
data.members[member["id"]] = Life360Member(
data.members[member_id] = Life360Member(
address,
dt_util.utc_from_timestamp(int(loc["since"])),
bool(int(loc["charge"])),

View File

@ -4,7 +4,6 @@ from __future__ import annotations
from datetime import timedelta
import logging
from aiohttp.hdrs import CONTENT_TYPE
import requests
import voluptuous as vol
@ -144,7 +143,7 @@ class PyLoadAPI:
"""Initialize pyLoad API and set headers needed later."""
self.api_url = api_url
self.status = None
self.headers = {CONTENT_TYPE: CONTENT_TYPE_JSON}
self.headers = {"Content-Type": CONTENT_TYPE_JSON}
if username is not None and password is not None:
self.payload = {"username": username, "password": password}

View File

@ -199,7 +199,7 @@ class OptionsFlow(config_entries.OptionsFlow):
if not errors:
devices = {}
device = {
CONF_DEVICE_ID: device_id,
CONF_DEVICE_ID: list(device_id),
}
devices[self._selected_device_event_code] = device

View File

@ -29,8 +29,7 @@ from .coordinator import RuckusUnleashedDataUpdateCoordinator
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Ruckus Unleashed from a config entry."""
try:
ruckus = await hass.async_add_executor_job(
Ruckus,
ruckus = await Ruckus.create(
entry.data[CONF_HOST],
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
@ -42,10 +41,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
system_info = await hass.async_add_executor_job(ruckus.system_info)
system_info = await ruckus.system_info()
registry = device_registry.async_get(hass)
ap_info = await hass.async_add_executor_job(ruckus.ap_info)
ap_info = await ruckus.ap_info()
for device in ap_info[API_AP][API_ID].values():
registry.async_get_or_create(
config_entry_id=entry.entry_id,

View File

@ -21,22 +21,24 @@ DATA_SCHEMA = vol.Schema(
)
def validate_input(hass: core.HomeAssistant, data):
async def validate_input(hass: core.HomeAssistant, data):
"""Validate the user input allows us to connect.
Data has the keys from DATA_SCHEMA with values provided by the user.
"""
try:
ruckus = Ruckus(data[CONF_HOST], data[CONF_USERNAME], data[CONF_PASSWORD])
ruckus = await Ruckus.create(
data[CONF_HOST], data[CONF_USERNAME], data[CONF_PASSWORD]
)
except AuthenticationError as error:
raise InvalidAuth from error
except ConnectionError as error:
raise CannotConnect from error
mesh_name = ruckus.mesh_name()
mesh_name = await ruckus.mesh_name()
system_info = ruckus.system_info()
system_info = await ruckus.system_info()
try:
host_serial = system_info[API_SYSTEM_OVERVIEW][API_SERIAL]
except KeyError as error:
@ -58,9 +60,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
try:
info = await self.hass.async_add_executor_job(
validate_input, self.hass, user_input
)
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:

View File

@ -37,9 +37,7 @@ class RuckusUnleashedDataUpdateCoordinator(DataUpdateCoordinator):
async def _fetch_clients(self) -> dict:
"""Fetch clients from the API and format them."""
clients = await self.hass.async_add_executor_job(
self.ruckus.current_active_clients
)
clients = await self.ruckus.current_active_clients()
return {e[API_MAC]: e for e in clients[API_CURRENT_ACTIVE_CLIENTS][API_CLIENTS]}
async def _async_update_data(self) -> dict:

View File

@ -3,7 +3,7 @@
"name": "Ruckus Unleashed",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ruckus_unleashed",
"requirements": ["pyruckus==0.12"],
"requirements": ["pyruckus==0.16"],
"codeowners": ["@gabe565"],
"iot_class": "local_polling",
"loggers": ["pexpect", "pyruckus"]

View File

@ -5,7 +5,7 @@ from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ADDRESS, CONF_LOCATION
from homeassistant.const import CONF_ADDRESS, CONF_CODE, CONF_LOCATION
from homeassistant.core import HomeAssistant
from . import SimpliSafe
@ -23,6 +23,7 @@ CONF_WIFI_SSID = "wifi_ssid"
TO_REDACT = {
CONF_ADDRESS,
CONF_CODE,
CONF_CREDIT_CARD,
CONF_EXPIRES,
CONF_LOCATION,

View File

@ -69,11 +69,13 @@ from homeassistant.const import (
SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET,
SERVICE_VOLUME_UP,
STATE_BUFFERING,
STATE_IDLE,
STATE_OFF,
STATE_ON,
STATE_PAUSED,
STATE_PLAYING,
STATE_STANDBY,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
@ -101,8 +103,10 @@ STATES_ORDER = [
STATE_UNAVAILABLE,
STATE_OFF,
STATE_IDLE,
STATE_STANDBY,
STATE_ON,
STATE_PAUSED,
STATE_BUFFERING,
STATE_PLAYING,
]
ATTRS_SCHEMA = cv.schema_with_slug_keys(cv.string)

View File

@ -170,7 +170,7 @@ def async_get_zha_device(hass: HomeAssistant, device_id: str) -> ZHADevice:
device_registry = dr.async_get(hass)
registry_device = device_registry.async_get(device_id)
if not registry_device:
raise ValueError(f"Device id `{device_id}` not found in registry.")
raise KeyError(f"Device id `{device_id}` not found in registry.")
zha_gateway: ZHAGateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
ieee_address = list(list(registry_device.identifiers)[0])[1]
ieee = zigpy.types.EUI64.convert(ieee_address)

View File

@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/zha",
"requirements": [
"bellows==0.31.0",
"bellows==0.31.1",
"pyserial==3.5",
"pyserial-asyncio==0.6",
"zha-quirks==0.0.77",
@ -12,7 +12,7 @@
"zigpy==0.47.2",
"zigpy-xbee==0.15.0",
"zigpy-zigate==0.9.0",
"zigpy-znp==0.8.0"
"zigpy-znp==0.8.1"
],
"usb": [
{

View File

@ -7,7 +7,7 @@ from .backports.enum import StrEnum
MAJOR_VERSION: Final = 2022
MINOR_VERSION: Final = 7
PATCH_VERSION: Final = "3"
PATCH_VERSION: Final = "4"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)

View File

@ -14,7 +14,6 @@ from aiohttp import web
from aiohttp.hdrs import CONTENT_TYPE, USER_AGENT
from aiohttp.web_exceptions import HTTPBadGateway, HTTPGatewayTimeout
import async_timeout
import orjson
from homeassistant import config_entries
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__
@ -23,6 +22,7 @@ from homeassistant.loader import bind_hass
from homeassistant.util import ssl as ssl_util
from .frame import warn_use
from .json import json_dumps
DATA_CONNECTOR = "aiohttp_connector"
DATA_CONNECTOR_NOTVERIFY = "aiohttp_connector_notverify"
@ -98,7 +98,7 @@ def _async_create_clientsession(
"""Create a new ClientSession with kwargs, i.e. for cookies."""
clientsession = aiohttp.ClientSession(
connector=_async_get_connector(hass, verify_ssl),
json_serialize=lambda x: orjson.dumps(x).decode("utf-8"),
json_serialize=json_dumps,
**kwargs,
)
# Prevent packages accidentally overriding our default headers

View File

@ -114,3 +114,7 @@ backoff<2.0
# Breaking change in version
# https://github.com/samuelcolvin/pydantic/issues/4092
pydantic!=1.9.1
# Breaks asyncio
# https://github.com/pubnub/python/issues/130
pubnub!=6.4.0

View File

@ -89,9 +89,6 @@ def install_package(
# This only works if not running in venv
args += ["--user"]
env["PYTHONUSERBASE"] = os.path.abspath(target)
# Workaround for incompatible prefix setting
# See http://stackoverflow.com/a/4495175
args += ["--prefix="]
_LOGGER.debug("Running pip command: args=%s", args)
with Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env) as process:
_, stderr = process.communicate()

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2022.7.3"
version = "2022.7.4"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"

View File

@ -5,7 +5,7 @@
AEMET-OpenData==0.2.1
# homeassistant.components.aladdin_connect
AIOAladdinConnect==0.1.21
AIOAladdinConnect==0.1.23
# homeassistant.components.adax
Adax-local==0.1.4
@ -89,7 +89,7 @@ adguardhome==0.5.1
advantage_air==0.3.1
# homeassistant.components.frontier_silicon
afsapi==0.2.5
afsapi==0.2.6
# homeassistant.components.agent_dvr
agent-py==0.0.23
@ -393,7 +393,7 @@ beautifulsoup4==4.11.1
# beewi_smartclim==0.0.10
# homeassistant.components.zha
bellows==0.31.0
bellows==0.31.1
# homeassistant.components.bmw_connected_drive
bimmer_connected==0.9.6
@ -834,7 +834,7 @@ home-assistant-frontend==20220707.0
homeconnect==0.7.1
# homeassistant.components.homematicip_cloud
homematicip==1.0.3
homematicip==1.0.4
# homeassistant.components.home_plus_control
homepluscontrol==0.0.5
@ -846,7 +846,7 @@ horimote==0.4.1
httplib2==0.20.4
# homeassistant.components.huawei_lte
huawei-lte-api==1.6.0
huawei-lte-api==1.6.1
# homeassistant.components.huisbaasje
huisbaasje-client==0.1.0
@ -891,7 +891,7 @@ influxdb-client==1.24.0
influxdb==5.3.1
# homeassistant.components.insteon
insteon-frontend-home-assistant==0.1.1
insteon-frontend-home-assistant==0.2.0
# homeassistant.components.intellifire
intellifire4py==2.0.1
@ -1553,13 +1553,13 @@ pyhomematic==0.1.77
pyhomeworks==0.0.6
# homeassistant.components.ialarm
pyialarm==1.9.0
pyialarm==2.2.0
# homeassistant.components.icloud
pyicloud==1.0.0
# homeassistant.components.insteon
pyinsteon==1.1.1
pyinsteon==1.1.3
# homeassistant.components.intesishome
pyintesishome==1.8.0
@ -1780,7 +1780,7 @@ pyrisco==0.3.1
pyrituals==0.0.6
# homeassistant.components.ruckus_unleashed
pyruckus==0.12
pyruckus==0.16
# homeassistant.components.sabnzbd
pysabnzbd==1.1.1
@ -2516,7 +2516,7 @@ zigpy-xbee==0.15.0
zigpy-zigate==0.9.0
# homeassistant.components.zha
zigpy-znp==0.8.0
zigpy-znp==0.8.1
# homeassistant.components.zha
zigpy==0.47.2

View File

@ -7,7 +7,7 @@
AEMET-OpenData==0.2.1
# homeassistant.components.aladdin_connect
AIOAladdinConnect==0.1.21
AIOAladdinConnect==0.1.23
# homeassistant.components.adax
Adax-local==0.1.4
@ -308,7 +308,7 @@ base36==0.1.1
beautifulsoup4==4.11.1
# homeassistant.components.zha
bellows==0.31.0
bellows==0.31.1
# homeassistant.components.bmw_connected_drive
bimmer_connected==0.9.6
@ -601,7 +601,7 @@ home-assistant-frontend==20220707.0
homeconnect==0.7.1
# homeassistant.components.homematicip_cloud
homematicip==1.0.3
homematicip==1.0.4
# homeassistant.components.home_plus_control
homepluscontrol==0.0.5
@ -610,7 +610,7 @@ homepluscontrol==0.0.5
httplib2==0.20.4
# homeassistant.components.huawei_lte
huawei-lte-api==1.6.0
huawei-lte-api==1.6.1
# homeassistant.components.huisbaasje
huisbaasje-client==0.1.0
@ -634,7 +634,7 @@ influxdb-client==1.24.0
influxdb==5.3.1
# homeassistant.components.insteon
insteon-frontend-home-assistant==0.1.1
insteon-frontend-home-assistant==0.2.0
# homeassistant.components.intellifire
intellifire4py==2.0.1
@ -1047,13 +1047,13 @@ pyhiveapi==0.5.13
pyhomematic==0.1.77
# homeassistant.components.ialarm
pyialarm==1.9.0
pyialarm==2.2.0
# homeassistant.components.icloud
pyicloud==1.0.0
# homeassistant.components.insteon
pyinsteon==1.1.1
pyinsteon==1.1.3
# homeassistant.components.ipma
pyipma==2.0.5
@ -1211,7 +1211,7 @@ pyrisco==0.3.1
pyrituals==0.0.6
# homeassistant.components.ruckus_unleashed
pyruckus==0.12
pyruckus==0.16
# homeassistant.components.sabnzbd
pysabnzbd==1.1.1
@ -1677,7 +1677,7 @@ zigpy-xbee==0.15.0
zigpy-zigate==0.9.0
# homeassistant.components.zha
zigpy-znp==0.8.0
zigpy-znp==0.8.1
# homeassistant.components.zha
zigpy==0.47.2

View File

@ -132,6 +132,10 @@ backoff<2.0
# Breaking change in version
# https://github.com/samuelcolvin/pydantic/issues/4092
pydantic!=1.9.1
# Breaks asyncio
# https://github.com/pubnub/python/issues/130
pubnub!=6.4.0
"""
IGNORE_PRE_COMMIT_HOOK_ID = (

View File

@ -867,6 +867,9 @@ async def test_options_configure_rfy_cover_device(hass):
entry.data["devices"]["0C1a0000010203010000000000"]["venetian_blind_mode"]
== "EU"
)
assert isinstance(
entry.data["devices"]["0C1a0000010203010000000000"]["device_id"], list
)
device_registry = dr.async_get(hass)
device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id)
@ -904,6 +907,9 @@ async def test_options_configure_rfy_cover_device(hass):
entry.data["devices"]["0C1a0000010203010000000000"]["venetian_blind_mode"]
== "EU"
)
assert isinstance(
entry.data["devices"]["0C1a0000010203010000000000"]["device_id"], list
)
def test_get_serial_by_id_no_dir():

View File

@ -31,7 +31,7 @@ async def test_setup_entry_login_error(hass):
"""Test entry setup failed due to login error."""
entry = mock_config_entry()
with patch(
"homeassistant.components.ruckus_unleashed.Ruckus",
"homeassistant.components.ruckus_unleashed.Ruckus.connect",
side_effect=AuthenticationError,
):
entry.add_to_hass(hass)
@ -45,7 +45,7 @@ async def test_setup_entry_connection_error(hass):
"""Test entry setup failed due to connection error."""
entry = mock_config_entry()
with patch(
"homeassistant.components.ruckus_unleashed.Ruckus",
"homeassistant.components.ruckus_unleashed.Ruckus.connect",
side_effect=ConnectionError,
):
entry.add_to_hass(hass)

View File

@ -43,7 +43,9 @@ def api_fixture(api_auth_state, data_subscription, system_v3, websocket):
@pytest.fixture(name="config_entry")
def config_entry_fixture(hass, config, unique_id):
"""Define a config entry."""
entry = MockConfigEntry(domain=DOMAIN, unique_id=unique_id, data=config)
entry = MockConfigEntry(
domain=DOMAIN, unique_id=unique_id, data=config, options={CONF_CODE: "1234"}
)
entry.add_to_hass(hass)
return entry

View File

@ -8,7 +8,9 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_simplisa
"""Test config entry diagnostics."""
assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == {
"entry": {
"options": {},
"options": {
"code": REDACTED,
},
},
"subscription_data": {
"system_123": {

View File

@ -370,3 +370,41 @@ async def test_exception_bad_trigger(hass, mock_devices, calls, caplog):
)
await hass.async_block_till_done()
assert "Invalid config for [automation]" in caplog.text
async def test_exception_no_device(hass, mock_devices, calls, caplog):
"""Test for exception on event triggers firing."""
zigpy_device, zha_device = mock_devices
zigpy_device.device_automation_triggers = {
(SHAKEN, SHAKEN): {COMMAND: COMMAND_SHAKE},
(DOUBLE_PRESS, DOUBLE_PRESS): {COMMAND: COMMAND_DOUBLE},
(SHORT_PRESS, SHORT_PRESS): {COMMAND: COMMAND_SINGLE},
(LONG_PRESS, LONG_PRESS): {COMMAND: COMMAND_HOLD},
(LONG_RELEASE, LONG_RELEASE): {COMMAND: COMMAND_HOLD},
}
await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"device_id": "no_such_device_id",
"domain": "zha",
"platform": "device",
"type": "junk",
"subtype": "junk",
},
"action": {
"service": "test.automation",
"data": {"message": "service called"},
},
}
]
},
)
await hass.async_block_till_done()
assert "Invalid config for [automation]" in caplog.text

View File

@ -19,6 +19,7 @@ from homeassistant.const import (
)
from homeassistant.core import EVENT_HOMEASSISTANT_CLOSE
import homeassistant.helpers.aiohttp_client as client
from homeassistant.util.color import RGBColor
from tests.common import MockConfigEntry
@ -215,6 +216,16 @@ async def test_async_aiohttp_proxy_stream_client_err(aioclient_mock, camera_clie
assert resp.status == 502
async def test_sending_named_tuple(hass, aioclient_mock):
"""Test sending a named tuple in json."""
resp = aioclient_mock.post("http://127.0.0.1/rgb", json={"rgb": RGBColor(4, 3, 2)})
session = client.async_create_clientsession(hass)
resp = await session.post("http://127.0.0.1/rgb", json={"rgb": RGBColor(4, 3, 2)})
assert resp.status == 200
await resp.json() == {"rgb": RGBColor(4, 3, 2)}
aioclient_mock.mock_calls[0][2]["rgb"] == RGBColor(4, 3, 2)
async def test_client_session_immutable_headers(hass):
"""Test we can't mutate headers."""
session = client.async_get_clientsession(hass)

View File

@ -2,6 +2,7 @@
import datetime
import json
import time
from typing import NamedTuple
import pytest
@ -13,6 +14,7 @@ from homeassistant.helpers.json import (
json_dumps_sorted,
)
from homeassistant.util import dt as dt_util
from homeassistant.util.color import RGBColor
@pytest.mark.parametrize("encoder", (JSONEncoder, ExtendedJSONEncoder))
@ -96,3 +98,23 @@ def test_json_dumps_tuple_subclass():
tt = time.struct_time((1999, 3, 17, 32, 44, 55, 2, 76, 0))
assert json_dumps(tt) == "[1999,3,17,32,44,55,2,76,0]"
def test_json_dumps_named_tuple_subclass():
"""Test the json dumps a tuple subclass."""
class NamedTupleSubclass(NamedTuple):
"""A NamedTuple subclass."""
name: str
nts = NamedTupleSubclass("a")
assert json_dumps(nts) == '["a"]'
def test_json_dumps_rgb_color_subclass():
"""Test the json dumps of RGBColor."""
rgb = RGBColor(4, 2, 1)
assert json_dumps(rgb) == "[4,2,1]"

View File

@ -111,7 +111,7 @@ class AiohttpClientMocker:
def create_session(self, loop):
"""Create a ClientSession that is bound to this mocker."""
session = ClientSession(loop=loop)
session = ClientSession(loop=loop, json_serialize=json_dumps)
# Setting directly on `session` will raise deprecation warning
object.__setattr__(session, "_request", self.match_request)
return session

View File

@ -137,7 +137,6 @@ def test_install_target(mock_sys, mock_popen, mock_env_copy, mock_venv):
"--quiet",
TEST_NEW_REQ,
"--user",
"--prefix=",
]
assert package.install_package(TEST_NEW_REQ, False, target=target)
@ -156,7 +155,7 @@ def test_install_target_venv(mock_sys, mock_popen, mock_env_copy, mock_venv):
def test_install_error(caplog, mock_sys, mock_popen, mock_venv):
"""Test an install with a target."""
"""Test an install that errors out."""
caplog.set_level(logging.WARNING)
mock_popen.return_value.returncode = 1
assert not package.install_package(TEST_NEW_REQ)