Refactor: loading of components now done in a seperate module + better error reporting
This commit is contained in:
parent
3c37f491b2
commit
a9ee2f9c54
5 changed files with 125 additions and 90 deletions
|
@ -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. """
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
107
homeassistant/loader.py
Normal 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
|
Loading…
Add table
Reference in a new issue