8000 Allow reloading automation without restarting HA by balloob · Pull Request #3002 · home-assistant/core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Allow reloading automation without restarting HA #3002

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 71 additions & 58 deletions homeassistant/bootstrap.py
8000
Original file line number Diff line number Diff line change
Expand Up 8000 @@ -90,67 +90,12 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool:
domain, domain)
return False

component = loader.get_component(domain)
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components]
config = prepare_setup_component(hass, config, domain)

if missing_deps:
_LOGGER.error(
'Not initializing %s because not all dependencies loaded: %s',
domain, ", ".join(missing_deps))
return False

if hasattr(component, 'CONFIG_SCHEMA'):
try:
config = component.CONFIG_SCHEMA(config)
except vol.MultipleInvalid as ex:
log_exception(ex, domain, config)
return False

elif hasattr(component, 'PLATFORM_SCHEMA'):
platforms = []
for p_name, p_config in config_per_platform(config, domain):
# Validate component specific platform schema
try:
p_validated = component.PLATFORM_SCHEMA(p_config)
except vol.MultipleInvalid as ex:
log_exception(ex, domain, p_config)
return False

# Not all platform components follow same pattern for platforms
# So if p_name is None we are not going to validate platform
# (the automation component is one of them)
if p_name is None:
platforms.append(p_validated)
continue

platform = prepare_setup_platform(hass, config, domain,
p_name)

if platform is None:
return False

# Validate platform specific schema
if hasattr(platform, 'PLATFORM_SCHEMA'):
try:
p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.MultipleInvalid as ex:
log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated)
return False

platforms.append(p_validated)

# Create a copy of the configuration with all config for current
# component removed and add validated config back in.
filter_keys = extract_domain_configs(config, domain)
config = {key: value for key, value in config.items()
if key not in filter_keys}
config[domain] = platforms

if not _handle_requirements(hass, component, domain):
if config is None:
return False

component = loader.get_component(domain)
_CURRENT_SETUP.append(domain)

try:
Expand Down Expand Up @@ -182,6 +127,74 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool:
return True


def prepare_setup_component(hass: core.HomeAssistant, config: dict,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is slightly different from what I envisioned in #2912 - to have a function for config validation only

But I'm taking my sweet time, so let me rebase that PR from this one and continue working on it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went this approach to be in line with the prepare_setup_platform

< 8000 !-- -->

domain: str):
"""Prepare setup of a component and return processed config."""
# pylint: disable=too-many-return-statements
component = loader.get_component(domain)
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components]

if missing_deps:
_LOGGER.error(
'Not initializing %s because not all dependencies loaded: %s',
domain, ", ".join(missing_deps))
return None

if hasattr(component, 'CONFIG_SCHEMA'):
try:
config = component.CONFIG_SCHEMA(config)
except vol.MultipleInvalid as ex:
log_exception(ex, domain, config)
return None

elif hasattr(component, 'PLATFORM_SCHEMA'):
platforms = []
for p_name, p_config in config_per_platform(config, domain):
# Validate component specific platform schema
try:
p_validated = component.PLATFORM_SCHEMA(p_config)
except vol.MultipleInvalid as ex:
log_exception(ex, domain, p_config)
return None

# Not all platform components follow same pattern for platforms
# So if p_name is None we are not going to validate platform
# (the automation component is one of them)
if p_name is None:
platforms.append(p_validated)
continue

platform = prepare_setup_platform(hass, config, domain,
p_name)

if platform is None:
return None

# Validate platform specific schema
if hasattr(platform, 'PLATFORM_SCHEMA'):
try:
p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.MultipleInvalid as ex:
log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated)
return None

platforms.append(p_validated)

# Create a copy of the configuration with all config for current
# component removed and add validated config back in.
filter_keys = extract_domain_configs(config, domain)
config = {key: value for key, value in config.items()
if key not in filter_keys}
config[domain] = platforms

if not _handle_requirements(hass, component, domain):
return None

return config


def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
platform_name: str) -> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup."""
Expand Down
103 changes: 76 additions & 27 deletions homeassistant/components/automation/__init__.py
10000
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
"""
from functools import partial
import logging
import os

import voluptuous as vol

from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.bootstrap import (
prepare_setup_platform, prepare_setup_component)
from homeassistant import config as conf_util
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_TOGGLE)
Expand Down Expand Up @@ -46,6 +49,7 @@
ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_VARIABLES = 'variables'
SERVICE_TRIGGER = 'trigger'
SERVICE_RELOAD = 'reload'

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -112,6 +116,8 @@ def validator(config):
vol.Optional(ATTR_VARIABLES, default={}): dict,
})

RELOAD_SERVICE_SCHEMA = vol.Schema({})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be RELOAD_AUTOMATION_SERVICE_SCHEMA?



def is_on(hass, entity_id=None):
"""
Expand Down Expand Up @@ -148,40 +154,23 @@ def trigger(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_TRIGGER, data)


def reload(hass):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're only reloading the automations: def reload_automation(hass):

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's part of the automation component. So to use it you would:

from homeassistant.components import automation
automation.reload(hass)

Which is in line with the turn_on, turn_off and toggle methods under switch, light, etc

"""Reload the automation from config."""
hass.services.call(DOMAIN, SERVICE_RELOAD)


def setup(hass, config):
"""Setup the automation."""
# pylint: disable=too-many-locals
component = EntityComponent(_LOGGER, DOMAIN, hass)

success = False
for config_key in extract_domain_configs(config, DOMAIN):
conf = config[config_key]

for list_no, config_block in enumerate(conf):
name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key,
list_no)

action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)

if CONF_CONDITION in config_block:
cond_func = _process_if(hass, config, config_block)

if cond_func is None:
continue
else:
def cond_func(variables):
"""Condition will always pass."""
return True

attach_triggers = partial(_process_trigger, hass, config,
config_block.get(CONF_TRIGGER, []), name)
entity = AutomationEntity(name, attach_triggers, cond_func, action)
component.add_entities((entity,))
success = True
success = _process_config(hass, config, component)

if not success:
return False

descriptions = conf_util.load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))

def trigger_service_handler(service_call):
"""Handle automation triggers."""
for entity in component.extract_from_service(service_call):
Expand All @@ -192,11 +181,34 @@ def service_handler(service_call):
for entity in component.extract_from_service(service_call):
getattr(entity, service_call.service)()

def reload_service_handler(service_call):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def reload_automation_service_handler(service_call):

"""Remove all automations and load new ones from config."""
try:
path = conf_util.find_config_file(hass.config.config_dir)
conf = conf_util.load_yaml_config_file(path)
except HomeAssistantError as err:
_LOGGER.error(err)
return

conf = prepare_setup_component(hass, conf, DOMAIN)

if conf is None:
return

component.reset()
_process_config(hass, conf, component)

hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
descriptions.get(SERVICE_TRIGGER),
schema=TRIGGER_SERVICE_SCHEMA)

hass.services.register(DOMAIN, SERVICE_RELOAD, reload_service_handler,
descriptions.get(SERVICE_RELOAD),
schema=RELOAD_SERVICE_SCHEMA)

for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE):
hass.services.register(DOMAIN, service, service_handler,
descriptions.get(service),
schema=SERVICE_SCHEMA)

return True
Expand Down Expand Up @@ -263,6 +275,43 @@ def trigger(self, variables):
self._last_triggered = utcnow()
self.update_ha_state()

def remove(self):
"""Remove automation from HASS."""
self.turn_off()
super().remove()


def _process_config(hass, config, component):
"""Process config and add automations."""
success = False

for config_key in extract_domain_configs(config, DOMAIN):
conf = config[config_key]

for list_no, config_block in enumerate(conf):
name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key,
list_no)

action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)

if CONF_CONDITION in config_block:
cond_func = _process_if(hass, config, config_block)

if cond_func is None:
continue
else:
def cond_func(variables):
"""Condition will always pass."""
return True

attach_triggers = partial(_process_trigger, hass, config,
config_block.get(CONF_TRIGGER, []), name)
entity = AutomationEntity(name, attach_triggers, cond_func, action)
component.add_entities((entity,))
success = True

return success


def _get_action(hass, config, name):
"""Return an action based on a configuration."""
Expand Down
34 changes: 34 additions & 0 deletions homeassistant/components/automation/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
turn_on:
description: Enable an automation.

fields:
entity_id:
description: Name of the automation to turn on.
example: 'automation.notify_home'

turn_off:
description: Disable an automation.

fields:
entity_id:
description: Name of the automation to turn off.
example: 'automation.notify_home'

toggle:
description: Toggle an automation.

fields:
entity_id:
description: Name of the automation to toggle on/off.
example: 'automation.notify_home'

trigger:
description: Trigger the action of an automation.

fields:
entity_id:
description: Name of the automation to trigger.
example: 'automation.notify_home'

reload:
description: Reload the automation configuration.
4 changes: 4 additions & 0 deletions homeassistant/helpers/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ def update_ha_state(self, force_refresh=False):
return self.hass.states.set(
self.entity_id, state, attr, self.force_update)

def remove(self) -> None:
"""Remove entitiy from HASS."""
self.hass.states.remove(self.entity_id)

def _attr_setter(self, name, typ, attr, attrs):
"""Helper method to populate attributes based on properties."""
if attr in attrs:
Expand Down
Loading
0