-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Ping handling should be more robust - Fixed an issue where HABApp would not reconnect after an error - Doc fixes by Rosi2143 - Added DictParameter which can be accessed like a dict - Reworked file handling: It is now possible to specify files as dependencies which will be loaded before the specifying file and it is possible to automatically reload a file if another file changed. The topics for file handling changed, too.
- Loading branch information
1 parent
983d08b
commit 1f3fb2c
Showing
52 changed files
with
1,400 additions
and
541 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
__version__ = '0.16.2' | ||
__version__ = '0.17.0' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ | |
from . import topics | ||
from .const import MISSING | ||
from .loop import loop | ||
from .yml import yml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import ruamel.yaml | ||
|
||
yml = ruamel.yaml.YAML() | ||
yml.default_flow_style = False | ||
yml.default_style = False # type: ignore | ||
yml.width = 1000000 # type: ignore | ||
yml.allow_unicode = True | ||
yml.sort_base_mapping_type_on_output = False # type: ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from . import watcher | ||
from .file_name import path_from_name, name_from_path | ||
from .file import HABAppFile | ||
from .all import watch_folder, file_load_failed, file_load_ok | ||
from .event_listener import add_event_bus_listener |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import logging | ||
import typing | ||
from itertools import chain | ||
from pathlib import Path | ||
from threading import Lock | ||
|
||
import HABApp | ||
from HABApp.core.wrapper import ignore_exception | ||
from . import name_from_path | ||
from .file import CircularReferenceError, HABAppFile | ||
from .watcher import AggregatingAsyncEventHandler | ||
|
||
log = logging.getLogger('HABApp.files') | ||
|
||
LOCK = Lock() | ||
|
||
ALL: typing.Dict[str, HABAppFile] = {} | ||
LOAD_RUNNING = False | ||
|
||
|
||
def process(files: typing.List[Path], load_next: bool = True): | ||
global LOAD_RUNNING | ||
|
||
for file in files: | ||
name = name_from_path(file) | ||
|
||
# unload | ||
if not file.is_file(): | ||
with LOCK: | ||
existing = ALL.pop(name, None) | ||
if existing is not None: | ||
existing.unload() | ||
continue | ||
|
||
# reload/initial load | ||
obj = HABAppFile.from_path(name, file) | ||
with LOCK: | ||
ALL[name] = obj | ||
|
||
if not load_next: | ||
return None | ||
|
||
# Start loading only once | ||
with LOCK: | ||
if LOAD_RUNNING: | ||
return None | ||
LOAD_RUNNING = True | ||
|
||
_load_next() | ||
|
||
|
||
@ignore_exception | ||
def file_load_ok(name: str): | ||
with LOCK: | ||
file = ALL.get(name) | ||
file.load_ok() | ||
|
||
# reload files with the property "reloads_on" | ||
reload = [k.path for k in filter(lambda f: f.is_loaded and name in f.properties.reloads_on, ALL.values())] | ||
if reload: | ||
process(reload, load_next=False) | ||
|
||
_load_next() | ||
|
||
|
||
@ignore_exception | ||
def file_load_failed(name: str): | ||
with LOCK: | ||
f = ALL.get(name) | ||
f.load_failed() | ||
|
||
_load_next() | ||
|
||
|
||
def _load_next(): | ||
global LOAD_RUNNING | ||
|
||
# check files for dependencies etc. | ||
for file in list(filter(lambda x: not x.is_checked, ALL.values())): | ||
try: | ||
file.check_properties() | ||
except Exception as e: | ||
if not isinstance(e, (CircularReferenceError, FileNotFoundError)): | ||
HABApp.core.wrapper.process_exception(file.check_properties, e, logger=log) | ||
|
||
# Load order is parameters -> openhab config files-> rules | ||
f_n = HABApp.core.files.file_name | ||
_all = sorted(ALL.keys()) | ||
files = chain(filter(f_n.is_param, _all), filter(f_n.is_config, _all), filter(f_n.is_rule, _all)) | ||
|
||
for name in files: | ||
file = ALL[name] | ||
if file.is_loaded or file.is_failed: | ||
continue | ||
|
||
if file.can_be_loaded(): | ||
file.load() | ||
return None | ||
|
||
with LOCK: | ||
LOAD_RUNNING = False | ||
|
||
|
||
def watch_folder(folder: Path, file_ending: str, watch_subfolders: bool = False) -> AggregatingAsyncEventHandler: | ||
handler = AggregatingAsyncEventHandler(folder, process, file_ending, watch_subfolders) | ||
HABApp.core.files.watcher.add_folder_watch(handler) | ||
return handler |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
from typing import Any, Callable, Optional | ||
import logging | ||
from pathlib import Path | ||
from HABApp.core.logger import log_error | ||
|
||
import HABApp | ||
|
||
|
||
def add_event_bus_listener( | ||
file_type: str, | ||
func_load: Optional[Callable[[str, Path], Any]], | ||
func_unload: Optional[Callable[[str, Path], Any]], | ||
logger: logging.Logger): | ||
|
||
func = { | ||
'config': HABApp.core.files.file_name.is_config, | ||
'rule': HABApp.core.files.file_name.is_rule, | ||
'param': HABApp.core.files.file_name.is_param, | ||
}[file_type] | ||
|
||
def filter_func_load(event: HABApp.core.events.habapp_events.RequestFileLoadEvent): | ||
if not func(event.name): | ||
return None | ||
|
||
name = event.name | ||
path = event.get_path() | ||
|
||
# Only load existing files | ||
if not path.is_file(): | ||
log_error(logger, f'{file_type} file "{path}" does not exist and can not be loaded!') | ||
return None | ||
|
||
func_load(name, path) | ||
|
||
def filter_func_unload(event: HABApp.core.events.habapp_events.RequestFileUnloadEvent): | ||
if not func(event.name): | ||
return None | ||
name = event.name | ||
path = event.get_path() | ||
func_unload(name, path) | ||
|
||
if filter_func_unload is not None: | ||
HABApp.core.EventBus.add_listener( | ||
HABApp.core.EventBusListener( | ||
HABApp.core.const.topics.FILES, | ||
HABApp.core.WrappedFunction(filter_func_unload), | ||
HABApp.core.events.habapp_events.RequestFileUnloadEvent | ||
) | ||
) | ||
|
||
if filter_func_load is not None: | ||
HABApp.core.EventBus.add_listener( | ||
HABApp.core.EventBusListener( | ||
HABApp.core.const.topics.FILES, | ||
HABApp.core.WrappedFunction(filter_func_load), | ||
HABApp.core.events.habapp_events.RequestFileLoadEvent | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
from __future__ import annotations | ||
|
||
import logging | ||
import typing | ||
from pathlib import Path | ||
|
||
import HABApp | ||
from HABApp.core.const.topics import FILES as T_FILES | ||
from HABApp.core.events.habapp_events import RequestFileLoadEvent, RequestFileUnloadEvent | ||
from .file_props import FileProperties, get_props | ||
|
||
log = logging.getLogger('HABApp.files') | ||
|
||
|
||
class CircularReferenceError(Exception): | ||
pass | ||
|
||
|
||
class HABAppFile: | ||
|
||
@classmethod | ||
def from_path(cls, name: str, path: Path) -> HABAppFile: | ||
with path.open('r', encoding='utf-8') as f: | ||
txt = f.read(10 * 1024) | ||
return cls(name, path, get_props(txt)) | ||
|
||
def __init__(self, name: str, path: Path, properties: FileProperties): | ||
self.name: str = name | ||
self.path: Path = path | ||
self.properties: FileProperties = properties | ||
|
||
# file checks | ||
self.is_checked = False | ||
self.is_valid = False | ||
|
||
# file loaded | ||
self.is_loaded = False | ||
self.is_failed = False | ||
|
||
def _check_refs(self, stack, prop: str): | ||
c: typing.List[str] = getattr(self.properties, prop) | ||
for f in c: | ||
_stack = stack + (f, ) | ||
if f in stack: | ||
log.error(f'Circular reference: {" -> ".join(_stack)}') | ||
raise CircularReferenceError(" -> ".join(_stack)) | ||
|
||
next_file = ALL.get(f) | ||
if next_file is not None: | ||
next_file._check_refs(_stack, prop) | ||
|
||
def check_properties(self): | ||
self.is_checked = True | ||
|
||
# check dependencies | ||
mis = set(filter(lambda x: x not in ALL, self.properties.depends_on)) | ||
if mis: | ||
one = len(mis) == 1 | ||
msg = f'File {self.path} depends on file{"" if one else "s"} that ' \ | ||
f'do{"es" if one else ""}n\'t exist: {", ".join(sorted(mis))}' | ||
log.error(msg) | ||
raise FileNotFoundError(msg) | ||
|
||
# check reload | ||
mis = set(filter(lambda x: x not in ALL, self.properties.reloads_on)) | ||
if mis: | ||
one = len(mis) == 1 | ||
log.warning(f'File {self.path} reloads on file{"" if one else "s"} that ' | ||
f'do{"es" if one else ""}n\'t exist: {", ".join(sorted(mis))}') | ||
|
||
# check for circular references | ||
self._check_refs((self.name, ), 'depends_on') | ||
self._check_refs((self.name, ), 'reloads_on') | ||
|
||
self.is_valid = True | ||
|
||
def can_be_loaded(self) -> bool: | ||
if not self.is_valid: | ||
return False | ||
|
||
for name in self.properties.depends_on: | ||
f = ALL.get(name, None) | ||
if f is None: | ||
return False | ||
|
||
if not f.is_loaded: | ||
return False | ||
return True | ||
|
||
def load(self): | ||
self.is_loaded = False | ||
|
||
HABApp.core.EventBus.post_event( | ||
T_FILES, RequestFileLoadEvent(self.name) | ||
) | ||
|
||
def unload(self): | ||
HABApp.core.EventBus.post_event( | ||
T_FILES, RequestFileUnloadEvent(self.name) | ||
) | ||
|
||
def load_ok(self): | ||
self.is_loaded = True | ||
|
||
def load_failed(self): | ||
self.is_failed = True | ||
|
||
|
||
from .all import ALL # noqa F401 |
Oops, something went wrong.