Skip to content

Commit

Permalink
0.15.4
Browse files Browse the repository at this point in the history
- Reworked items file creation, made the file nicer and fixed a crash (#fixes 171)
- Changing an item through the rest api new gets properly reflected in HABApp (#fixes 170)
- Added some tests and modified the build pipeline
  • Loading branch information
spacemanspiff2007 authored Oct 19, 2020
1 parent b9ddb59 commit 2a4836a
Show file tree
Hide file tree
Showing 19 changed files with 277 additions and 137 deletions.
12 changes: 7 additions & 5 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ ignore =
E203,
# E203 whitespace before ':'
E221,
# E241 multiple spaces after ','
E241,
# E251 unexpected spaces around keyword / parameter equals
E251
E251,
# E303 too many blank lines
E303
E303,

max-line-length = 120
exclude =
Expand All @@ -20,8 +22,8 @@ exclude =
dist,
conf,
__init__.py,
tests/context.py
tests/conftest.py,

# the interfaces will throw unused imports
HABApp/openhab/interface.py
HABApp/openhab/interface_async.py
HABApp/openhab/interface.py,
HABApp/openhab/interface_async.py,
22 changes: 20 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
language: python
os: linux
stages:
- unit tests
- docker
- docs

jobs:
include:
- &python_38
stage: unit tests
python: 3.8
script: tox
install: pip install tox
Expand All @@ -17,14 +22,22 @@ jobs:
python: 3.7
env: TOXENV=py37

# Travis does not support
# - <<: *python_38
# python: 3.9
# env: TOXENV=py39

- <<: *python_38
stage: docs
env: TOXENV=flake

- <<: *python_38
python: 3.7
stage: docs
python: 3.8
env: TOXENV=docs

- stage: docker
- &docker
stage: docker
language: shell
install:
# test docker build
Expand All @@ -36,6 +49,11 @@ jobs:
# output stdout to travis in case we can not start the container
- docker logs habapp
# test if container is still running
# -q means quiet and will return 0 if a match is found
- docker ps | grep -q habapp
# Show logs from HABApp
- docker exec habapp tail -n +1 /config/log/HABApp.log

# Docker arm build (e.g. raspberry pi)
- <<: *docker
arch: arm64
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.7-alpine
FROM python:3.8-alpine

VOLUME [ "/config"]

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.15.3'
__version__ = '0.15.4'
81 changes: 0 additions & 81 deletions HABApp/openhab/connection_logic/plugin_things/item_worker.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from pathlib import Path
from typing import Set, Dict

import HABApp
Expand Down Expand Up @@ -143,83 +142,3 @@ async def create_item(item: UserItem, test: bool) -> bool:
await async_set_habapp_metadata(name, habapp_data)

return True


def create_items_file(path: Path, items: Dict[str, UserItem]):

field_fmt = {
'type': '{}',
'name': '{}',
'label': '"{}"',
'icon': '<{}>',
'groups': '({})',
'tags': '[{}]',
'bracket_open': '',
'link': 'channel = "{}"',
'metadata': '',
'bracket_close': '',
}

values = []
for item in items.values():
new = {}
for k, format in field_fmt.items():
if k in ('bracket_open', 'metadata', 'bracket_close'):
continue
val = item.__dict__[k]
if isinstance(val, list):
val = ', '.join(val)

new[k] = format.format(val) if val else ''

if item.link or item.metadata:
new['bracket_open'] = '{'
new['bracket_close'] = '}'

if item.metadata:
__m = []
for k, __meta in item.metadata.items():
__val = __meta['value']
__cfg = __meta['config']

_str = f'{k}={__val}' if not isinstance(__val, str) else f'{k}="{__val}"'
if __cfg:
__conf_strs = []
for _k, _v in __cfg.items():
__conf_strs.append(f'{_k}={_v}' if not isinstance(_v, str) else f'{_k}="{_v}"')
_str += f' [{", ".join(__conf_strs)}]'
__m.append(_str)

# link needs the "," so we indent properly
if item.link:
new['link'] += ', '
# metadata
new['metadata'] = ', '.join(__m)

values.append(new)

# if we have no items we don't create the file
if not values:
return None

f_dict = {}
for k in field_fmt.keys():
width = 1

if k not in ('bracket_open', 'metadata', 'bracket_close'):
width = max(map(len, map(lambda x: x[k], values)), default=0)
# indent to multiples of 4, if the entries are missing do not indent
if width:
for _ in range(4):
width += 1
if not width % 4:
break

# set with to min 1 because format crashes with with=0
f_dict[f'w_{k}'] = max(1, width)

fmt_str = ' '.join(f'{{{k}:{{w_{k}}}s}}' for k in field_fmt.keys()) + '\n'

with path.open(mode='w', encoding='utf-8') as file:
for v in values:
file.write(fmt_str.format(**f_dict, **v))
134 changes: 134 additions & 0 deletions HABApp/openhab/connection_logic/plugin_things/items_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
from pathlib import Path
from typing import Dict, List
import re

from .cfg_validator import UserItem


RE_GROUP_NAMES = re.compile(r'([A-Za-z0-9-]+?)(?=[A-Z_ -])')


def _get_item_val_dict(field_fmt: Dict[str, str], item: UserItem):
new = {}
for k, format in field_fmt.items():
if k in ('bracket_open', 'metadata', 'bracket_close'):
continue
val = item.__dict__[k]
if isinstance(val, list):
val = ', '.join(val)

new[k] = format.format(val) if val else ''

if item.link or item.metadata:
new['bracket_open'] = '{'
new['bracket_close'] = '}'
else:
new['bracket_open'] = ''
new['bracket_close'] = ''

if item.metadata:
__m = []
for k, __meta in item.metadata.items():
__val = __meta['value']
__cfg = __meta['config']

_str = f'{k}={__val}' if not isinstance(__val, str) else f'{k}="{__val}"'
if __cfg:
__conf_strs = []
for _k, _v in __cfg.items():
__conf_strs.append(f'{_k}={_v}' if not isinstance(_v, str) else f'{_k}="{_v}"')
_str += f' [{", ".join(__conf_strs)}]'
__m.append(_str)

# link needs the "," so we indent properly
if item.link:
new['link'] += ','
# metadata
new['metadata'] = ', '.join(__m)
else:
new['metadata'] = ''

return new


def _get_fmt_str(field_fmt: Dict[str, str], vals: List[Dict[str, str]]) -> str:
w_dict = {}
for k in field_fmt.keys():
#
# w_dict[k] = 0
# continue

width = max(map(len, map(lambda x: x[k], vals)), default=0)
# indent to multiples of 4, if the entries are missing do not indent
if width and k not in ('bracket_open', 'bracket_close', 'metadata'):
add = width % 4
if not add:
add = 4
width += add
w_dict[k] = width

ret = ''
for k in field_fmt.keys():
w = w_dict[k]
if not w:
ret += f'{{{k}:s}}' # format crashes with with=0 so this is a different format string
continue
else:
ret += f'{{{k}:{w}s}}'
return ret


def create_items_file(path: Path, items_dict: Dict[str, UserItem]):
# if we don't have any items we don't create an empty file
if not items_dict:
return None

field_fmt = {
'type': '{}',
'name': '{}',
'label': '"{}"',
'icon': '<{}>',
'groups': '({})',
'tags': '[{}]',
'bracket_open': '',
'link': 'channel = "{}"',
'metadata': '{}',
'bracket_close': '',
}

grouped_items = {None: []}
for _name, _item in sorted(items_dict.items()):
m = RE_GROUP_NAMES.match(_name)
grp = grouped_items.setdefault(m.group(1) if m is not None else None, [])
grp.append(_get_item_val_dict(field_fmt, _item))

# aggregate single entry items to a block
_aggr = []
for _name, _items in grouped_items.items():
if len(_items) <= 1:
_aggr.append(_name)
for _name in _aggr:
grouped_items[None].extend(grouped_items[_name])
grouped_items.pop(_name)

# single entry items get created at the end of file
if None in grouped_items:
grouped_items[None] = grouped_items.pop(None)

lines = []
for _name, _item_vals in grouped_items.items():
# skip empty items
if not _item_vals:
continue

fmt = _get_fmt_str(field_fmt, _item_vals)

for _val in _item_vals:
_l = fmt.format(**_val)
lines.append(_l.strip() + '\n')

# newline aber each name block
lines.append('\n')

with path.open(mode='w', encoding='utf-8') as file:
file.writelines(lines)
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from HABApp.openhab.connection_logic.plugin_things.filters import THING_ALIAS, CHANNEL_ALIAS
from HABApp.openhab.connection_logic.plugin_things.filters import apply_filters, log_overview
from ._log import log
from .item_worker import create_item, cleanup_items, create_items_file
from .item_worker import create_item, cleanup_items
from .items_file import create_items_file
from .thing_worker import update_thing_cfg
from .._plugin import OnConnectPlugin

Expand Down
2 changes: 1 addition & 1 deletion HABApp/openhab/events/item_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def from_dict(cls, topic: str, payload: dict):
# 'payload': '[{"type":"Switch","name":"Test","tags":[],"groupNames":[]},
# {"type":"Contact","name":"Test","tags":[],"groupNames":[]}]',
# 'type': 'ItemUpdatedEvent'
return cls(topic[16:-8], payload[1]['type'])
return cls(topic[16:-8], payload[0]['type'])

def __repr__(self):
return f'<{self.__class__.__name__} name: {self.name}, type: {self.type}>'
Expand Down
27 changes: 26 additions & 1 deletion conf_testing/rules/test_habapp_internals.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from HABApp.openhab.connection_handler.func_async import async_get_item_with_habapp_meta, async_set_habapp_metadata, \
async_remove_habapp_metadata
from HABApp.openhab.definitions.rest.habapp_data import HABAppThingPluginData
from HABAppTests import TestBaseRule, OpenhabTmpItem, run_coro
from HABApp.openhab.events import ItemUpdatedEvent
from HABApp.openhab.interface import create_item
from HABApp.openhab.items import StringItem, NumberItem, DatetimeItem
from HABAppTests import TestBaseRule, OpenhabTmpItem, run_coro, EventWaiter


class TestMetadata(TestBaseRule):
Expand Down Expand Up @@ -42,3 +45,25 @@ def create_meta(self):


TestMetadata()


class ChangeItemType(TestBaseRule):

def __init__(self):
super().__init__()
self.add_test('change_item', self.change_item)

def change_item(self):
with OpenhabTmpItem(None, 'Number') as tmpitem:
NumberItem.get_item(tmpitem.name)

create_item('String', tmpitem.name)
EventWaiter(tmpitem.name, ItemUpdatedEvent(tmpitem.name, 'String'), 2, False)
StringItem.get_item(tmpitem.name)

create_item('DateTime', tmpitem.name)
EventWaiter(tmpitem.name, ItemUpdatedEvent(tmpitem.name, 'DateTime'), 2, False)
DatetimeItem.get_item(tmpitem.name)


ChangeItemType()
1 change: 0 additions & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
from .rule_runner import SimpleRuleRunner
from .context import add_stdout, HABApp
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .helpers import parent_rule
Loading

0 comments on commit 2a4836a

Please sign in to comment.