Skip to content

Commit

Permalink
Allow combining commands (#167)
Browse files Browse the repository at this point in the history
* Allow combining commands

* Add feature flag

* Bump version

* Add test for None

* More tests

* Lint

* Add missing assert
  • Loading branch information
Hate-Usernames authored and Patrik committed May 28, 2018
1 parent 8eb1129 commit 1d3b0e8
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 1 deletion.
35 changes: 35 additions & 0 deletions pytradfri/command.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Command implementation."""

from copy import deepcopy


class Command(object):
"""The object for coap commands."""
Expand Down Expand Up @@ -71,3 +73,36 @@ def url(self, host):
"""Generate url for coap client."""
path = '/'.join(str(v) for v in self._path)
return 'coaps://{}:5684/{}'.format(host, path)

def _merge(self, a, b):
"""Merges a into b."""
for k, v in a.items():
if isinstance(v, dict):
item = b.setdefault(k, {})
self._merge(v, item)
elif isinstance(v, list):
item = b.setdefault(k, [{}])
if len(v) == 1 and isinstance(v[0], dict):
self._merge(v[0], item[0])
else:
b[k] = v
else:
b[k] = v
return b

def combine_data(self, command2):
"""Combines the data for this command with another."""
if command2 is None:
return
self._data = self._merge(command2._data, self._data)

def __add__(self, other):
if other is None:
return deepcopy(self)
if isinstance(other, self.__class__):
newObj = deepcopy(self)
newObj.combine_data(other)
return newObj
else:
raise (TypeError("unsupported operand type(s) for +: "
"'{}' and '{}'").format(self.__class__, type(other)))
13 changes: 13 additions & 0 deletions pytradfri/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,22 @@ def __init__(self, device):
if ATTR_LIGHT_COLOR_HUE in self.raw[0]:
self.can_set_color = True

# Currently uncertain which bulbs are capable of setting
# multiple values simultaneously. As of gateway firmware
# 1.3.14 1st party bulbs do not seem to support this properly,
# but (at least some) hue bulbs do.
if 'Philips' in self._device.device_info.manufacturer:
self.can_combine_commands = True

self.min_mireds = RANGE_MIREDS[0]
self.max_mireds = RANGE_MIREDS[1]

self.min_hue = RANGE_HUE[0]
self.max_hue = RANGE_HUE[1]

self.min_saturation = RANGE_SATURATION[0]
self.max_saturation = RANGE_SATURATION[1]

@property
def raw(self):
"""Return raw data that it represents."""
Expand Down
2 changes: 1 addition & 1 deletion setup.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
with open(path.join(here, 'README.md'), encoding='utf-8') as f:
long_description = f.read()

VERSION = "5.4.2"
VERSION = "5.5"
DOWNLOAD_URL = \
'https://github.com/ggravlingen/pytradfri/archive/{}.zip'.format(VERSION)

Expand Down
133 changes: 133 additions & 0 deletions tests/test_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
from pytradfri.command import Command


def test_combining_mutates():
DATA_INT = {'key_int': 0}
DATA_INT2 = {'key_int_2': 1}
COMBINED_INT = {'key_int': 0, 'key_int_2': 1}

command1 = Command('method', 'path', DATA_INT)
command2 = Command('method', 'path', DATA_INT2)
combined = command1 + command2

# Adding shouldn't mutate the original commands
assert command1._data == DATA_INT
assert command2._data == DATA_INT2
assert combined._data == COMBINED_INT

# Combining should mutate the original command
command1.combine_data(command2)
assert command1._data == COMBINED_INT
assert command2._data == DATA_INT2


def test_combining_with_none():
DATA_INT = {'key_int': 0}

command1 = Command('method', 'path', DATA_INT)
combined = command1 + None

assert combined._data == DATA_INT

# Combining should mutate the original command
command1.combine_data(None)
assert command1._data == DATA_INT


def test_combining_integer_keys():
DATA_INT = {'key_int': 0}
DATA_INT_SAME_KEY = {'key_int': 1}
DATA_INT2 = {'key_int_2': 1}
COMBINED_INT = {'key_int': 0, 'key_int_2': 1}

command1 = Command('method', 'path', DATA_INT)
command2 = Command('method', 'path', DATA_INT2)
combined = command1 + command2
assert combined._data == COMBINED_INT

command1 = Command('method', 'path', DATA_INT)
command2 = Command('method', 'path', DATA_INT_SAME_KEY)
# We should always take the last key if we can't merge
combined = command1 + command2
assert combined._data == DATA_INT_SAME_KEY


def test_combining_string_keys():
DATA_STRING = {'key_string': 'a'}
DATA_STRING_SAME_KEY = {'key_string': 'same'}
DATA_STRING2 = {'key_string_2': 'b'}
COMBINED_STRING = {'key_string': 'a', 'key_string_2': 'b'}

command1 = Command('method', 'path', DATA_STRING)
command2 = Command('method', 'path', DATA_STRING2)
combined = command1 + command2
assert combined._data == COMBINED_STRING

command1 = Command('method', 'path', DATA_STRING)
command2 = Command('method', 'path', DATA_STRING_SAME_KEY)
# We should always take the last key if we can't merge
combined = command1 + command2
assert combined._data == DATA_STRING_SAME_KEY


def test_combining_dict_keys():
DATA_EMPTY_DICT = {'key_dict': {}}
DATA_DICT_INT = {'key_dict': {'key_int': 0}}
DATA_DICT_STRING = {'key_dict': {'key_string': 'a'}}
DATA_DICT_STRING2 = {'key_dict': {'key_string': 'b'}}
DATA_DICT_INTSTRING = {'key_dict': {'key_int': 0, 'key_string': 'a'}}

command1 = Command('method', 'path', DATA_EMPTY_DICT)
command2 = Command('method', 'path', DATA_DICT_INT)
combined = command1 + command2
assert combined._data == DATA_DICT_INT

command1 = Command('method', 'path', DATA_DICT_INT)
command2 = Command('method', 'path', DATA_DICT_STRING)
combined = command1 + command2
assert combined._data == DATA_DICT_INTSTRING

command1 = Command('method', 'path', DATA_DICT_STRING)
command2 = Command('method', 'path', DATA_DICT_STRING2)
combined = command1 + command2
assert combined._data == DATA_DICT_STRING2

command1 = Command('method', 'path', DATA_DICT_INT)
command2 = Command('method', 'path', DATA_DICT_STRING2)
command3 = Command('method', 'path', DATA_DICT_STRING)
combined = command1 + command2 + command3
assert combined._data == DATA_DICT_INTSTRING


def test_combining_list_keys():
DATA_EMPTY_LIST = {'key_list': []}
DATA_INT_LIST1 = {'key_list': [0, 1, 2]}
DATA_INT_LIST2 = {'key_list': [10, 11, 12]}

command1 = Command('method', 'path', DATA_EMPTY_LIST)
command2 = Command('method', 'path', DATA_INT_LIST1)
combined = command1 + command2
assert combined._data == DATA_INT_LIST1

# Duplicated keys are replaced if not dicts
command1 = Command('method', 'path', DATA_INT_LIST1)
command2 = Command('method', 'path', DATA_INT_LIST2)
combined = command1 + command2
assert combined._data == DATA_INT_LIST2


def test_combining_listed_dict_keys():
DATA_EMPTY_DICT = {'key_ldict': [{}]}
DATA_DICT_INT = {'key_ldict': [{'key_int': 0}]}
DATA_DICT_STRING = {'key_ldict': [{'key_string': 'a'}]}
DATA_DICT_INTSTRING = {'key_ldict': [{'key_int': 0, 'key_string': 'a'}]}

command1 = Command('method', 'path', DATA_EMPTY_DICT)
command2 = Command('method', 'path', DATA_DICT_INT)
combined = command1 + command2
assert combined._data == DATA_DICT_INT

command1 = Command('method', 'path', DATA_DICT_INT)
command2 = Command('method', 'path', DATA_DICT_STRING)
combined = command1 + command2
assert combined._data == DATA_DICT_INTSTRING

0 comments on commit 1d3b0e8

Please sign in to comment.