A major change to the bootstrapping of Home Assistant decoupling the knowledge in bootstrap for a more dynamic approach. This refactoring also prepares the code for different configuration backends and the loading components from different places.
175 lines
5.5 KiB
Python
175 lines
5.5 KiB
Python
"""
|
|
Provides methods to bootstrap a home assistant instance.
|
|
|
|
Each method will return a tuple (bus, statemachine).
|
|
|
|
After bootstrapping you can add your own components or
|
|
start by calling homeassistant.start_home_assistant(bus)
|
|
"""
|
|
|
|
import configparser
|
|
import logging
|
|
from collections import defaultdict
|
|
from itertools import chain
|
|
|
|
import homeassistant
|
|
import homeassistant.components as core_components
|
|
import homeassistant.components.group as group
|
|
|
|
|
|
# pylint: disable=too-many-branches
|
|
def from_config_dict(config, hass=None):
|
|
"""
|
|
Tries to configure Home Assistant from a config dict.
|
|
|
|
Dynamically loads required components and its dependencies.
|
|
"""
|
|
if hass is None:
|
|
hass = homeassistant.HomeAssistant()
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Make a copy because we are mutating it.
|
|
# Convert it to defaultdict so components can always have config dict
|
|
config = defaultdict(dict, config)
|
|
|
|
# List of loaded components
|
|
components = {}
|
|
|
|
# List of components to validate
|
|
to_validate = []
|
|
|
|
# List of validated components
|
|
validated = []
|
|
|
|
# List of components we are going to load
|
|
to_load = [key for key in config.keys() if key != homeassistant.DOMAIN]
|
|
|
|
# Load required components
|
|
while to_load:
|
|
domain = to_load.pop()
|
|
|
|
component = core_components.get_component(domain, logger)
|
|
|
|
# if None it does not exist, error already thrown by get_component
|
|
if component is not None:
|
|
components[domain] = component
|
|
|
|
# Special treatment for GROUP, we want to load it as late as
|
|
# possible. We do this by loading it if all other to be loaded
|
|
# modules depend on it.
|
|
if component.DOMAIN == group.DOMAIN:
|
|
pass
|
|
|
|
# Components with no dependencies are valid
|
|
elif not component.DEPENDENCIES:
|
|
validated.append(domain)
|
|
|
|
# If dependencies we'll validate it later
|
|
else:
|
|
to_validate.append(domain)
|
|
|
|
# Make sure to load all dependencies that are not being loaded
|
|
for dependency in component.DEPENDENCIES:
|
|
if dependency not in chain(components.keys(), to_load):
|
|
to_load.append(dependency)
|
|
|
|
# Validate dependencies
|
|
group_added = False
|
|
|
|
while to_validate:
|
|
newly_validated = []
|
|
|
|
for domain in to_validate:
|
|
if all(domain in validated for domain
|
|
in components[domain].DEPENDENCIES):
|
|
|
|
newly_validated.append(domain)
|
|
|
|
# We validated new domains this iteration, add them to validated
|
|
if newly_validated:
|
|
|
|
# Add newly validated domains to validated
|
|
validated.extend(newly_validated)
|
|
|
|
# remove domains from to_validate
|
|
for domain in newly_validated:
|
|
to_validate.remove(domain)
|
|
|
|
newly_validated.clear()
|
|
|
|
# Nothing validated this iteration. Add group dependency and try again.
|
|
elif not group_added:
|
|
group_added = True
|
|
validated.append(group.DOMAIN)
|
|
|
|
# Group has already been added and we still can't validate all.
|
|
# Report missing deps as error and skip loading of these domains
|
|
else:
|
|
for domain in to_validate:
|
|
missing_deps = [dep for dep in components[domain].DEPENDENCIES
|
|
if dep not in validated]
|
|
|
|
logger.error(
|
|
"Could not validate all dependencies for {}: {}".format(
|
|
domain, ", ".join(missing_deps)))
|
|
|
|
break
|
|
|
|
# Setup the components
|
|
if core_components.setup(hass, config):
|
|
logger.info("Home Assistant core initialized")
|
|
|
|
for domain in validated:
|
|
component = components[domain]
|
|
|
|
try:
|
|
if component.setup(hass, config):
|
|
logger.info("component {} initialized".format(domain))
|
|
else:
|
|
logger.error(
|
|
"component {} failed to initialize".format(domain))
|
|
|
|
except Exception: # pylint: disable=broad-except
|
|
logger.exception(
|
|
"Error during setup of component {}".format(domain))
|
|
|
|
else:
|
|
logger.error(("Home Assistant core failed to initialize. "
|
|
"Further initialization aborted."))
|
|
|
|
return hass
|
|
|
|
|
|
def from_config_file(config_path, hass=None, enable_logging=True):
|
|
"""
|
|
Reads the configuration file and tries to start all the required
|
|
functionality. Will add functionality to 'hass' parameter if given,
|
|
instantiates a new Home Assistant object if 'hass' is not given.
|
|
"""
|
|
if enable_logging:
|
|
# Setup the logging for home assistant.
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
# Log errors to a file
|
|
err_handler = logging.FileHandler("home-assistant.log",
|
|
mode='w', delay=True)
|
|
err_handler.setLevel(logging.ERROR)
|
|
err_handler.setFormatter(
|
|
logging.Formatter('%(asctime)s %(name)s: %(message)s',
|
|
datefmt='%H:%M %d-%m-%y'))
|
|
logging.getLogger('').addHandler(err_handler)
|
|
|
|
# Read config
|
|
config = configparser.ConfigParser()
|
|
config.read(config_path)
|
|
|
|
config_dict = {}
|
|
|
|
for section in config.sections():
|
|
config_dict[section] = {}
|
|
|
|
for key, val in config.items(section):
|
|
config_dict[section][key] = val
|
|
|
|
return from_config_dict(config_dict, hass)
|