Merge pull request #36840 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2020-06-15 17:32:52 -07:00 committed by GitHub
commit dfc345a921
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 151 additions and 41 deletions

View File

@ -2,6 +2,6 @@
"domain": "asuswrt", "domain": "asuswrt",
"name": "ASUSWRT", "name": "ASUSWRT",
"documentation": "https://www.home-assistant.io/integrations/asuswrt", "documentation": "https://www.home-assistant.io/integrations/asuswrt",
"requirements": ["aioasuswrt==1.2.5"], "requirements": ["aioasuswrt==1.2.6"],
"codeowners": ["@kennedyshead"] "codeowners": ["@kennedyshead"]
} }

View File

@ -19,7 +19,7 @@ from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.util.aiohttp import MockRequest from homeassistant.util.aiohttp import MockRequest
from . import alexa_config, google_config, utils from . import alexa_config, google_config, utils
from .const import DISPATCHER_REMOTE_UPDATE from .const import DISPATCHER_REMOTE_UPDATE, DOMAIN
from .prefs import CloudPreferences from .prefs import CloudPreferences
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -182,6 +182,7 @@ class CloudClient(Interface):
headers=payload["headers"], headers=payload["headers"],
method=payload["method"], method=payload["method"],
query_string=payload["query"], query_string=payload["query"],
mock_source=DOMAIN,
) )
response = await self._hass.components.webhook.async_handle_webhook( response = await self._hass.components.webhook.async_handle_webhook(

View File

@ -249,9 +249,19 @@ async def async_setup(hass, config):
await hassio.update_hass_api(config.get("http", {}), refresh_token) await hassio.update_hass_api(config.get("http", {}), refresh_token)
last_timezone = None
async def push_config(_): async def push_config(_):
"""Push core config to Hass.io.""" """Push core config to Hass.io."""
await hassio.update_hass_timezone(str(hass.config.time_zone)) nonlocal last_timezone
new_timezone = str(hass.config.time_zone)
if new_timezone == last_timezone:
return
last_timezone = new_timezone
await hassio.update_hass_timezone(new_timezone)
hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, push_config) hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, push_config)

View File

@ -12,11 +12,7 @@ import voluptuous as vol
from homeassistant.components import recorder from homeassistant.components import recorder
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.components.recorder.models import ( from homeassistant.components.recorder.models import States, process_timestamp
DB_TIMEZONE,
States,
process_timestamp,
)
from homeassistant.components.recorder.util import execute, session_scope from homeassistant.components.recorder.util import execute, session_scope
from homeassistant.const import ( from homeassistant.const import (
ATTR_HIDDEN, ATTR_HIDDEN,
@ -355,7 +351,9 @@ def _sorted_states_to_json(
ent_results.append( ent_results.append(
{ {
STATE_KEY: db_state.state, STATE_KEY: db_state.state,
LAST_CHANGED_KEY: f"{str(_process_timestamp(db_state.last_changed)).replace(' ','T').split('.')[0]}{DB_TIMEZONE}", LAST_CHANGED_KEY: _process_timestamp(
db_state.last_changed
).isoformat(),
} }
) )
prev_state = db_state prev_state = db_state

View File

@ -124,4 +124,4 @@ class InsteonEntity(Entity):
async def _async_add_default_links(self): async def _async_add_default_links(self):
"""Add default links between the device and the modem.""" """Add default links between the device and the modem."""
await self._insteon_device.async_add_default_links(self.address) await self._insteon_device.async_add_default_links()

View File

@ -2,6 +2,6 @@
"domain": "insteon", "domain": "insteon",
"name": "Insteon", "name": "Insteon",
"documentation": "https://www.home-assistant.io/integrations/insteon", "documentation": "https://www.home-assistant.io/integrations/insteon",
"requirements": ["pyinsteon==1.0.3"], "requirements": ["pyinsteon==1.0.4"],
"codeowners": ["@teharris1"] "codeowners": ["@teharris1"]
} }

View File

@ -230,7 +230,6 @@ class NanoleafLight(LightEntity):
try: try:
self._available = self._light.available self._available = self._light.available
self._brightness = self._light.brightness self._brightness = self._light.brightness
self._color_temp = self._light.color_temperature
self._effects_list = self._light.effects self._effects_list = self._light.effects
# Nanoleaf api returns non-existent effect named "*Solid*" when light set to solid color. # Nanoleaf api returns non-existent effect named "*Solid*" when light set to solid color.
# This causes various issues with scening (see https://github.com/home-assistant/core/issues/36359). # This causes various issues with scening (see https://github.com/home-assistant/core/issues/36359).
@ -238,7 +237,12 @@ class NanoleafLight(LightEntity):
self._effect = ( self._effect = (
self._light.effect if self._light.effect in self._effects_list else None self._light.effect if self._light.effect in self._effects_list else None
) )
if self._effect is None:
self._color_temp = self._light.color_temperature
self._hs_color = self._light.hue, self._light.saturation self._hs_color = self._light.hue, self._light.saturation
else:
self._color_temp = None
self._hs_color = None
self._state = self._light.on self._state = self._light.on
except Unavailable as err: except Unavailable as err:
_LOGGER.error("Could not update status for %s (%s)", self.name, err) _LOGGER.error("Could not update status for %s (%s)", self.name, err)

View File

@ -12,6 +12,7 @@ from homeassistant.const import HTTP_OK
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.network import get_url from homeassistant.helpers.network import get_url
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
from homeassistant.util.aiohttp import MockRequest
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -76,9 +77,15 @@ async def async_handle_webhook(hass, webhook_id, request):
# Always respond successfully to not give away if a hook exists or not. # Always respond successfully to not give away if a hook exists or not.
if webhook is None: if webhook is None:
peer_ip = request[KEY_REAL_IP] if isinstance(request, MockRequest):
received_from = request.mock_source
else:
received_from = request[KEY_REAL_IP]
_LOGGER.warning( _LOGGER.warning(
"Received message for unregistered webhook %s from %s", webhook_id, peer_ip "Received message for unregistered webhook %s from %s",
webhook_id,
received_from,
) )
# Look at content to provide some context for received webhook # Look at content to provide some context for received webhook
# Limit to 64 chars to avoid flooding the log # Limit to 64 chars to avoid flooding the log

View File

@ -3,7 +3,7 @@
"name": "WLED", "name": "WLED",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/wled", "documentation": "https://www.home-assistant.io/integrations/wled",
"requirements": ["wled==0.4.2"], "requirements": ["wled==0.4.3"],
"zeroconf": ["_wled._tcp.local."], "zeroconf": ["_wled._tcp.local."],
"codeowners": ["@frenck"], "codeowners": ["@frenck"],
"quality_scale": "platinum" "quality_scale": "platinum"

View File

@ -6,7 +6,7 @@
"requirements": [ "requirements": [
"bellows==0.16.2", "bellows==0.16.2",
"pyserial==3.4", "pyserial==3.4",
"zha-quirks==0.0.39", "zha-quirks==0.0.40",
"zigpy-cc==0.4.4", "zigpy-cc==0.4.4",
"zigpy-deconz==0.9.2", "zigpy-deconz==0.9.2",
"zigpy==0.20.4", "zigpy==0.20.4",

View File

@ -1,7 +1,7 @@
"""Constants used by Home Assistant components.""" """Constants used by Home Assistant components."""
MAJOR_VERSION = 0 MAJOR_VERSION = 0
MINOR_VERSION = 111 MINOR_VERSION = 111
PATCH_VERSION = "2" PATCH_VERSION = "3"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 7, 0) REQUIRED_PYTHON_VER = (3, 7, 0)

View File

@ -261,11 +261,11 @@ class HomeAssistant:
This method is a coroutine. This method is a coroutine.
""" """
_LOGGER.info("Starting Home Assistant") _LOGGER.info("Starting Home Assistant")
self.state = CoreState.starting
setattr(self.loop, "_thread_ident", threading.get_ident()) setattr(self.loop, "_thread_ident", threading.get_ident())
self.bus.async_fire(EVENT_HOMEASSISTANT_START)
self.state = CoreState.starting
self.bus.async_fire(EVENT_CORE_CONFIG_UPDATE) self.bus.async_fire(EVENT_CORE_CONFIG_UPDATE)
self.bus.async_fire(EVENT_HOMEASSISTANT_START)
try: try:
# Only block for EVENT_HOMEASSISTANT_START listener # Only block for EVENT_HOMEASSISTANT_START listener
@ -291,8 +291,9 @@ class HomeAssistant:
return return
self.state = CoreState.running self.state = CoreState.running
_async_create_timer(self) self.bus.async_fire(EVENT_CORE_CONFIG_UPDATE)
self.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) self.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
_async_create_timer(self)
def add_job(self, target: Callable[..., Any], *args: Any) -> None: def add_job(self, target: Callable[..., Any], *args: Any) -> None:
"""Add job to the executor pool. """Add job to the executor pool.

View File

@ -1,4 +1,5 @@
"""Utilities to help with aiohttp.""" """Utilities to help with aiohttp."""
import io
import json import json
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
from urllib.parse import parse_qsl from urllib.parse import parse_qsl
@ -8,12 +9,29 @@ from multidict import CIMultiDict, MultiDict
from homeassistant.const import HTTP_OK from homeassistant.const import HTTP_OK
class MockStreamReader:
"""Small mock to imitate stream reader."""
def __init__(self, content: bytes) -> None:
"""Initialize mock stream reader."""
self._content = io.BytesIO(content)
async def read(self, byte_count: int = -1) -> bytes:
"""Read bytes."""
if byte_count == -1:
return self._content.read()
return self._content.read(byte_count)
class MockRequest: class MockRequest:
"""Mock an aiohttp request.""" """Mock an aiohttp request."""
mock_source: Optional[str] = None
def __init__( def __init__(
self, self,
content: bytes, content: bytes,
mock_source: str,
method: str = "GET", method: str = "GET",
status: int = HTTP_OK, status: int = HTTP_OK,
headers: Optional[Dict[str, str]] = None, headers: Optional[Dict[str, str]] = None,
@ -27,6 +45,7 @@ class MockRequest:
self.headers: CIMultiDict[str] = CIMultiDict(headers or {}) self.headers: CIMultiDict[str] = CIMultiDict(headers or {})
self.query_string = query_string or "" self.query_string = query_string or ""
self._content = content self._content = content
self.mock_source = mock_source
@property @property
def query(self) -> "MultiDict[str]": def query(self) -> "MultiDict[str]":
@ -38,6 +57,11 @@ class MockRequest:
"""Return the body as text.""" """Return the body as text."""
return self._content.decode("utf-8") return self._content.decode("utf-8")
@property
def content(self) -> MockStreamReader:
"""Return the body as text."""
return MockStreamReader(self._content)
async def json(self) -> Any: async def json(self) -> Any:
"""Return the body as JSON.""" """Return the body as JSON."""
return json.loads(self._text) return json.loads(self._text)

View File

@ -156,7 +156,7 @@ aio_georss_gdacs==0.3
aioambient==1.1.1 aioambient==1.1.1
# homeassistant.components.asuswrt # homeassistant.components.asuswrt
aioasuswrt==1.2.5 aioasuswrt==1.2.6
# homeassistant.components.aws # homeassistant.components.aws
aiobotocore==0.11.1 aiobotocore==0.11.1
@ -1375,7 +1375,7 @@ pyialarm==0.3
pyicloud==0.9.7 pyicloud==0.9.7
# homeassistant.components.insteon # homeassistant.components.insteon
pyinsteon==1.0.3 pyinsteon==1.0.4
# homeassistant.components.intesishome # homeassistant.components.intesishome
pyintesishome==1.7.4 pyintesishome==1.7.4
@ -2195,7 +2195,7 @@ wirelesstagpy==0.4.0
withings-api==2.1.3 withings-api==2.1.3
# homeassistant.components.wled # homeassistant.components.wled
wled==0.4.2 wled==0.4.3
# homeassistant.components.xbee # homeassistant.components.xbee
xbee-helper==0.0.7 xbee-helper==0.0.7
@ -2242,7 +2242,7 @@ zengge==0.2
zeroconf==0.27.1 zeroconf==0.27.1
# homeassistant.components.zha # homeassistant.components.zha
zha-quirks==0.0.39 zha-quirks==0.0.40
# homeassistant.components.zhong_hong # homeassistant.components.zhong_hong
zhong_hong_hvac==1.0.9 zhong_hong_hvac==1.0.9

View File

@ -66,7 +66,7 @@ aio_georss_gdacs==0.3
aioambient==1.1.1 aioambient==1.1.1
# homeassistant.components.asuswrt # homeassistant.components.asuswrt
aioasuswrt==1.2.5 aioasuswrt==1.2.6
# homeassistant.components.aws # homeassistant.components.aws
aiobotocore==0.11.1 aiobotocore==0.11.1
@ -901,7 +901,7 @@ wiffi==1.0.0
withings-api==2.1.3 withings-api==2.1.3
# homeassistant.components.wled # homeassistant.components.wled
wled==0.4.2 wled==0.4.3
# homeassistant.components.bluesound # homeassistant.components.bluesound
# homeassistant.components.rest # homeassistant.components.rest
@ -918,7 +918,7 @@ ya_ma==0.3.8
zeroconf==0.27.1 zeroconf==0.27.1
# homeassistant.components.zha # homeassistant.components.zha
zha-quirks==0.0.39 zha-quirks==0.0.40
# homeassistant.components.zha # homeassistant.components.zha
zigpy-cc==0.4.4 zigpy-cc==0.4.4

View File

@ -114,12 +114,14 @@ async def test_view(hass):
"""Test view.""" """Test view."""
hass.config_entries.flow.async_init = AsyncMock() hass.config_entries.flow.async_init = AsyncMock()
request = aiohttp.MockRequest(b"", query_string="code=test_code") request = aiohttp.MockRequest(
b"", query_string="code=test_code", mock_source="test"
)
request.app = {"hass": hass} request.app = {"hass": hass}
view = config_flow.AmbiclimateAuthCallbackView() view = config_flow.AmbiclimateAuthCallbackView()
assert await view.get(request) == "OK!" assert await view.get(request) == "OK!"
request = aiohttp.MockRequest(b"", query_string="") request = aiohttp.MockRequest(b"", query_string="", mock_source="test")
request.app = {"hass": hass} request.app = {"hass": hass}
view = config_flow.AmbiclimateAuthCallbackView() view = config_flow.AmbiclimateAuthCallbackView()
assert await view.get(request) == "No code" assert await view.get(request) == "No code"

View File

@ -141,7 +141,7 @@ async def test_handler_google_actions_disabled(hass, mock_cloud_fixture):
assert resp["payload"]["errorCode"] == "deviceTurnedOff" assert resp["payload"]["errorCode"] == "deviceTurnedOff"
async def test_webhook_msg(hass): async def test_webhook_msg(hass, caplog):
"""Test webhook msg.""" """Test webhook msg."""
with patch("hass_nabucasa.Cloud.start"): with patch("hass_nabucasa.Cloud.start"):
setup = await async_setup_component(hass, "cloud", {"cloud": {}}) setup = await async_setup_component(hass, "cloud", {"cloud": {}})
@ -151,7 +151,14 @@ async def test_webhook_msg(hass):
await cloud.client.prefs.async_initialize() await cloud.client.prefs.async_initialize()
await cloud.client.prefs.async_update( await cloud.client.prefs.async_update(
cloudhooks={ cloudhooks={
"hello": {"webhook_id": "mock-webhook-id", "cloudhook_id": "mock-cloud-id"} "mock-webhook-id": {
"webhook_id": "mock-webhook-id",
"cloudhook_id": "mock-cloud-id",
},
"no-longere-existing": {
"webhook_id": "no-longere-existing",
"cloudhook_id": "mock-nonexisting-id",
},
} }
) )
@ -183,6 +190,31 @@ async def test_webhook_msg(hass):
assert len(received) == 1 assert len(received) == 1
assert await received[0].json() == {"hello": "world"} assert await received[0].json() == {"hello": "world"}
# Non existing webhook
caplog.clear()
response = await cloud.client.async_webhook_message(
{
"cloudhook_id": "mock-nonexisting-id",
"body": '{"nonexisting": "payload"}',
"headers": {"content-type": "application/json"},
"method": "POST",
"query": None,
}
)
assert response == {
"status": 200,
"body": None,
"headers": {"Content-Type": "application/octet-stream"},
}
assert (
"Received message for unregistered webhook no-longere-existing from cloud"
in caplog.text
)
assert '{"nonexisting": "payload"}' in caplog.text
async def test_google_config_expose_entity(hass, mock_cloud_setup, mock_cloud_login): async def test_google_config_expose_entity(hass, mock_cloud_setup, mock_cloud_login):
"""Test Google config exposing entity method uses latest config.""" """Test Google config exposing entity method uses latest config."""

View File

@ -104,7 +104,7 @@ async def test_hassio_discovery_startup_done(hass, aioclient_mock, hassio_client
await async_setup_component(hass, "hassio", {}) await async_setup_component(hass, "hassio", {})
await hass.async_block_till_done() await hass.async_block_till_done()
assert aioclient_mock.call_count == 3 assert aioclient_mock.call_count == 2
assert mock_mqtt.called assert mock_mqtt.called
mock_mqtt.assert_called_with( mock_mqtt.assert_called_with(
{ {

View File

@ -222,11 +222,8 @@ class TestComponentHistory(unittest.TestCase):
# will happen with encoding a native state # will happen with encoding a native state
input_state = states["media_player.test"][1] input_state = states["media_player.test"][1]
orig_last_changed = json.dumps( orig_last_changed = json.dumps(
process_timestamp(input_state.last_changed.replace(microsecond=0)), process_timestamp(input_state.last_changed), cls=JSONEncoder,
cls=JSONEncoder,
).replace('"', "") ).replace('"', "")
if orig_last_changed.endswith("+00:00"):
orig_last_changed = f"{orig_last_changed[:-6]}{recorder.models.DB_TIMEZONE}"
orig_state = input_state.state orig_state = input_state.state
states["media_player.test"][1] = { states["media_player.test"][1] = {
"last_changed": orig_last_changed, "last_changed": orig_last_changed,

View File

@ -22,12 +22,14 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_CLOSE, EVENT_HOMEASSISTANT_CLOSE,
EVENT_HOMEASSISTANT_FINAL_WRITE, EVENT_HOMEASSISTANT_FINAL_WRITE,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STARTED,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REGISTERED,
EVENT_SERVICE_REMOVED, EVENT_SERVICE_REMOVED,
EVENT_STATE_CHANGED, EVENT_STATE_CHANGED,
EVENT_TIME_CHANGED, EVENT_TIME_CHANGED,
EVENT_TIMER_OUT_OF_SYNC, EVENT_TIMER_OUT_OF_SYNC,
MATCH_ALL,
__version__, __version__,
) )
import homeassistant.core as ha import homeassistant.core as ha
@ -1333,3 +1335,35 @@ async def test_additional_data_in_core_config(hass, hass_storage):
} }
await config.async_load() await config.async_load()
assert config.location_name == "Test Name" assert config.location_name == "Test Name"
async def test_start_events(hass):
"""Test events fired when starting Home Assistant."""
hass.state = ha.CoreState.not_running
all_events = []
@ha.callback
def capture_events(ev):
all_events.append(ev.event_type)
hass.bus.async_listen(MATCH_ALL, capture_events)
core_states = []
@ha.callback
def capture_core_state(_):
core_states.append(hass.state)
hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, capture_core_state)
await hass.async_start()
await hass.async_block_till_done()
assert all_events == [
EVENT_CORE_CONFIG_UPDATE,
EVENT_HOMEASSISTANT_START,
EVENT_CORE_CONFIG_UPDATE,
EVENT_HOMEASSISTANT_STARTED,
]
assert core_states == [ha.CoreState.starting, ha.CoreState.running]

View File

@ -5,14 +5,14 @@ from homeassistant.util import aiohttp
async def test_request_json(): async def test_request_json():
"""Test a JSON request.""" """Test a JSON request."""
request = aiohttp.MockRequest(b'{"hello": 2}') request = aiohttp.MockRequest(b'{"hello": 2}', mock_source="test")
assert request.status == 200 assert request.status == 200
assert await request.json() == {"hello": 2} assert await request.json() == {"hello": 2}
async def test_request_text(): async def test_request_text():
"""Test a JSON request.""" """Test a JSON request."""
request = aiohttp.MockRequest(b"hello", status=201) request = aiohttp.MockRequest(b"hello", status=201, mock_source="test")
assert request.status == 201 assert request.status == 201
assert await request.text() == "hello" assert await request.text() == "hello"
@ -20,7 +20,7 @@ async def test_request_text():
async def test_request_post_query(): async def test_request_post_query():
"""Test a JSON request.""" """Test a JSON request."""
request = aiohttp.MockRequest( request = aiohttp.MockRequest(
b"hello=2&post=true", query_string="get=true", method="POST" b"hello=2&post=true", query_string="get=true", method="POST", mock_source="test"
) )
assert request.method == "POST" assert request.method == "POST"
assert await request.post() == {"hello": "2", "post": "true"} assert await request.post() == {"hello": "2", "post": "true"}