diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index 4f92295fed9..874d868d07e 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -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. """ diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 99fc014e2ec..5ccb6f1fe76 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -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): diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 6acf178acb0..02387f23118 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -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): diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 5e9976a3dc8..a42ae280768 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -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', diff --git a/homeassistant/loader.py b/homeassistant/loader.py new file mode 100644 index 00000000000..bdfae13f102 --- /dev/null +++ b/homeassistant/loader.py @@ -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