diff --git a/Adafruit_IO/__init__.py b/Adafruit_IO/__init__.py index e34eb3d..01e7587 100644 --- a/Adafruit_IO/__init__.py +++ b/Adafruit_IO/__init__.py @@ -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__ diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 3f92326..46f8519 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -22,7 +22,13 @@ 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 @@ -30,13 +36,26 @@ 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(), @@ -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. diff --git a/Adafruit_IO/model.py b/Adafruit_IO/model.py index 51d5633..74ef33e 100644 --- a/Adafruit_IO/model.py +++ b/Adafruit_IO/model.py @@ -41,6 +41,10 @@ 'lon', 'ele'] +# List of fields/properties for GroupFeedData object +GROUPFEEDDATA_FIELDS = [ 'value', + 'key'] + FEED_FIELDS = [ 'name', 'key', 'id', @@ -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) @@ -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) diff --git a/tests/test_client.py b/tests/test_client.py index 7714909..613d7e6 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -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 @@ -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 """ diff --git a/tests/test_model.py b/tests/test_model.py index 02b105e..7212f4c 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -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 @@ -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') @@ -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'))