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.states = StateMachine(self.bus)
self._config_dir = os.getcwd()
self.config_dir = os.getcwd()
@property
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):
def get_config_path(self, path):
""" 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):
""" Start home assistant. """

View file

@ -1,4 +1,6 @@
"""
homeassistant.bootstrap
~~~~~~~~~~~~~~~~~~~~~~~
Provides methods to bootstrap a home assistant instance.
Each method will return a tuple (bus, statemachine).
@ -14,6 +16,7 @@ from collections import defaultdict
from itertools import chain
import homeassistant
import homeassistant.loader as loader
import homeassistant.components as core_components
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
to_load = [key for key in config.keys() if key != homeassistant.DOMAIN]
loader.prepare(hass)
# Load required components
while to_load:
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 component is not None:
@ -123,7 +128,7 @@ def from_config_dict(config, hass=None):
if group.DOMAIN not in components:
components[group.DOMAIN] = \
core_components.get_component(group.DOMAIN, logger)
loader.get_component(group.DOMAIN)
# Setup the components
if core_components.setup(hass, config):

View file

@ -20,6 +20,7 @@ import importlib
import homeassistant as ha
import homeassistant.util as util
from homeassistant.loader import get_component
# Contains one string or a list of strings, each being an 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_PREV_TRACK = "media_prev_track"
_COMPONENT_CACHE = {}
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
__LOGGER = logging.getLogger(__name__)
def is_on(hass, entity_id=None):
""" Loads up the module to call the is_on method.
If there is no entity id given we will check all. """
logger = logging.getLogger(__name__)
if entity_id:
group = get_component('group', logger)
group = get_component('group')
entity_ids = group.expand_entity_ids([entity_id])
else:
@ -129,7 +64,7 @@ def is_on(hass, entity_id=None):
for entity_id in entity_ids:
domain = util.split_entity_id(entity_id)[0]
module = get_component(domain, logger)
module = get_component(domain)
try:
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 homeassistant as ha
import homeassistant.components.group as group
import homeassistant.loader as loader
from homeassistant.components import (SERVICE_TURN_ON, SERVICE_TURN_OFF,
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,
GROUP_NAME_ALL_LIGHTS)
from homeassistant.util import split_entity_id
@ -22,6 +22,7 @@ DEPENDENCIES = []
def setup(hass, config):
""" Setup a demo environment. """
group = loader.get_component('group')
if config[DOMAIN].get('hide_demo_state') != '1':
hass.states.set('a.Demo_Mode', 'Enabled')
@ -57,7 +58,7 @@ def setup(hass, config):
if ha.CONF_LONGITUDE not in config[ha.DOMAIN]:
config[ha.DOMAIN][ha.CONF_LONGITUDE] = '-117.22743'
get_component('sun').setup(hass, config)
loader.get_component('sun').setup(hass, config)
# Setup fake lights
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