Skip to content

Commit

Permalink
0.8.0 (#73)
Browse files Browse the repository at this point in the history
Reworked Parameters:
- can now be used to setup rules dynamically
- can be imported and created through ``HABApp.Parameter``
- own documentation page

Reworked ``MultiValueItem``:
- Removed ``MultiValue``
- Added lots of documentation

Other:
- ``run_in`` supports ``timedelta``
- Fixed example (fixes #72)
- Added more documentation
  • Loading branch information
spacemanspiff2007 authored Sep 28, 2019
1 parent f193226 commit 22556a4
Show file tree
Hide file tree
Showing 36 changed files with 929 additions and 521 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
.idea
__pycache__
/conf
/conf_testing
/build
2 changes: 2 additions & 0 deletions HABApp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@
import HABApp.runtime

from HABApp.rule import Rule
from HABApp.parameters import Parameter

#from HABApp.runtime import Runtime
2 changes: 1 addition & 1 deletion HABApp/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__VERSION__ = '0.7.2'
__VERSION__ = '0.8.0'
2 changes: 1 addition & 1 deletion HABApp/core/Items.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def get_all_items() -> typing.List[__Item]:
return list(_ALL_ITEMS.values())


def get_item_names() -> typing.List[str]:
def get_all_item_names() -> typing.List[str]:
return list(_ALL_ITEMS.keys())


Expand Down
2 changes: 1 addition & 1 deletion HABApp/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
import HABApp.core.items

import HABApp.core.EventBus
import HABApp.core.Items
import HABApp.core.Items
2 changes: 1 addition & 1 deletion HABApp/core/event_bus_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def __init__(self, name, callback, event_type=AllEvents):
assert isinstance(callback, WrappedFunction)

self.name: str = name
self.func = callback
self.func: WrappedFunction = callback

self.event_filter = event_type

Expand Down
11 changes: 11 additions & 0 deletions HABApp/core/items/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@

class Item:

@classmethod
def get_item(cls, name: str):
"""Returns an already existing item. If it does not exist or has a different item type an exception will occur.
:param name: Name of the item
:return: the item
"""
item = HABApp.core.Items.get_item(name)
assert isinstance(item, cls), f'{cls} != {type(item)}'
return item

@classmethod
def get_create_item(cls, name: str, default_state=None):
"""Creates a new item in HABApp and returns it or returns the already existing one with the given name
Expand Down
2 changes: 1 addition & 1 deletion HABApp/openhab/oh_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ async def update_all_items(self) -> int:
HABApp.core.Items.set_item(new_item)

# remove items which are no longer available
ist = set(HABApp.core.Items.get_item_names())
ist = set(HABApp.core.Items.get_all_item_names())
soll = {k['name'] for k in data}
for k in ist - soll:
HABApp.core.Items.pop_item(k)
Expand Down
2 changes: 2 additions & 0 deletions HABApp/parameters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

from .parameter import Parameter
62 changes: 62 additions & 0 deletions HABApp/parameters/parameter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import typing

from .parameters import add_parameter as _add_parameter
from .parameters import get_value as _get_value


class Parameter:
def __init__(self, filename: str, *keys, default_value: typing.Any = 'ToDo'):
"""Class to dynamically access parameters which are loaded from file.
:param filename: filename (without extension)
:param keys: structure in the file
:param default_value: default value for the parameter.
Is used to create the file and the structure if it does not exist yet.
"""

assert isinstance(filename, str), type(filename)
self.filename: str = filename
self.keys = keys

# as a convenience try to create the file and the file structure
_add_parameter(self.filename, *self.keys, default_value=default_value)

@property
def value(self):
"""Return the current value. This will do the lookup so make sure to not cache this value, otherwise
the parameter might not work as expected.
"""
return _get_value(self.filename, *self.keys)

def __eq__(self, other):
return self.value == other

def __lt__(self, other):
if not isinstance(other, (int, float)):
return NotImplemented

return self.value < other

def __le__(self, other):
if not isinstance(other, (int, float)):
return NotImplemented

return self.value <= other

def __ge__(self, other):
if not isinstance(other, (int, float)):
return NotImplemented

return self.value >= other

def __gt__(self, other):
if not isinstance(other, (int, float)):
return NotImplemented

return self.value > other

def __repr__(self):
return f'<Parameter file: {self.filename}, keys: {self.keys}, value: {self.value}'

def __str__(self):
return str(self.value)
Original file line number Diff line number Diff line change
@@ -1,140 +1,101 @@
import logging
import threading
import traceback
import typing
from pathlib import Path

import ruamel.yaml

import HABApp
from HABApp.runtime import FileEventTarget
from HABApp.util import PrintException

log = logging.getLogger('HABApp.RuleParameters')

_yml_setup = ruamel.yaml.YAML()
_yml_setup.default_flow_style = False
_yml_setup.default_style = False
_yml_setup.width = 1000000
_yml_setup.allow_unicode = True
_yml_setup.sort_base_mapping_type_on_output = False


class RuleParameters(FileEventTarget):
UNITTEST = False

@PrintException
def __init__(self, config, folder_watcher):

# serialize loading
self.__lock = threading.Lock()

self.params: typing.Dict[str, dict] = {}

if RuleParameters.UNITTEST:
return
assert isinstance(config, HABApp.config.Config)
assert isinstance(folder_watcher, HABApp.runtime.folder_watcher.FolderWatcher)
self.config = config

if not self.config.directories.param.is_dir():
log.info(f'Parameter files disabled. Folder {self.config.directories.param} does not exist!')
return

# folder watcher
folder_watcher.watch_folder(
folder=self.config.directories.param,
file_ending='.yml',
event_target=self,
worker_factory=lambda x : HABApp.core.WrappedFunction(x, logger=log).run,
watch_subfolders=False
)

# load all parameter files
def load_all_files():
for f in self.config.directories.param.glob('**/*.yml'):
try:
self.add_file(f)
except Exception as e:
log.error(e)
HABApp.core.WrappedFunction(load_all_files, logger=log, name='Load all parameter files').run()

def reload_file(self, path: Path):
self.add_file(path)

def remove_file(self, path: Path):
try:
with self.__lock:
self.params.pop(path.stem)
except Exception:
log.error(f"Could not remove parameters from {path.name}!")
for l in traceback.format_exc().splitlines():
log.error(l)
return None

log.debug(f'Removed params from {path.name}!')

def add_file(self, path: Path):
try:
with self.__lock:
with path.open( mode='r', encoding='utf-8') as file:
data = _yml_setup.load(file)
if data is None:
data = {}
self.params[path.stem] = data
except Exception:
log.error(f"Could not load params from {path.name}!")
for l in traceback.format_exc().splitlines():
log.error(l)
return None

log.debug(f'Loaded params from {path.name}!')

def save_file(self, file: str):
assert isinstance(file, str), type(file)

if RuleParameters.UNITTEST:
return None

filename = self.config.directories.param / (file + '.yml')
with self.__lock:
log.info(f'Updated {filename}')
with filename.open('w', encoding='utf-8') as outfile:
_yml_setup.dump(self.params[file], outfile)

def add_param(self, file, *keys, default_value):
save = False

if file not in self.params:
save = True
self.params[file] = param = {}
else:
param = self.params[file]

if keys:
# Create structure
for key in keys[:-1]:
if key not in param:
param[key] = {}
save = True
param = param[key]

# Create value
if keys[-1] not in param:
param[keys[-1]] = default_value
save = True

if save:
self.save_file(file)
return None

def get_param(self, file, *keys):
try:
param = self.params[file]
except KeyError:
raise FileNotFoundError(f'File {file}.yml not found in params folder!')

# lookup parameter
for key in keys:
param = param[key]
return param
import logging
import threading
import traceback
from pathlib import Path

import HABApp
import ruamel.yaml
from HABApp.runtime import FileEventTarget
from HABApp.util import PrintException

from .parameters import get_parameter_file, remove_parameter_file, set_parameter_file

log = logging.getLogger('HABApp.RuleParameters')

_yml_setup = ruamel.yaml.YAML()
_yml_setup.default_flow_style = False
_yml_setup.default_style = False
_yml_setup.width = 1000000
_yml_setup.allow_unicode = True
_yml_setup.sort_base_mapping_type_on_output = False


class ParameterFileWatcher(FileEventTarget):
UNITTEST = False

@PrintException
def __init__(self, config, folder_watcher):

# serialize loading
self.__lock: threading.Lock = threading.Lock()

if ParameterFileWatcher.UNITTEST:
return
assert isinstance(config, HABApp.config.Config)
assert isinstance(folder_watcher, HABApp.runtime.folder_watcher.FolderWatcher)
self.config = config

if not self.config.directories.param.is_dir():
log.info(f'Parameter files disabled. Folder {self.config.directories.param} does not exist!')
return

# folder watcher
folder_watcher.watch_folder(
folder=self.config.directories.param,
file_ending='.yml',
event_target=self,
worker_factory=lambda x : HABApp.core.WrappedFunction(x, logger=log).run,
watch_subfolders=False
)

# load all parameter files
def load_all_files():
for f in self.config.directories.param.glob('**/*.yml'):
try:
self.add_file(f)
except Exception as e:
log.error(e)
HABApp.core.WrappedFunction(load_all_files, logger=log, name='Load all parameter files').run()

def reload_file(self, path: Path):
self.add_file(path)

def remove_file(self, path: Path):
try:
with self.__lock:
remove_parameter_file(path.stem)
except Exception:
log.error(f"Could not remove parameters from {path.name}!")
for l in traceback.format_exc().splitlines():
log.error(l)
return None

log.debug(f'Removed params from {path.name}!')

def add_file(self, path: Path):
try:
with self.__lock:
with path.open( mode='r', encoding='utf-8') as file:
data = _yml_setup.load(file)
if data is None:
data = {}
set_parameter_file(path.stem, data)
except Exception:
log.error(f"Could not load params from {path.name}!")
for l in traceback.format_exc().splitlines():
log.error(l)
return None

log.debug(f'Loaded params from {path.name}!')

def save_file(self, file: str):
assert isinstance(file, str), type(file)

if ParameterFileWatcher.UNITTEST:
return None

filename = self.config.directories.param / (file + '.yml')
with self.__lock:
log.info(f'Updated {filename}')
with filename.open('w', encoding='utf-8') as outfile:
_yml_setup.dump(get_parameter_file(file), outfile)
Loading

0 comments on commit 22556a4

Please sign in to comment.