Skip to content

Commit

Permalink
0.20.0 (#201)
Browse files Browse the repository at this point in the history
  • Loading branch information
spacemanspiff2007 authored Jan 21, 2021
1 parent 16e35cd commit 7e7cab5
Show file tree
Hide file tree
Showing 65 changed files with 1,732 additions and 440 deletions.
96 changes: 96 additions & 0 deletions HABApp/__cmd_args__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import argparse
import os
import sys
import time
import typing
from pathlib import Path

# Global var if we want to run the benchmark
DO_BENCH = False


def parse_args(passed_args=None) -> Path:
global DO_BENCH

parser = argparse.ArgumentParser(description='Start HABApp')
parser.add_argument(
'-c',
'--config',
help='Path to configuration folder (where the config.yml is located)',
default=None
)
parser.add_argument(
'-s',
'--sleep',
help='Sleep time in seconds before starting HABApp',
type=int,
default=None
)
parser.add_argument(
'-b',
'--benchmark',
help='Do a Benchmark based on the current config',
action='store_true'
)
args = parser.parse_args(passed_args)

DO_BENCH = args.benchmark

if args.sleep:
args.sleep = max(0, args.sleep)
print(f'Waiting {args.sleep:d} seconds before starting HABApp ...', end='')
time.sleep(args.sleep)
print(' done!')

path = args.config
if path is not None:
path = Path(path).resolve()
return find_config_folder(path)


def find_config_folder(arg_config_path: typing.Optional[Path]) -> Path:

if arg_config_path is None:
# Nothing is specified, we try to find the config automatically
check_path = []
try:
working_dir = Path(os.getcwd())
check_path.append( working_dir / 'HABApp')
check_path.append( working_dir.with_name('HABApp'))
check_path.append( working_dir.parent.with_name('HABApp'))
except ValueError:
# the ValueError gets raised if the working_dir or its parent is empty (e.g. c:\ or /)
pass

check_path.append(Path.home() / 'HABApp') # User home

# if we run in a venv check the venv, too
v_env = os.environ.get('VIRTUAL_ENV', '')
if v_env:
check_path.append(Path(v_env) / 'HABApp') # Virtual env dir
else:
# in case the user specifies the config.yml we automatically switch to the parent folder
if arg_config_path.name.lower() == 'config.yml' and arg_config_path.is_file():
arg_config_path = arg_config_path.parent

# Override automatic config detection if something is specified through command line
check_path = [arg_config_path]

for config_folder in check_path:
config_folder = config_folder.resolve()
if not config_folder.is_dir():
continue

config_file = config_folder / 'config.yml'
if config_file.is_file():
return config_folder

# we have specified a folder, but the config does not exist so we will create it
if arg_config_path is not None and arg_config_path.is_dir():
return arg_config_path

# we have nothing found and nothing specified -> exit
print('Config file "config.yml" not found!')
print('Checked folders:\n - ' + '\n - '.join(str(k) for k in check_path))
print('Please create file or specify a folder with the "-c" arg switch.')
sys.exit(1)
3 changes: 1 addition & 2 deletions HABApp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,4 @@
from HABApp.rule import Rule
from HABApp.parameters import Parameter, DictParameter

#from HABApp.runtime import Runtime
from HABApp.config import CONFIG
from HABApp.config import CONFIG
83 changes: 4 additions & 79 deletions HABApp/__main__.py
Original file line number Diff line number Diff line change
@@ -1,99 +1,24 @@
import argparse
import asyncio
import logging
import os
import signal
import sys
import time
import traceback
import typing
from pathlib import Path

import HABApp


def find_config_folder(arg_config_path: typing.Optional[Path]) -> Path:

if arg_config_path is None:
# Nothing is specified, we try to find the config automatically
check_path = []
try:
working_dir = Path(os.getcwd())
check_path.append( working_dir / 'HABApp')
check_path.append( working_dir.with_name('HABApp'))
check_path.append( working_dir.parent.with_name('HABApp'))
except ValueError:
# the ValueError gets raised if the working_dir or its parent is empty (e.g. c:\ or /)
pass

check_path.append(Path.home() / 'HABApp') # User home

# if we run in a venv check the venv, too
v_env = os.environ.get('VIRTUAL_ENV', '')
if v_env:
check_path.append(Path(v_env) / 'HABApp') # Virtual env dir
else:
# in case the user specifies the config.yml we automatically switch to the parent folder
if arg_config_path.name.lower() == 'config.yml' and arg_config_path.is_file():
arg_config_path = arg_config_path.parent

# Override automatic config detection if something is specified through command line
check_path = [arg_config_path]

for config_folder in check_path:
config_folder = config_folder.resolve()
if not config_folder.is_dir():
continue

config_file = config_folder / 'config.yml'
if config_file.is_file():
return config_folder

# we have specified a folder, but the config does not exist so we will create it
if arg_config_path is not None and arg_config_path.is_dir():
return arg_config_path

# we have nothing found and nothing specified -> exit
print('Config file "config.yml" not found!')
print('Checked folders:\n - ' + '\n - '.join(str(k) for k in check_path))
print('Please create file or specify a folder with the "-c" arg switch.')
sys.exit(1)


def get_command_line_args(args=None):
parser = argparse.ArgumentParser(description='Start HABApp')
parser.add_argument(
'-c',
'--config',
help='Path to configuration folder (where the config.yml is located)',
default=None
)
parser.add_argument(
'-s',
'--sleep',
help='Sleep time in seconds before starting HABApp',
type=int,
default=None
)
return parser.parse_args(args)
from HABApp.__cmd_args__ import parse_args


def main() -> typing.Union[int, str]:

args = get_command_line_args()
if args.sleep:
args.sleep = max(0, args.sleep)
print(f'Waiting {args.sleep:d} seconds before starting HABApp ...', end='')
time.sleep(args.sleep)
print(' done!')
if args.config is not None:
args.config = Path(args.config).resolve()
# This has do be done before we create HABApp because of the possible sleep time
cfg_folder = parse_args()

log = logging.getLogger('HABApp')

try:
app = HABApp.runtime.Runtime()
app.startup(config_folder=find_config_folder(args.config))
app.startup(config_folder=cfg_folder)

def shutdown_handler(sig, frame):
print('Shutting down ...')
Expand Down
2 changes: 1 addition & 1 deletion HABApp/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.19.1'
__version__ = '0.20.0'
21 changes: 16 additions & 5 deletions HABApp/config/config_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,24 @@ def load_log(self):
except Exception as e:
print(f'Error loading logging config: {e}')
return None
log.debug('Loaded logging config')

# Try rotating the logs on first start
if self.first_start:
for handler in logging._handlerList:
for wr in reversed(logging._handlerList[:]):
handler = wr() # weakref -> call it to get object

# only rotate these types
if not isinstance(handler, (RotatingFileHandler, TimedRotatingFileHandler)):
continue

# Rotate only if files have content
logfile = Path(handler.baseFilename)
if not logfile.is_file() or logfile.stat().st_size <= 0:
continue

try:
handler = handler() # weakref -> call it to get object
if isinstance(handler, (RotatingFileHandler, TimedRotatingFileHandler)):
handler.doRollover()
handler.acquire()
handler.doRollover()
except Exception:
lines = traceback.format_exc().splitlines()
# cut away AbsolutePathExpected Exception from log output
Expand All @@ -143,6 +152,8 @@ def load_log(self):
start = i
for line in lines[start:]:
log.error(line)
finally:
handler.release()

logging.getLogger('HABApp').info(f'HABApp Version {__version__}')

Expand Down
40 changes: 20 additions & 20 deletions HABApp/core/event_bus_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@

class EventBusListener:
def __init__(self, topic, callback, event_type=AllEvents,
prop_name1: Optional[str] = None, prop_value1: Optional[Any] = None,
prop_name2: Optional[str] = None, prop_value2: Optional[Any] = None,
attr_name1: Optional[str] = None, attr_value1: Optional[Any] = None,
attr_name2: Optional[str] = None, attr_value2: Optional[Any] = None,
):
assert isinstance(topic, str), type(topic)
assert isinstance(callback, WrappedFunction)
assert prop_name1 is None or isinstance(prop_name1, str), prop_name1
assert prop_name2 is None or isinstance(prop_name2, str), prop_name2
assert attr_name1 is None or isinstance(attr_name1, str), attr_name1
assert attr_name2 is None or isinstance(attr_name2, str), attr_name2

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

self.event_filter = event_type

# Property filters
self.prop_name1 = prop_name1
self.prop_value1 = prop_value1
self.prop_name2 = prop_name2
self.prop_value2 = prop_value2
self.attr_name1 = attr_name1
self.attr_value1 = attr_value1
self.attr_name2 = attr_name2
self.attr_value2 = attr_value2

self.__is_all: bool = self.event_filter is AllEvents
self.__is_single: bool = not isinstance(self.event_filter, (list, tuple, set))
Expand All @@ -38,11 +38,11 @@ def notify_listeners(self, event):
if self.__is_single:
if isinstance(event, self.event_filter):
# If we have property filters wie only trigger when value is set accordingly
if self.prop_name1 is not None:
if getattr(event, self.prop_name1, None) != self.prop_value1:
if self.attr_name1 is not None:
if getattr(event, self.attr_name1, None) != self.attr_value1:
return None
if self.prop_name2 is not None:
if getattr(event, self.prop_name2, None) != self.prop_value2:
if self.attr_name2 is not None:
if getattr(event, self.attr_name2, None) != self.attr_value2:
return None

self.func.run(event)
Expand All @@ -52,11 +52,11 @@ def notify_listeners(self, event):
for cls in self.event_filter:
if isinstance(event, cls):
# If we have property filters wie only trigger when value is set accordingly
if self.prop_name1 is not None:
if getattr(event, self.prop_name1, None) != self.prop_value1:
if self.attr_name1 is not None:
if getattr(event, self.attr_name1, None) != self.attr_value1:
return None
if self.prop_name2 is not None:
if getattr(event, self.prop_name2, None) != self.prop_value2:
if self.attr_name2 is not None:
if getattr(event, self.attr_name2, None) != self.attr_value2:
return None

self.func.run(event)
Expand All @@ -73,9 +73,9 @@ def desc(self):
_type = _type[8:-2].split('.')[-1]

_val = ''
if self.prop_name1 is not None:
_val += f', {self.prop_name1}=={self.prop_value1}'
if self.prop_name2 is not None:
_val += f', {self.prop_name2}=={self.prop_value2}'
if self.attr_name1 is not None:
_val += f', {self.attr_name1}=={self.attr_value1}'
if self.attr_name2 is not None:
_val += f', {self.attr_name2}=={self.attr_value2}'

return f'"{self.topic}" (type {_type}{_val})'
1 change: 1 addition & 0 deletions HABApp/core/events/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .events import ComplexEventValue, ValueUpdateEvent, ValueChangeEvent, \
ItemNoChangeEvent, ItemNoUpdateEvent, AllEvents
from . import habapp_events
from .event_filters import EventFilter, ValueChangeEventFilter, ValueUpdateEventFilter
53 changes: 53 additions & 0 deletions HABApp/core/events/event_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from typing import Any

import HABApp
from HABApp.core.const import MISSING
from . import ValueChangeEvent, ValueUpdateEvent


class EventFilter:
def __init__(self, event_type, **kwargs):
assert len(kwargs) < 3, 'EventFilter only allows up to two args that will be used to filter'

for arg in kwargs:
if arg not in event_type.__annotations__:
raise AttributeError(f'Filter attribute "{arg}" does not exist for "{event_type.__name__}"')

self.__cls = event_type
self.__filter = kwargs

def create_event_listener(self, name, cb) -> 'HABApp.core.EventBusListener':
kwargs = {'event_type': self.__cls}
ct = 1
for k, v in self.__filter.items():
kwargs[f'attr_name{ct}'] = k
kwargs[f'attr_value{ct}'] = v
ct += 1

return HABApp.core.EventBusListener(name, cb, **kwargs)

def __repr__(self):
name = self.__class__.__name__
vals = [f'{k}={v}' for k, v in self.__filter.items()]
if name == EventFilter.__name__:
vals.insert(0, f'event_type={self.__cls.__name__}')
return f'{name}({", ".join(vals)})'


class ValueUpdateEventFilter(EventFilter):
_EVENT_TYPE = ValueUpdateEvent

def __init__(self, value):
super().__init__(self._EVENT_TYPE, value=value)


class ValueChangeEventFilter(EventFilter):
_EVENT_TYPE = ValueChangeEvent

def __init__(self, value: Any = MISSING, old_value: Any = MISSING):
args = {}
if value is not MISSING:
args['value'] = value
if old_value is not MISSING:
args['old_value'] = old_value
super().__init__(self._EVENT_TYPE, **args)
Loading

0 comments on commit 7e7cab5

Please sign in to comment.