diff --git a/src/config/hotreloader.py b/src/config/hotdogtrigger.py similarity index 53% rename from src/config/hotreloader.py rename to src/config/hotdogtrigger.py index 0ca4010..86f8c80 100644 --- a/src/config/hotreloader.py +++ b/src/config/hotdogtrigger.py @@ -1,5 +1,7 @@ import logging +from config.reloadtrigger import ReloadTrigger + try: from watchdog.observers import Observer from watchdog.events import ( @@ -8,11 +10,11 @@ try: FileModifiedEvent, ) - class HotReloader(FileSystemEventHandler): - """Might be unbound, if watchdog is not installed. Check if equal to None before use.""" + class HotDogTrigger(ReloadTrigger, FileSystemEventHandler): + """Trigger for config files. Might be unbound, if watchdog is not installed. Check if equal to None before use.""" - def __init__(self, path): - self.path = path + def __init__(self, path: str): + super().__init__(path) self.observer = Observer() self.observer.schedule(self, path, recursive=True) self.observer.start() @@ -22,13 +24,13 @@ try: self.observer.join() def on_modified(self, event: FileModifiedEvent): - logging.info("Config file modified. Triggering hot reload.") + self.__trigger_reload() def on_created(self, event: FileCreatedEvent): - logging.info("New config file created. Triggering hot reload.") + self.__trigger_reload() - logging.debug("Watchdog imported successfully. Hot reloading available.") + logging.debug("Watchdog imported successfully. HotDogTrigger available.") except ImportError: - logging.info("Watchdog is not installed. Hot reloading unavailable.") + logging.info("Watchdog is not installed. HotDogTrigger unavailable.") diff --git a/src/config/parser.py b/src/config/parser.py new file mode 100644 index 0000000..5bef0ea --- /dev/null +++ b/src/config/parser.py @@ -0,0 +1,49 @@ +import abc +from typing import Any + +from models.home import Home + + +class ConfigParser: + def get_config(self) -> Home: # TODO: Check type + """Load and parse the config.""" + return self.__parse_config(self.__load_config()) + + @abc.abstractmethod + def __load_config(self) -> Any: + """Load the config.""" + raise NotImplementedError() + + @abc.abstractmethod + def __parse_config(self, config) -> Home: + """Parse the config.""" + raise NotImplementedError() + + +class FileConfigParser(ConfigParser): + """Scaffoling for parsers based on files.""" + + path: str + """Path to the config file.""" + + def __init__(self, path: str): + self.path = path + + +class YAMLConfig(FileConfigParser): + """Loads and parses config from a YAML file.""" + + def __init__(self, path: str): + super().__init__(path) + + def __load_config(self) -> dict: + """Loads config from a YAML file.""" + import yaml + + with open(self.path, "r") as file: + return yaml.safe_load(file) + + def __parse_config(self, config: dict) -> Home: + """Parses config in dict format. Usually based on JSON or YAML.""" + # TODO: Implement + raise NotImplementedError() diff --git a/src/config/reloadtrigger.py b/src/config/reloadtrigger.py new file mode 100644 index 0000000..c0129a0 --- /dev/null +++ b/src/config/reloadtrigger.py @@ -0,0 +1,26 @@ +class ReloadTrigger(object): + """Abstract interface for config hot reloading.""" + + path: str + """Path to the config file.""" + + callbacks: list + """List of functions to callback on reload of config.""" + + def __init__(self, path: str): + """Initialize the trigger. + + Args: + path (str): Path to the config file. + """ + self.path = path + self.callbacks = [] + + def on_reload(self, callback): + """Register a callback to be called when the config is reloaded.""" + self.callbacks.append(callback) + + def __trigger_reload(self): + """Trigger a reload of the config.""" + for callback in self.callbacks: + callback() diff --git a/src/mash.py b/src/mash.py new file mode 100644 index 0000000..1a7f995 --- /dev/null +++ b/src/mash.py @@ -0,0 +1,48 @@ +from config.parser import ConfigParser +from config.reloadtrigger import ReloadTrigger +from models.home import Home + + +class MaSH: + """Max' Smart Home. Smart home automation framework.""" + + config_parser: ConfigParser + """Parser for the config file.""" + + config_trigger: list[ReloadTrigger] + """Active triggers for reloading the config.""" + + home: Home + """Home to control and make smart.""" + + def __init__(self, config_parser: ConfigParser) -> None: + """Initialize the framework. + + Args: + config_parser (ConfigParser): Parser for the config file. + """ + self.config_parser = config_parser + self.config_trigger = [] + + self.reload_config() + + def reload_config(self) -> None: + """Reload the config.""" + self.home = self.config_parser.get_config() # TODO: Check type + + def register_config_trigger(self, trigger: ReloadTrigger) -> bool: + """Register a trigger for hot-reloading the config. + + Args: + trigger (ReloadTrigger): Trigger for hot-reloading the config based on ReloadTrigger. + + Returns: + bool: True if the trigger was registered, False if not. + """ + if trigger is None or trigger in self.config_trigger: + return False + + trigger.on_reload(self.reload_config) + self.config_trigger.append(trigger) + + return True