Skip to content

Commit

Permalink
0.10.1
Browse files Browse the repository at this point in the history
- Openhab Tests log to own file
- Serialized rule (un-) loading (fixes #80)
- MyPy fixes
- Added documentation for set_file_validator
- added func get_path for FileEvents
- Gracefully shutdown aiohttp on shutdown of HABApp
  • Loading branch information
spacemanspiff2007 committed Nov 12, 2019
1 parent ab2939c commit 2453af0
Show file tree
Hide file tree
Showing 27 changed files with 209 additions and 96 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.mypy_cache
.idea
__pycache__
/conf
Expand Down
6 changes: 4 additions & 2 deletions HABApp/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ def main() -> typing.Union[int, str]:
if args.NoMQTTConnectionErrors is True:
HABApp.mqtt.MqttInterface._RAISE_CONNECTION_ERRORS = False

loop = None
log = logging.getLogger('HABApp')

# if installed we use uvloop because it seems to be much faster (untested)
Expand All @@ -113,7 +112,7 @@ def main() -> typing.Union[int, str]:
# https://docs.python.org/3/library/asyncio-subprocess.html#subprocess-and-threads
asyncio.get_child_watcher()

loop = asyncio.get_event_loop()
loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()

loop.set_debug(True)
loop.slow_callback_duration = 0.02
Expand Down Expand Up @@ -142,6 +141,9 @@ def shutdown_handler(sig, frame):
print(e)
return str(e)
finally:
# Sleep to allow underlying connections of aiohttp to close
# https://aiohttp.readthedocs.io/en/stable/client_advanced.html#graceful-shutdown
loop.run_until_complete(asyncio.sleep(1))
loop.close()
return 0

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.10.0'
__VERSION__ = '0.10.1'
6 changes: 3 additions & 3 deletions HABApp/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@

_yaml_param = ruamel.yaml.YAML(typ='safe')
_yaml_param.default_flow_style = False
_yaml_param.default_style = False
_yaml_param.width = 1000000
_yaml_param.default_style = False # type: ignore
_yaml_param.width = 1000000 # type: ignore
_yaml_param.allow_unicode = True
_yaml_param.sort_base_mapping_type_on_output = False
_yaml_param.sort_base_mapping_type_on_output = False # type: ignore


log = logging.getLogger('HABApp.Config')
Expand Down
11 changes: 9 additions & 2 deletions HABApp/core/events/habapp_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ class RequestFileLoadEvent:
def from_path(cls, folder: Path, file: Path) -> 'RequestFileLoadEvent':
return cls(str(file.relative_to(folder)))

def __init__(self, name: str = None):
def __init__(self, name: str):
self.filename: str = name

def get_path(self, parent_folder: Path) -> Path:
return parent_folder / self.filename

def __repr__(self):
return f'<{self.__class__.__name__} filename: {self.filename}>'

Expand All @@ -28,9 +31,13 @@ class RequestFileUnloadEvent:
def from_path(cls, folder: Path, file: Path) -> 'RequestFileUnloadEvent':
return cls(str(file.relative_to(folder)))

def __init__(self, name: str = None):
def __init__(self, name: str):
self.filename: str = name

def get_path(self, parent_folder: Path) -> Path:
return parent_folder / self.filename


def __repr__(self):
return f'<{self.__class__.__name__} filename: {self.filename}>'

Expand Down
3 changes: 2 additions & 1 deletion HABApp/mqtt/mqtt_connection.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import typing
import ujson

import paho.mqtt.client as mqtt
Expand All @@ -24,7 +25,7 @@ def __init__(self, mqtt_config: MqttConfig, shutdown_helper: ShutdownHelper):

self.client: mqtt.Client = None

self.subscriptions = []
self.subscriptions: typing.List[typing.Tuple[str, int]] = []

# config changes
self.__config = mqtt_config
Expand Down
3 changes: 1 addition & 2 deletions HABApp/mqtt/mqtt_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ def unsubscribe(self, topic: str) -> int:
return result



MQTT_INTERFACE: MqttInterface = None
MQTT_INTERFACE: MqttInterface


def get_mqtt_interface(connection=None, config=None) -> MqttInterface:
Expand Down
15 changes: 9 additions & 6 deletions HABApp/openhab/definitions/values.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import typing
from HABApp.core.events import ComplexEventValue


Expand All @@ -16,9 +17,9 @@ def __str__(self):

class PercentValue(ComplexEventValue):
def __init__(self, value: str):
value = float(value)
assert 0 <= value <= 100, f'{value} ({type(value)})'
super().__init__(value)
percent = float(value)
assert 0 <= percent <= 100, f'{percent} ({type(percent)})'
super().__init__(percent)

def __str__(self):
return f'{self.value}%'
Expand Down Expand Up @@ -47,14 +48,16 @@ def __str__(self):

class QuantityValue(ComplexEventValue):
def __init__(self, value: str):
val, unit = value.split(' ')
str_val, unit = value.split(' ')

try:
val = int(val)
val: typing.Union[int, float] = int(str_val)
except ValueError:
val = float(val)
val = float(str_val)

super().__init__(val)
self.unit = unit


def __str__(self):
return f'{self.value} {self.unit}'
8 changes: 4 additions & 4 deletions HABApp/openhab/http_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,16 @@ def _is_disconnect_exception(self, e) -> bool:
self.__set_offline(str(e))
return True

async def _check_http_response(self, future, additional_info="",
accept_404=False) -> typing.Optional[ ClientResponse]:
async def _check_http_response(self, future: aiohttp.client._RequestContextManager, additional_info="",
accept_404=False) -> ClientResponse:
try:
resp = await future
except Exception as e:
is_disconnect = self._is_disconnect_exception(e)
log.log(logging.WARNING if is_disconnect else logging.ERROR, f'"{e}" ({type(e)})')
if is_disconnect:
raise OpenhabDisconnectedError()
else:
return None
raise

# Server Errors if openhab is not ready yet
if resp.status >= 500:
Expand Down Expand Up @@ -290,6 +289,7 @@ async def async_get_items(self) -> typing.Optional[list]:
if not isinstance(e, (OpenhabDisconnectedError, OpenhabNotReadyYet)):
for l in traceback.format_exc().splitlines():
log.error(l)
return None

async def async_get_item(self, item_name: str) -> dict:
fut = self.__session.get(self.__get_openhab_url('rest/items/{:s}', item_name))
Expand Down
3 changes: 2 additions & 1 deletion HABApp/openhab/items/map_items.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
import typing

from HABApp.core.items import Item
from . import SwitchItem, ContactItem, RollershutterItem, DimmerItem, ColorItem, NumberItem
Expand All @@ -8,7 +9,7 @@ def map_items(name, openhab_type : str, openhab_value : str):
assert isinstance(openhab_type, str), type(openhab_type)
assert isinstance(openhab_value, str), type(openhab_value)

value = openhab_value
value: typing.Optional[str] = openhab_value
if openhab_value == 'NULL' or openhab_value == 'UNDEF':
value = None

Expand Down
9 changes: 4 additions & 5 deletions HABApp/openhab/oh_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
log = logging.getLogger('HABApp.openhab.Connection')



class OpenhabConnection(HttpConnectionEventHandler):

def __init__(self, config, shutdown):
Expand Down Expand Up @@ -99,11 +98,11 @@ async def async_ping(self):



def on_sse_event(self, event: dict):
def on_sse_event(self, event_dict: dict):

try:
# Lookup corresponding OpenHAB event
event = get_event(event)
event = get_event(event_dict)

# Events which change the ItemRegistry
if isinstance(event, (HABApp.openhab.events.ItemAddedEvent, HABApp.openhab.events.ItemUpdatedEvent)):
Expand Down Expand Up @@ -140,7 +139,7 @@ def on_sse_event(self, event: dict):


@PrintException
async def update_all_items(self) -> int:
async def update_all_items(self):

try:
data = await self.connection.async_get_items()
Expand Down Expand Up @@ -176,4 +175,4 @@ async def update_all_items(self) -> int:
log.error(e)
for l in traceback.format_exc().splitlines():
log.error(l)
return 0
return None
3 changes: 2 additions & 1 deletion HABApp/parameters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@

from .parameter import Parameter
from .parameter import Parameter
from .parameters import set_file_validator
13 changes: 7 additions & 6 deletions HABApp/parameters/parameter_files.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import threading
import traceback
import typing

import ruamel.yaml

Expand All @@ -11,14 +12,14 @@

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

LOCK = threading.Lock()
HABAPP_PARAM_TOPIC = 'HABApp.Parameters'
CONFIG = None
CONFIG = None # type: typing.Optional[HABApp.config.Config]


def setup_param_files(config, folder_watcher):
Expand Down Expand Up @@ -56,7 +57,7 @@ def setup_param_files(config, folder_watcher):


def load_file(event: HABApp.core.events.habapp_events.RequestFileLoadEvent):
path = CONFIG.directories.param / event.filename
path = event.get_path(CONFIG.directories.param)

with LOCK: # serialize to get proper error messages
try:
Expand All @@ -75,7 +76,7 @@ def load_file(event: HABApp.core.events.habapp_events.RequestFileLoadEvent):


def unload_file(event: HABApp.core.events.habapp_events.RequestFileUnloadEvent):
path = CONFIG.directories.param / event.filename
path = event.get_path(CONFIG.directories.param)

with LOCK: # serialize to get proper error messages
try:
Expand Down
21 changes: 15 additions & 6 deletions HABApp/parameters/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,31 @@ def get_parameter_file(file: str):
return _PARAMETERS[file]


def set_file_validator(file: str, validator: typing.Any, allow_extra_keys=True):
def set_file_validator(filename: str, validator: typing.Any, allow_extra_keys=True):
"""Add a validator for the parameter file. If the file is already loaded this will reload the file.
:param filename: filename which shall be validated (without extension)
:param validator: Description of file content - see the library
`voluptuous <https://github.com/alecthomas/voluptuous#show-me-an-example/>`_ for examples.
Use `None` to remove validator.
:param allow_extra_keys: Allow additional keys in the file structure
"""

# Remove validator
if validator is None:
_VALIDATORS.pop(file, None)
_VALIDATORS.pop(filename, None)
return

# Set validator
old_validator = _VALIDATORS.get(file)
_VALIDATORS[file] = new_validator = voluptuous.Schema(
old_validator = _VALIDATORS.get(filename)
_VALIDATORS[filename] = new_validator = voluptuous.Schema(
validator, required=True, extra=(voluptuous.ALLOW_EXTRA if allow_extra_keys else voluptuous.PREVENT_EXTRA)
)

# todo: move this to file handling so we get the extension
if old_validator != new_validator:
HABApp.core.EventBus.post_event(
HABAPP_PARAM_TOPIC, HABApp.core.events.habapp_events.RequestFileLoadEvent(file + '.yml')
HABAPP_PARAM_TOPIC, HABApp.core.events.habapp_events.RequestFileLoadEvent(filename + '.yml')
)


Expand All @@ -50,7 +58,8 @@ def add_parameter(file: str, *keys, default_value):

if file not in _PARAMETERS:
save = True
_PARAMETERS[file] = param = {}
param: typing.Dict[str, typing.Any] = {}
_PARAMETERS[file] = param
else:
param = _PARAMETERS[file]

Expand Down
16 changes: 9 additions & 7 deletions HABApp/rule/interfaces/rule_subprocess.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import asyncio
import typing


class FinishedProcessInfo:
"""Information about the finished process."""

def __init__(self, returncode: int, stdout: str, stderr: str):
def __init__(self, returncode: int, stdout: typing.Optional[str], stderr: typing.Optional[str]):
self.returncode: int = returncode
self.stdout: str = stdout
self.stderr: str = stderr
self.stdout: typing.Optional[str] = stdout
self.stderr: typing.Optional[str] = stderr

def __repr__(self):
return f'<ProcessInfo: returncode:{self.returncode} stdout:{self.stdout} stderr:{self.stderr}>'
return f'<ProcessInfo: returncode: {self.returncode}, stdout: {self.stdout}, stderr: {self.stderr}>'


async def async_subprocess_exec(callback, program: str, *args, capture_output=True):
Expand All @@ -29,10 +30,11 @@ async def async_subprocess_exec(callback, program: str, *args, capture_output=Tr
stderr=asyncio.subprocess.PIPE if capture_output else None
)

stdout, stderr = await proc.communicate()
stdout = stdout.decode()
stderr = stderr.decode()
b_stdout, b_stderr = await proc.communicate()
ret_code = proc.returncode
if capture_output:
stdout = b_stdout.decode()
stderr = b_stderr.decode()
except asyncio.CancelledError:
if proc is not None:
proc.terminate()
Expand Down
2 changes: 2 additions & 0 deletions HABApp/rule/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
log = logging.getLogger('HABApp.Rule')


# Func to log deprecation warnings
def send_warnings_to_log(message, category, filename, lineno, file=None, line=None):
log.warning('%s:%s: %s:%s' % (filename, lineno, category.__name__, message))
return


# Setup deprecation warnings
warnings.simplefilter('default')
warnings.showwarning = send_warnings_to_log

Expand Down
4 changes: 2 additions & 2 deletions HABApp/rule/scheduler/reoccurring_cb.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self, time: TYPING_DATE_TIME, interval: typing.Union[int, datetime.
assert isinstance(interval, datetime.timedelta), type(interval)
self.time_interval = interval

def check_due(self, now: datetime):
def check_due(self, now: datetime.datetime):
super().check_due(now)
if self.is_due:
self.next_call += self.time_interval
Expand All @@ -40,7 +40,7 @@ def __init__(self, time: TYPING_DATE_TIME, weekdays: typing.List[int], callback,
while not self.next_call.isoweekday() in self.weekdays:
self.next_call += datetime.timedelta(days=1)

def check_due(self, now: datetime):
def check_due(self, now: datetime.datetime):
super().check_due(now)
if self.is_due:
self.next_call += datetime.timedelta(days=1)
Expand Down
Loading

0 comments on commit 2453af0

Please sign in to comment.