Skip to content

Commit

Permalink
Refactor: loading of components now done in a seperate module + bette…
Browse files Browse the repository at this point in the history
…r error reporting
  • Loading branch information
balloob committed Nov 5, 2014
1 parent 3c37f49 commit a9ee2f9
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 90 deletions.
19 changes: 3 additions & 16 deletions homeassistant/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,11 @@ def __init__(self):
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. """
Expand Down
9 changes: 7 additions & 2 deletions homeassistant/bootstrap.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""
homeassistant.bootstrap
~~~~~~~~~~~~~~~~~~~~~~~
Provides methods to bootstrap a home assistant instance.
Each method will return a tuple (bus, statemachine).
Expand All @@ -14,6 +16,7 @@
from itertools import chain

import homeassistant
import homeassistant.loader as loader
import homeassistant.components as core_components
import homeassistant.components.group as group

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand Down
73 changes: 4 additions & 69 deletions homeassistant/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

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'
Expand Down Expand Up @@ -47,80 +48,14 @@
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:
Expand All @@ -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):
Expand Down
7 changes: 4 additions & 3 deletions homeassistant/components/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
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
Expand All @@ -22,6 +22,7 @@

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')
Expand Down Expand Up @@ -57,7 +58,7 @@ def mock_turn_off(service):
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',
Expand Down
107 changes: 107 additions & 0 deletions homeassistant/loader.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit a9ee2f9

Please sign in to comment.