Refactor: loading of components now done in a seperate module + better error reporting

This commit is contained in:
Paulus Schoutsen 2014-11-04 23:34:19 -08:00
parent 3c37f491b2
commit a9ee2f9c54
5 changed files with 125 additions and 90 deletions

View File

@ -61,24 +61,11 @@ class HomeAssistant(object):
self.services = ServiceRegistry(self.bus, pool) self.services = ServiceRegistry(self.bus, pool)
self.states = StateMachine(self.bus) self.states = StateMachine(self.bus)
self._config_dir = os.getcwd() self.config_dir = os.getcwd()
@property def get_config_path(self, path):
def config_dir(self):
""" Return value of config dir. """
return self._config_dir
@config_dir.setter
def config_dir(self, value):
""" Update value of config dir and ensures it's in Python path. """
self._config_dir = value
# Ensure we can load components from the config dir
sys.path.append(value)
def get_config_path(self, sub_path):
""" Returns path to the file within the config dir. """ """ Returns path to the file within the config dir. """
return os.path.join(self._config_dir, sub_path) return os.path.join(self.config_dir, path)
def start(self): def start(self):
""" Start home assistant. """ """ Start home assistant. """

View File

@ -1,4 +1,6 @@
""" """
homeassistant.bootstrap
~~~~~~~~~~~~~~~~~~~~~~~
Provides methods to bootstrap a home assistant instance. Provides methods to bootstrap a home assistant instance.
Each method will return a tuple (bus, statemachine). Each method will return a tuple (bus, statemachine).
@ -14,6 +16,7 @@ from collections import defaultdict
from itertools import chain from itertools import chain
import homeassistant import homeassistant
import homeassistant.loader as loader
import homeassistant.components as core_components import homeassistant.components as core_components
import homeassistant.components.group as group import homeassistant.components.group as group
@ -46,11 +49,13 @@ def from_config_dict(config, hass=None):
# List of components we are going to load # List of components we are going to load
to_load = [key for key in config.keys() if key != homeassistant.DOMAIN] to_load = [key for key in config.keys() if key != homeassistant.DOMAIN]
loader.prepare(hass)
# Load required components # Load required components
while to_load: while to_load:
domain = to_load.pop() domain = to_load.pop()
component = core_components.get_component(domain, logger) component = loader.get_component(domain)
# if None it does not exist, error already thrown by get_component # if None it does not exist, error already thrown by get_component
if component is not None: if component is not None:
@ -123,7 +128,7 @@ def from_config_dict(config, hass=None):
if group.DOMAIN not in components: if group.DOMAIN not in components:
components[group.DOMAIN] = \ components[group.DOMAIN] = \
core_components.get_component(group.DOMAIN, logger) loader.get_component(group.DOMAIN)
# Setup the components # Setup the components
if core_components.setup(hass, config): if core_components.setup(hass, config):

View File

@ -20,6 +20,7 @@ import importlib
import homeassistant as ha import homeassistant as ha
import homeassistant.util as util import homeassistant.util as util
from homeassistant.loader import get_component
# Contains one string or a list of strings, each being an entity id # Contains one string or a list of strings, each being an entity id
ATTR_ENTITY_ID = 'entity_id' ATTR_ENTITY_ID = 'entity_id'
@ -47,80 +48,14 @@ SERVICE_MEDIA_PAUSE = "media_pause"
SERVICE_MEDIA_NEXT_TRACK = "media_next_track" SERVICE_MEDIA_NEXT_TRACK = "media_next_track"
SERVICE_MEDIA_PREV_TRACK = "media_prev_track" SERVICE_MEDIA_PREV_TRACK = "media_prev_track"
_COMPONENT_CACHE = {} __LOGGER = logging.getLogger(__name__)
def get_component(comp_name, logger=None):
""" Tries to load specified component.
Looks in config dir first, then built-in components.
Only returns it if also found to be valid. """
if comp_name in _COMPONENT_CACHE:
return _COMPONENT_CACHE[comp_name]
# First config dir, then built-in
potential_paths = ['custom_components.{}'.format(comp_name),
'homeassistant.components.{}'.format(comp_name)]
for path in potential_paths:
comp = _get_component(path, logger)
if comp is not None:
if logger is not None:
logger.info("Loaded component {} from {}".format(
comp_name, path))
_COMPONENT_CACHE[comp_name] = comp
return comp
# We did not find a component
if logger is not None:
logger.error(
"Failed to find component {}".format(comp_name))
return None
def _get_component(module, logger):
""" Tries to load specified component.
Only returns it if also found to be valid."""
try:
comp = importlib.import_module(module)
except ImportError:
return None
# Validation if component has required methods and attributes
errors = []
if not hasattr(comp, 'DOMAIN'):
errors.append("Missing DOMAIN attribute")
if not hasattr(comp, 'DEPENDENCIES'):
errors.append("Missing DEPENDENCIES attribute")
if not hasattr(comp, 'setup'):
errors.append("Missing setup method")
if errors:
if logger:
logger.error("Found invalid component {}: {}".format(
module, ", ".join(errors)))
return None
else:
return comp
def is_on(hass, entity_id=None): def is_on(hass, entity_id=None):
""" Loads up the module to call the is_on method. """ Loads up the module to call the is_on method.
If there is no entity id given we will check all. """ If there is no entity id given we will check all. """
logger = logging.getLogger(__name__)
if entity_id: if entity_id:
group = get_component('group', logger) group = get_component('group')
entity_ids = group.expand_entity_ids([entity_id]) entity_ids = group.expand_entity_ids([entity_id])
else: else:
@ -129,7 +64,7 @@ def is_on(hass, entity_id=None):
for entity_id in entity_ids: for entity_id in entity_ids:
domain = util.split_entity_id(entity_id)[0] domain = util.split_entity_id(entity_id)[0]
module = get_component(domain, logger) module = get_component(domain)
try: try:
if module.is_on(hass, entity_id): if module.is_on(hass, entity_id):

View File

@ -7,10 +7,10 @@ Sets up a demo environment that mimics interaction with devices
import random import random
import homeassistant as ha import homeassistant as ha
import homeassistant.components.group as group import homeassistant.loader as loader
from homeassistant.components import (SERVICE_TURN_ON, SERVICE_TURN_OFF, from homeassistant.components import (SERVICE_TURN_ON, SERVICE_TURN_OFF,
STATE_ON, STATE_OFF, ATTR_ENTITY_PICTURE, STATE_ON, STATE_OFF, ATTR_ENTITY_PICTURE,
get_component, extract_entity_ids) extract_entity_ids)
from homeassistant.components.light import (ATTR_XY_COLOR, ATTR_BRIGHTNESS, from homeassistant.components.light import (ATTR_XY_COLOR, ATTR_BRIGHTNESS,
GROUP_NAME_ALL_LIGHTS) GROUP_NAME_ALL_LIGHTS)
from homeassistant.util import split_entity_id from homeassistant.util import split_entity_id
@ -22,6 +22,7 @@ DEPENDENCIES = []
def setup(hass, config): def setup(hass, config):
""" Setup a demo environment. """ """ Setup a demo environment. """
group = loader.get_component('group')
if config[DOMAIN].get('hide_demo_state') != '1': if config[DOMAIN].get('hide_demo_state') != '1':
hass.states.set('a.Demo_Mode', 'Enabled') hass.states.set('a.Demo_Mode', 'Enabled')
@ -57,7 +58,7 @@ def setup(hass, config):
if ha.CONF_LONGITUDE not in config[ha.DOMAIN]: if ha.CONF_LONGITUDE not in config[ha.DOMAIN]:
config[ha.DOMAIN][ha.CONF_LONGITUDE] = '-117.22743' config[ha.DOMAIN][ha.CONF_LONGITUDE] = '-117.22743'
get_component('sun').setup(hass, config) loader.get_component('sun').setup(hass, config)
# Setup fake lights # Setup fake lights
lights = ['light.Bowl', 'light.Ceiling', 'light.TV_Back_light', lights = ['light.Bowl', 'light.Ceiling', 'light.TV_Back_light',

107
homeassistant/loader.py Normal file
View File

@ -0,0 +1,107 @@
"""
homeassistant.loader
~~~~~~~~~~~~~~~~~~~~
Provides methods for loading Home Assistant components.
"""
import sys
import pkgutil
import importlib
import logging
# List of available components
AVAILABLE_COMPONENTS = []
# Dict of loaded components mapped name => module
_COMPONENT_CACHE = {}
_LOGGER = logging.getLogger(__name__)
def prepare(hass):
""" Prepares the loading of components. """
# Ensure we can load custom components from the config dir
sys.path.append(hass.config_dir)
# pylint: disable=import-error
import custom_components
import homeassistant.components as components
AVAILABLE_COMPONENTS.clear()
AVAILABLE_COMPONENTS.extend(
item[1] for item in
pkgutil.iter_modules(components.__path__, 'homeassistant.components.'))
AVAILABLE_COMPONENTS.extend(
item[1] for item in
pkgutil.iter_modules(custom_components.__path__, 'custom_components.'))
def get_component(comp_name):
""" Tries to load specified component.
Looks in config dir first, then built-in components.
Only returns it if also found to be valid. """
if comp_name in _COMPONENT_CACHE:
return _COMPONENT_CACHE[comp_name]
# First check config dir, then built-in
potential_paths = [path for path in
['custom_components.{}'.format(comp_name),
'homeassistant.components.{}'.format(comp_name)]
if path in AVAILABLE_COMPONENTS]
if not potential_paths:
_LOGGER.error("Failed to find component {}".format(comp_name))
return None
for path in potential_paths:
comp = _get_component(path)
if comp is not None:
_LOGGER.info("Loaded component {} from {}".format(
comp_name, path))
_COMPONENT_CACHE[comp_name] = comp
return comp
# We did find components but were unable to load them
_LOGGER.error("Unable to load component {}".format(comp_name))
return None
def _get_component(module):
""" Tries to load specified component.
Only returns it if also found to be valid."""
try:
comp = importlib.import_module(module)
except ImportError:
_LOGGER.exception("Error loading {}".format(module))
return None
# Validation if component has required methods and attributes
errors = []
if not hasattr(comp, 'DOMAIN'):
errors.append("missing DOMAIN attribute")
if not hasattr(comp, 'DEPENDENCIES'):
errors.append("missing DEPENDENCIES attribute")
if not hasattr(comp, 'setup'):
errors.append("missing setup method")
if errors:
_LOGGER.error("Found invalid component {}: {}".format(
module, ", ".join(errors)))
return None
else:
return comp