""" 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 os 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 # Make sure we load groups if not in list yet. if not group_added: validated.append(group.DOMAIN) # 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 hass is None: hass = homeassistant.HomeAssistant() # Set config dir to directory holding config file hass.config_dir = os.path.abspath(os.path.dirname(config_path)) if enable_logging: # Setup the logging for home assistant. logging.basicConfig(level=logging.INFO) # Log errors to a file if we have write access to file or config dir err_log_path = hass.get_config_path("home-assistant.log") err_path_exists = os.path.isfile(err_log_path) if (err_path_exists and os.access(err_log_path, os.W_OK)) or \ (not err_path_exists and os.access(hass.config_dir, os.W_OK)): err_handler = logging.FileHandler( err_log_path, 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) else: logging.getLogger(__name__).error( "Unable to setup error log {} (access denied)".format( err_log_path)) # 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)