Skip to content

Groups: send multiple feeds data at once #162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Adafruit_IO/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@
from .client import Client
from .mqtt_client import MQTTClient
from .errors import AdafruitIOError, RequestError, ThrottlingError, MQTTError
from .model import Data, Feed, Group, Dashboard, Block, Layout
from .model import Data, Feed, Group, Dashboard, Block, Layout, GroupFeedData
from ._version import __version__
45 changes: 42 additions & 3 deletions Adafruit_IO/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,40 @@
from time import struct_time
import json
import platform
import pkg_resources
try:
from importlib.metadata import version as pkg_version # Python 3.8+
except ImportError:
try:
from importlib_metadata import version as pkg_version # Backport for <3.8
except ImportError:
pkg_version = None
import re
from urllib.parse import urlparse
from urllib.parse import parse_qs
# import logging

import requests


from .errors import RequestError, ThrottlingError
from .model import Data, Feed, Group, Dashboard, Block, Layout

DEFAULT_PAGE_LIMIT = 100

# set outgoing version, pulled from setup.py
version = pkg_resources.require("Adafruit_IO")[0].version
# set outgoing version, pulled from setup.py or package metadata
_package_name = "Adafruit_IO"
_version = None
if pkg_version:
try:
_version = pkg_version(_package_name)
except Exception:
pass
if not _version:
try:
_version = pkg_resources.require(_package_name)[0].version
except Exception:
_version = "unknown"
version = _version
default_headers = {
'User-Agent': 'AdafruitIO-Python/{0} ({1}, {2} {3})'.format(version,
platform.platform(),
Expand Down Expand Up @@ -185,6 +204,26 @@ def send_batch_data(self, feed, data_list):
data_dict = type(data_list)((data._asdict() for data in data_list))
self._post(path, {"data": data_dict})

def send_group_multiple_data(self, group, data_list):
"""Create a new row of data in the specified group. Group can be a group
ID, group key, or group name. Data must be a list of GroupFeedData objects, or
a Dict with a feeds property, containing a list of GroupFeedData objects with
at least a value property and key set on it. Optionally, metadata (created_at,
lat/lon/ele) can be set at the root object level.
Returns a Data instance with details about the newly appended rows of data.

:param string group: Name/Key/ID of Adafruit IO group.
:param List[GroupFeedData]|Dict data_list: Multiple data values with keys.
"""
path = "groups/{0}/data".format(group)
if isinstance(data_list, list):
data_dict = {"feeds": [data._asdict() for data in data_list]}
elif isinstance(data_list, dict):
data_dict = data_list
else:
raise TypeError("data_list must be a dict or list")
self._post(path, data_dict)

def append(self, feed, value):
"""Helper function to simplify adding a value to a feed. Will append the
specified value to the feed identified by either name, key, or ID.
Expand Down
7 changes: 7 additions & 0 deletions Adafruit_IO/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
'lon',
'ele']

# List of fields/properties for GroupFeedData object
GROUPFEEDDATA_FIELDS = [ 'value',
'key']

FEED_FIELDS = [ 'name',
'key',
'id',
Expand Down Expand Up @@ -95,12 +99,14 @@
Dashboard = namedtuple('Dashboard', DASHBOARD_FIELDS)
Block = namedtuple('Block', BLOCK_FIELDS)
Layout = namedtuple('Layout', LAYOUT_FIELDS)
GroupFeedData = namedtuple('GroupFeedData', GROUPFEEDDATA_FIELDS)

# Magic incantation to make all parameters to the initializers optional with a
# default value of None.
Group.__new__.__defaults__ = tuple(None for x in GROUP_FIELDS)
Data.__new__.__defaults__ = tuple(None for x in DATA_FIELDS)
Layout.__new__.__defaults__ = tuple(None for x in LAYOUT_FIELDS)
GroupFeedData.__new__.__defaults__ = tuple(None for x in GROUPFEEDDATA_FIELDS)

# explicitly set dashboard values so that 'color_mode' is 'dark'
Dashboard.__new__.__defaults__ = (None, None, None, False, "dark", True, None, None)
Expand Down Expand Up @@ -147,3 +153,4 @@ def _dashboard_from_dict(cls, data):
Dashboard.from_dict = classmethod(_dashboard_from_dict)
Block.from_dict = classmethod(_from_dict)
Layout.from_dict = classmethod(_from_dict)
GroupFeedData.from_dict = classmethod(_from_dict)
57 changes: 56 additions & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import time
import unittest

from Adafruit_IO import Client, Data, Feed, Group, Dashboard, Block, Layout, RequestError
from Adafruit_IO import Client, Data, Feed, Group, Dashboard, Block, Layout, RequestError, GroupFeedData

import base

Expand Down Expand Up @@ -95,6 +95,61 @@ def test_send_batch_data(self):
data = io.receive(test_feed.key)
self.assertEqual(int(data.value), 42)

def test_send_group_multiple_data_as_list(self):
"""send_group_multiple_data
"""
io = self.get_client()
self.ensure_group_deleted(io, 'testgroup')
self.ensure_feed_deleted(io, 'testfeed1')
self.ensure_feed_deleted(io, 'testfeed2')
test_group = io.create_group(Group(name="testgroup"))
test_feed1 = io.create_feed(Feed(name="testfeed1"), test_group.key)
test_feed2 = io.create_feed(Feed(name="testfeed2"), test_group.key)
data_list = [
GroupFeedData(value=42, key=test_feed1.key.replace(
test_group.key + ".", "")),
GroupFeedData(value=42, key=test_feed2.key.replace(
test_group.key + ".", ""))
]
io.send_group_multiple_data(test_group.key, data_list)
data = io.receive(test_feed1.key)
self.assertEqual(int(data.value), 42)
data = io.receive(test_feed2.key)
self.assertEqual(int(data.value), 42)
self.ensure_feed_deleted(io, 'testfeed1')
self.ensure_feed_deleted(io, 'testfeed2')
self.ensure_group_deleted(io, 'testgroup')

def test_send_group_multiple_data_as_dict(self):
"""send_group_multiple_data
"""
io = self.get_client()
self.ensure_group_deleted(io, 'testgroup')
self.ensure_feed_deleted(io, 'testfeed1')
self.ensure_feed_deleted(io, 'testfeed2')
test_group = io.create_group(Group(name="testgroup"))
test_feed1 = io.create_feed(Feed(name="testfeed1"), test_group.key)
test_feed2 = io.create_feed(Feed(name="testfeed2"), test_group.key)
data_dict = {
"feeds": [
{"key": test_feed1.key.replace(
test_group.key + ".", ""), "value": 43},
{"key": test_feed2.key.replace(
test_group.key + ".", ""), "value": 43}
],
"lat": 40.726190,
"lon": -74.005334,
"ele": -6,
}
io.send_group_multiple_data(test_group.key, data_dict)
data = io.receive(test_feed1.key)
self.assertEqual(int(data.value), 43)
data = io.receive(test_feed2.key)
self.assertEqual(int(data.value), 43)
self.ensure_feed_deleted(io, 'testfeed1')
self.ensure_feed_deleted(io, 'testfeed2')
self.ensure_group_deleted(io, 'testgroup')

def test_receive_next(self):
"""receive_next
"""
Expand Down
21 changes: 19 additions & 2 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from Adafruit_IO import Data, Feed, Group, Dashboard, Block, Layout
from Adafruit_IO import Data, Feed, Group, Dashboard, Block, Layout, GroupFeedData

import base

Expand Down Expand Up @@ -59,7 +59,7 @@ def test_feeds_have_explicitly_set_values(self):
self.assertIsNone(feed.license)
self.assertIsNone(feed.status_notify)
self.assertIsNone(feed.status_timeout)

def test_group_properties_are_optional(self):
group = Group(name="foo")
self.assertEqual(group.name, 'foo')
Expand Down Expand Up @@ -116,3 +116,20 @@ def test_from_dict_ignores_unknown_items(self):
self.assertIsNone(data.expiration)
self.assertIsNone(data.position)
self.assertIsNone(data.id)


class TestGroupFeedData(base.IOTestCase):

def test_groupfeeddata_properties_are_optional(self):
"""GroupFeedData fields have optional properties
"""
data = GroupFeedData(value='foo', key='test_key')
self.assertEqual(data.value, 'foo')
self.assertEqual(data.key, 'test_key')

def test_groupfeeddata_from_dict_ignores_unknown_items(self):
data = GroupFeedData.from_dict(
{'value': 'foo', 'key': 'test_key', 'unknown_param': 42})
self.assertEqual(data.value, 'foo')
self.assertEqual(data.key, 'test_key')
self.assertFalse(hasattr(data, 'unknown_param'))