Ejemplo n.º 1
0
def get_config(path: str) -> dict:
    """
    Loads the config file from path
    If the config file does not exist it will ask the user
    if it should initialise with default configuration
    """
    if not os.path.isfile(path):
        LOGGER.critical("Config file does not exist: %s", path)
        folder = os.path.dirname(path)
        create_new_config = input(
            (f"Shall a default config folder be created "
             f"at {folder}? [Y/n]"))

        if not create_new_config or create_new_config.lower()[0] == "y":
            LOGGER.info(
                "Installing the default configuration to %s",
                folder)
            import homecontrol
            source = pkg_resources.resource_filename(
                homecontrol.__name__, "default_config")
            copy_folder(source, folder)
            LOGGER.info("Running HomeControl with default config")
            return get_config(path=path)

        LOGGER.critical("Terminating")
        sys.exit(1)
    try:
        cfg: dict = YAMLLoader.load(open(path))
    except yaml.YAMLError:
        LOGGER.error("Error in config file", exc_info=True)
        sys.exit(1)
    return cfg
Ejemplo n.º 2
0
    async def reload_config(self, only_domain: Optional[str] = None) -> None:
        """Reloads the configuration and updates where it can"""
        cfg = YAMLLoader.load(
            open(self.cfg_path), cfg_folder=os.path.dirname(self.cfg_path))

        LOGGER.info("Reloading the configuration")
        for domain, raw_domain_config in cfg.items():
            if only_domain and domain != only_domain:
                continue
            if raw_domain_config != self.cfg.get(domain, None):
                LOGGER.info("New configuration detected for domain %s", domain)

                if not self.domains.get(domain):
                    LOGGER.warning(
                        "Configuration domain %s is not reloadable", domain)
                    continue
                try:
                    domain_config = self.validate_domain_config(
                        domain, raw_domain_config)
                except vol.Error:
                    continue

                self.cfg[domain] = raw_domain_config

                if hasattr(self.domains.get(domain, None),
                           "apply_configuration"):
                    handler = self.domains[domain]
                    await handler.apply_configuration(
                        domain, domain_config)

                LOGGER.info("Configuration for domain %s updated", domain)

        LOGGER.info("Completed updating the configuration")
Ejemplo n.º 3
0
def get_config(path: str) -> dict:
    """
    Loads the config file from path
    If the config file does not exist it will ask the user
    if it should initialise with default configuration
    """
    folder = os.path.dirname(path)
    if not os.path.isfile(path):
        LOGGER.critical("Config file does not exist: %s", path)
        create_new_config = input((f"Shall a default config folder be created "
                                   f"at {folder}? [Y/n]"))

        if not create_new_config or create_new_config.lower()[0] == "y":
            LOGGER.info("Installing the default configuration to %s", folder)
            import homecontrol
            source = pkg_resources.resource_filename(homecontrol.__name__,
                                                     "default_config")
            copy_folder(source, folder)
            LOGGER.info("Running HomeControl with default config")
            return get_config(path=path)

        LOGGER.critical("Terminating")
        sys.exit(1)
    try:
        cfg: dict = YAMLLoader.load(open(path), cfg_folder=folder)
    except yaml.YAMLError:
        LOGGER.error("Error in config file", exc_info=True)
        sys.exit(1)
    return cfg
Ejemplo n.º 4
0
    async def load_file_module(self, mod_path: str,
                               name: str) -> (Module, Exception):
        """
        Loads a module from a file and initialises it

        Returns a Module object
        """
        try:
            assert os.path.isfile(mod_path)
        except AssertionError as error:
            LOGGER.warning("Module could not be loaded: %s at %s", name,
                           mod_path)
            self.core.event_engine.broadcast("module_not_loaded",
                                             exception=error)
            return error

        spec = importlib.util.spec_from_file_location(name, mod_path)
        mod = importlib.util.module_from_spec(spec)
        mod.resource_folder = None
        mod.event = self.core.event_engine.register
        mod.tick = self.core.tick_engine.tick
        spec.loader.exec_module(mod)
        if not hasattr(mod, "Module"):
            mod.Module = type("Module_" + name, (Module, ), {})
        else:
            mod.Module = type("Module_" + name, (mod.Module, Module), {})

        cfg = (mod.SPEC if isinstance(mod.SPEC, dict) else YAMLLoader.load(
            mod.SPEC)) if hasattr(mod, "SPEC") else {}

        return await self._load_module_object(cfg, name, mod_path, mod)
Ejemplo n.º 5
0
    async def reload_config(self) -> None:
        """Reloads the configuration and updates where it can"""
        cfg = YAMLLoader.load(open(self.cfg_path))

        LOGGER.info("Updating the configuration")
        for domain, domain_config in cfg.items():
            if domain_config != self.cfg.get(domain, None):
                LOGGER.info("New configuration detected for domain %s", domain)

                if not self.domain_reloadable[domain]:
                    LOGGER.error(
                        "Configuration domain %s is not reloadable", domain)
                    continue
                try:
                    domain_config = await self.approve_domain_config(
                        domain,
                        domain_config,
                        initial=False)
                except (vol.Error, ConfigurationNotApproved):
                    continue

                self.cfg[domain] = domain_config

                if hasattr(self.registered_handlers.get(domain, None),
                           "apply_new_configuration"):
                    handler = self.registered_handlers[domain]
                    await handler.apply_new_configuration(
                        domain, domain_config)

                LOGGER.info("Configuration for domain %s updated", domain)

        LOGGER.info("Completed updating the configuration")
Ejemplo n.º 6
0
    async def reload_config(self) -> None:
        """Reloads the configuration and updates where it can"""
        cfg = YAMLLoader.load(open(self.cfg_path),
                              cfg_folder=os.path.dirname(self.cfg_path))

        LOGGER.info("Updating the configuration")
        for domain, domain_config in cfg.items():
            if domain_config != self.cfg.get(domain, None):
                LOGGER.info("New configuration detected for domain %s", domain)

                if not self.domain_reloadable[domain]:
                    LOGGER.error("Configuration domain %s is not reloadable",
                                 domain)
                    continue
                try:
                    domain_config = await self.approve_domain_config(
                        domain, domain_config, initial=False)
                except (vol.Error, ConfigurationNotApproved):
                    continue

                self.cfg[domain] = domain_config

                if hasattr(self.registered_handlers.get(domain, None),
                           "apply_new_configuration"):
                    handler = self.registered_handlers[domain]
                    await handler.apply_new_configuration(
                        domain, domain_config)

                LOGGER.info("Configuration for domain %s updated", domain)

        LOGGER.info("Completed updating the configuration")
Ejemplo n.º 7
0
    async def load_file_module(self,
                               mod_path: str,
                               name: str) -> (Module, Exception):
        """
        Loads a module from a file and initialises it

        Returns a Module object
        """
        try:
            assert os.path.isfile(mod_path)
        except AssertionError as error:
            LOGGER.warning(
                "Module could not be loaded: %s at %s", name, mod_path)
            self.core.event_engine.broadcast(
                "module_not_loaded", exception=error)
            return error

        spec = importlib.util.spec_from_file_location(name, mod_path)
        mod = importlib.util.module_from_spec(spec)
        mod.resource_folder = None
        mod.event = self.core.event_engine.register
        mod.tick = self.core.tick_engine.tick
        spec.loader.exec_module(mod)
        if not hasattr(mod, "Module"):
            mod.Module = type("Module_" + name, (Module,), {})
        else:
            mod.Module = type("Module_" + name, (mod.Module, Module), {})

        cfg = (mod.SPEC if isinstance(mod.SPEC, dict) else YAMLLoader.load(
            mod.SPEC)) if hasattr(mod, "SPEC") else {}

        return await self._load_module_object(cfg, name, mod_path, mod)
Ejemplo n.º 8
0
    async def load_folder_module(self, path: str,
                                 name: str) -> (Module, Exception):
        """
        Loads a module from a folder and initialises it

        It also takes care of pip requirements

        Returns a Module object
        """
        mod_path = os.path.join(path, "module.py")
        cfg_path = os.path.join(path, "module.yaml")
        try:
            assert os.path.isdir(path)
            assert os.path.isfile(mod_path)
            assert os.path.isfile(cfg_path)
        except AssertionError as error:
            LOGGER.warning("Module could not be loaded: %s at %s", name, path)
            self.core.event_engine.broadcast("module_not_loaded",
                                             exception=error,
                                             name=name)
            return error

        cfg = YAMLLoader.load(open(cfg_path))

        unsatisfied_pip_dependencies = set()
        for requirement in cfg.get("pip-requirements", []):
            matcher = NormalizedMatcher(requirement)
            if matcher.name not in self.installed_requirements:
                unsatisfied_pip_dependencies.add(requirement)
                continue
            if not matcher.match(self.installed_requirements[matcher.name]):
                unsatisfied_pip_dependencies.add(requirement)

        if unsatisfied_pip_dependencies:
            process = subprocess.Popen([
                sys.executable, "-m", "pip", "install",
                *unsatisfied_pip_dependencies
            ])
            if process.wait():
                LOGGER.warning("Module could not be loaded: %s at %s", name,
                               path)
                self.core.event_engine.broadcast("module_not_loaded",
                                                 exception=PipInstallError(),
                                                 name=name)
                return

        spec = importlib.util.spec_from_file_location(name, mod_path)
        mod = importlib.util.module_from_spec(spec)
        mod.resource_folder = path
        mod.event = self.core.event_engine.register
        mod.tick = self.core.tick_engine.tick
        sys.path.append(path)
        spec.loader.exec_module(mod)
        sys.path.remove(path)
        return await self._load_module_object(cfg, name, path, mod)
Ejemplo n.º 9
0
    async def load_folder_module(self,
                                 path: str,
                                 name: str) -> (Module, Exception):
        """
        Loads a module from a folder and initialises it

        It also takes care of pip requirements

        Returns a Module object
        """
        mod_path = os.path.join(path, "module.py")
        cfg_path = os.path.join(path, "module.yaml")
        try:
            assert os.path.isdir(path)
            assert os.path.isfile(mod_path)
            assert os.path.isfile(cfg_path)
        except AssertionError as error:
            LOGGER.warning("Module could not be loaded: %s at %s", name, path)
            self.core.event_engine.broadcast(
                "module_not_loaded", exception=error, name=name)
            return error

        cfg = YAMLLoader.load(open(cfg_path))

        unsatisfied_pip_dependencies = set()
        for requirement in cfg.get("pip-requirements", []):
            matcher = NormalizedMatcher(requirement)
            if matcher.name not in self.installed_requirements:
                unsatisfied_pip_dependencies.add(requirement)
                continue
            if not matcher.match(self.installed_requirements[matcher.name]):
                unsatisfied_pip_dependencies.add(requirement)

        if unsatisfied_pip_dependencies:
            process = subprocess.Popen(
                [sys.executable, "-m", "pip", "install",
                 *unsatisfied_pip_dependencies])
            if process.wait():
                LOGGER.warning(
                    "Module could not be loaded: %s at %s", name, path)
                self.core.event_engine.broadcast(
                    "module_not_loaded",
                    exception=PipInstallError(),
                    name=name)
                return

        spec = importlib.util.spec_from_file_location(name, mod_path)
        mod = importlib.util.module_from_spec(spec)
        mod.resource_folder = path
        mod.event = self.core.event_engine.register
        mod.tick = self.core.tick_engine.tick
        sys.path.append(path)
        spec.loader.exec_module(mod)
        sys.path.remove(path)
        return await self._load_module_object(cfg, name, path, mod)
def get_requirements() -> list:
    """
    Lists the pip requirements for the builtin folder modules
    """
    output = []

    for node in os.listdir(MODULE_FOLDER):
        if node == "__pycache__":
            continue
        mod_path = os.path.join(MODULE_FOLDER, node)

        if os.path.isdir(mod_path):
            cfg_path = os.path.join(mod_path, "module.yaml")
            cfg = YAMLLoader.load(open(cfg_path))
            output.extend(cfg.get("pip-requirements", []))

    return output
    async def load_folder_module(self, path: str,
                                 name: str) -> (Module, Exception):
        """
        Loads a module from a folder and initialises it

        It also takes care of pip requirements

        Returns a Module object
        """
        mod_path = os.path.join(path, "module.py")
        spec_path = os.path.join(path, "module.yaml")
        parent_path = os.path.dirname(path)

        try:
            assert os.path.isdir(path)
            assert os.path.isfile(mod_path)
            assert os.path.isfile(spec_path)
        except AssertionError as error:
            LOGGER.warning("Module could not be loaded: %s at %s", name, path)
            self.core.event_engine.broadcast("module_not_loaded",
                                             exception=error,
                                             name=name)
            return error

        spec = YAMLLoader.load(open(spec_path))

        try:
            ensure_pip_requirements(spec.get("pip-requirements", []))
        except PipInstallError as e:
            LOGGER.warning("Module could not be loaded: %s at %s", name, path)
            self.core.event_engine.broadcast("module_not_loaded",
                                             exception=e,
                                             name=name)
            return

        mod_name = f"homecontrol_{os.path.basename(parent_path)}.{name}"
        mod_spec = importlib.util.spec_from_file_location(
            name, mod_path, submodule_search_locations=[path])
        mod = importlib.util.module_from_spec(mod_spec)
        mod.__package__ = mod_name
        sys.modules[mod_name] = mod
        mod.SPEC = spec
        mod.resource_folder = path
        mod_spec.loader.exec_module(mod)
        return await self._load_module_object(spec, name, path, mod)
Ejemplo n.º 12
0
def get_config(directory: str) -> dict:
    """
    Loads the config from path
    If the config file does not exist it will ask the user
    if it should initialise with default configuration
    """
    file = os.path.join(directory, CONFIG_FILE_NAME)
    if not os.path.isfile(file):
        LOGGER.warning("Config file does not exist: %s", file)
        # Don't ask if HomeControl is not interactive.
        # It's quite likely a Docker container or daemon
        create_new_config = not sys.stdout.isatty()

        if not create_new_config:
            user_choice = input((f"Shall a default config folder be created "
                                 f"at {directory}? [Y/n]"))
            create_new_config = (not user_choice
                                 or user_choice.lower()[0] == "y")

        if create_new_config:
            LOGGER.info("Installing the default configuration to %s",
                        directory)
            # pylint: disable=import-outside-toplevel
            from homecontrol import __name__ as package_name
            source = pkg_resources.resource_filename(package_name,
                                                     "default_config")
            copy_folder(source, directory)
            LOGGER.info("Running HomeControl with default config")
            return get_config(directory)

        LOGGER.critical("Terminating")
        sys.exit(1)
    try:
        cfg: dict = YAMLLoader.load(open(file), cfg_folder=directory)
    except yaml.YAMLError:
        LOGGER.error("Error in config file", exc_info=True)
        sys.exit(1)
    return cfg
Ejemplo n.º 13
0
    def _folder_spec(self):
        spec_path = os.path.join(self.mod_path, "module.yaml")
        spec = (YAMLLoader.load(open(spec_path))
                if os.path.isfile(spec_path) else {})

        return spec, None