Add service to reload scenes from configuration.yaml (#25680)
* Allow reloading scenes * Update requirements * address comments * fix typing * fix tests * Update homeassistant/components/homeassistant/scene.py Co-Authored-By: Martin Hjelmare <marhje52@kth.se> * Address comments
This commit is contained in:
parent
0449132c35
commit
7a90808e52
9 changed files with 113 additions and 12 deletions
|
@ -1,5 +1,6 @@
|
|||
"""Allow users to set and activate scenes."""
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -11,12 +12,19 @@ from homeassistant.const import (
|
|||
CONF_PLATFORM,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
SERVICE_RELOAD,
|
||||
)
|
||||
from homeassistant.core import State, DOMAIN
|
||||
from homeassistant import config as conf_util
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.loader import async_get_integration
|
||||
from homeassistant.helpers import (
|
||||
config_per_platform,
|
||||
config_validation as cv,
|
||||
entity_platform,
|
||||
)
|
||||
from homeassistant.core import State
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.state import HASS_DOMAIN, async_reproduce_state
|
||||
from homeassistant.components.scene import STATES, Scene
|
||||
|
||||
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, STATES, Scene
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
@ -37,19 +45,63 @@ PLATFORM_SCHEMA = vol.Schema(
|
|||
)
|
||||
|
||||
SCENECONFIG = namedtuple("SceneConfig", [CONF_NAME, STATES])
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up home assistant scene entries."""
|
||||
scene_config = config.get(STATES)
|
||||
_process_scenes_config(hass, async_add_entities, config)
|
||||
|
||||
# This platform can be loaded multiple times. Only first time register the service.
|
||||
if hass.services.has_service(SCENE_DOMAIN, SERVICE_RELOAD):
|
||||
return
|
||||
|
||||
# Store platform for later.
|
||||
platform = entity_platform.current_platform.get()
|
||||
|
||||
async def reload_config(call):
|
||||
"""Reload the scene config."""
|
||||
try:
|
||||
conf = await conf_util.async_hass_config_yaml(hass)
|
||||
except HomeAssistantError as err:
|
||||
_LOGGER.error(err)
|
||||
return
|
||||
|
||||
integration = await async_get_integration(hass, SCENE_DOMAIN)
|
||||
|
||||
conf = await conf_util.async_process_component_config(hass, conf, integration)
|
||||
|
||||
if not conf or not platform:
|
||||
return
|
||||
|
||||
await platform.async_reset()
|
||||
|
||||
# Extract only the config for the Home Assistant platform, ignore the rest.
|
||||
for p_type, p_config in config_per_platform(conf, SCENE_DOMAIN):
|
||||
if p_type != DOMAIN:
|
||||
continue
|
||||
|
||||
_process_scenes_config(hass, async_add_entities, p_config)
|
||||
|
||||
hass.helpers.service.async_register_admin_service(
|
||||
SCENE_DOMAIN, SERVICE_RELOAD, reload_config
|
||||
)
|
||||
|
||||
|
||||
def _process_scenes_config(hass, async_add_entities, config):
|
||||
"""Process multiple scenes and add them."""
|
||||
scene_config = config[STATES]
|
||||
|
||||
# Check empty list
|
||||
if not scene_config:
|
||||
return
|
||||
|
||||
async_add_entities(
|
||||
HomeAssistantScene(hass, _process_config(scene)) for scene in scene_config
|
||||
HomeAssistantScene(hass, _process_scene_config(scene)) for scene in scene_config
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def _process_config(scene_config):
|
||||
def _process_scene_config(scene_config):
|
||||
"""Process passed in config into a format to work with.
|
||||
|
||||
Async friendly.
|
||||
|
|
|
@ -5,6 +5,7 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import DOMAIN as HA_DOMAIN
|
||||
from homeassistant.const import CONF_PLATFORM, SERVICE_TURN_ON
|
||||
from homeassistant.helpers.config_validation import ENTITY_SERVICE_SCHEMA
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
@ -60,6 +61,10 @@ async def async_setup(hass, config):
|
|||
component = hass.data[DOMAIN] = EntityComponent(logger, DOMAIN, hass)
|
||||
|
||||
await component.async_setup(config)
|
||||
# Ensure Home Assistant platform always loaded.
|
||||
await component.async_setup_platform(
|
||||
HA_DOMAIN, {"platform": "homeasistant", STATES: []}
|
||||
)
|
||||
|
||||
async def async_handle_scene_service(service):
|
||||
"""Handle calls to the switch services."""
|
||||
|
|
|
@ -114,7 +114,7 @@ class EntityComponent:
|
|||
# Look in config for Domain, Domain 2, Domain 3 etc and load them
|
||||
tasks = []
|
||||
for p_type, p_config in config_per_platform(config, self.domain):
|
||||
tasks.append(self._async_setup_platform(p_type, p_config))
|
||||
tasks.append(self.async_setup_platform(p_type, p_config))
|
||||
|
||||
if tasks:
|
||||
await asyncio.wait(tasks)
|
||||
|
@ -123,7 +123,7 @@ class EntityComponent:
|
|||
# Refer to: homeassistant.components.discovery.load_platform()
|
||||
async def component_platform_discovered(platform, info):
|
||||
"""Handle the loading of a platform."""
|
||||
await self._async_setup_platform(platform, {}, info)
|
||||
await self.async_setup_platform(platform, {}, info)
|
||||
|
||||
discovery.async_listen_platform(
|
||||
self.hass, self.domain, component_platform_discovered
|
||||
|
@ -212,10 +212,13 @@ class EntityComponent:
|
|||
|
||||
self.hass.services.async_register(self.domain, name, handle_service, schema)
|
||||
|
||||
async def _async_setup_platform(
|
||||
async def async_setup_platform(
|
||||
self, platform_type, platform_config, discovery_info=None
|
||||
):
|
||||
"""Set up a platform for this component."""
|
||||
if self.config is None:
|
||||
raise RuntimeError("async_setup needs to be called first")
|
||||
|
||||
platform = await async_prepare_setup_platform(
|
||||
self.hass, self.config, self.domain, platform_type
|
||||
)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"""Class to manage the entities for a single platform."""
|
||||
import asyncio
|
||||
from contextvars import ContextVar
|
||||
from typing import Optional
|
||||
|
||||
from homeassistant.const import DEVICE_DEFAULT_NAME
|
||||
from homeassistant.core import callback, valid_entity_id, split_entity_id
|
||||
|
@ -127,6 +129,7 @@ class EntityPlatform:
|
|||
|
||||
async_create_setup_task creates a coroutine that sets up platform.
|
||||
"""
|
||||
current_platform.set(self)
|
||||
logger = self.logger
|
||||
hass = self.hass
|
||||
full_name = "{}.{}".format(self.domain, self.platform_name)
|
||||
|
@ -457,3 +460,8 @@ class EntityPlatform:
|
|||
|
||||
if tasks:
|
||||
await asyncio.wait(tasks)
|
||||
|
||||
|
||||
current_platform: ContextVar[Optional[EntityPlatform]] = ContextVar(
|
||||
"current_platform", default=None
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@ async_timeout==3.0.1
|
|||
attrs==19.1.0
|
||||
bcrypt==3.1.7
|
||||
certifi>=2019.6.16
|
||||
contextvars==2.4;python_version<"3.7"
|
||||
cryptography==2.7
|
||||
distro==1.4.0
|
||||
hass-nabucasa==0.16
|
||||
|
|
|
@ -5,6 +5,7 @@ async_timeout==3.0.1
|
|||
attrs==19.1.0
|
||||
bcrypt==3.1.7
|
||||
certifi>=2019.6.16
|
||||
contextvars==2.4;python_version<"3.7"
|
||||
importlib-metadata==0.18
|
||||
jinja2>=2.10.1
|
||||
PyJWT==1.7.1
|
||||
|
|
1
setup.py
1
setup.py
|
@ -37,6 +37,7 @@ REQUIRES = [
|
|||
"attrs==19.1.0",
|
||||
"bcrypt==3.1.7",
|
||||
"certifi>=2019.6.16",
|
||||
'contextvars==2.4;python_version<"3.7"',
|
||||
"importlib-metadata==0.18",
|
||||
"jinja2>=2.10.1",
|
||||
"PyJWT==1.7.1",
|
||||
|
|
30
tests/components/homeassistant/test_scene.py
Normal file
30
tests/components/homeassistant/test_scene.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
"""Test Home Assistant scenes."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
||||
async def test_reload_config_service(hass):
|
||||
"""Test the reload config service."""
|
||||
assert await async_setup_component(hass, "scene", {})
|
||||
|
||||
with patch(
|
||||
"homeassistant.config.load_yaml_config_file",
|
||||
autospec=True,
|
||||
return_value={"scene": {"name": "Hallo", "entities": {"light.kitchen": "on"}}},
|
||||
), patch("homeassistant.config.find_config_file", return_value=""):
|
||||
await hass.services.async_call("scene", "reload", blocking=True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("scene.hallo") is not None
|
||||
|
||||
with patch(
|
||||
"homeassistant.config.load_yaml_config_file",
|
||||
autospec=True,
|
||||
return_value={"scene": {"name": "Bye", "entities": {"light.kitchen": "on"}}},
|
||||
), patch("homeassistant.config.find_config_file", return_value=""):
|
||||
await hass.services.async_call("scene", "reload", blocking=True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("scene.hallo") is None
|
||||
assert hass.states.get("scene.bye") is not None
|
|
@ -116,7 +116,7 @@ async def test_setup_recovers_when_setup_raises(hass):
|
|||
|
||||
|
||||
@asynctest.patch(
|
||||
"homeassistant.helpers.entity_component.EntityComponent" "._async_setup_platform",
|
||||
"homeassistant.helpers.entity_component.EntityComponent" ".async_setup_platform",
|
||||
return_value=mock_coro(),
|
||||
)
|
||||
@asynctest.patch(
|
||||
|
|
Loading…
Add table
Reference in a new issue