Skip to content

Commit 06df42d

Browse files
authored
Merge pull request #131 from lcmcninch/get-pages
Implement a means of getting more than 1000 data points
2 parents 92093d4 + b533482 commit 06df42d

File tree

3 files changed

+71
-10
lines changed

3 files changed

+71
-10
lines changed

Adafruit_IO/client.py

+56-8
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,18 @@
2323
import json
2424
import platform
2525
import pkg_resources
26+
import re
27+
from urllib.parse import urlparse
28+
from urllib.parse import parse_qs
2629
# import logging
2730

2831
import requests
2932

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

36+
DEFAULT_PAGE_LIMIT = 100
37+
3338
# set outgoing version, pulled from setup.py
3439
version = pkg_resources.require("Adafruit_IO")[0].version
3540
default_headers = {
@@ -61,6 +66,9 @@ def __init__(self, username, key, proxies=None, base_url='https://io.adafruit.co
6166
# constructing the path.
6267
self.base_url = base_url.rstrip('/')
6368

69+
# Store the last response of a get or post
70+
self._last_response = None
71+
6472
@staticmethod
6573
def to_red(data):
6674
"""Hex color feed to red channel.
@@ -112,10 +120,12 @@ def _handle_error(response):
112120
def _compose_url(self, path):
113121
return '{0}/api/{1}/{2}/{3}'.format(self.base_url, 'v2', self.username, path)
114122

115-
def _get(self, path):
123+
def _get(self, path, params=None):
116124
response = requests.get(self._compose_url(path),
117125
headers=self._headers({'X-AIO-Key': self.key}),
118-
proxies=self.proxies)
126+
proxies=self.proxies,
127+
params=params)
128+
self._last_response = response
119129
self._handle_error(response)
120130
return response.json()
121131

@@ -125,6 +135,7 @@ def _post(self, path, data):
125135
'Content-Type': 'application/json'}),
126136
proxies=self.proxies,
127137
data=json.dumps(data))
138+
self._last_response = response
128139
self._handle_error(response)
129140
return response.json()
130141

@@ -133,6 +144,7 @@ def _delete(self, path):
133144
headers=self._headers({'X-AIO-Key': self.key,
134145
'Content-Type': 'application/json'}),
135146
proxies=self.proxies)
147+
self._last_response = response
136148
self._handle_error(response)
137149

138150
# Data functionality.
@@ -242,17 +254,53 @@ def receive_previous(self, feed):
242254
path = "feeds/{0}/data/previous".format(feed)
243255
return Data.from_dict(self._get(path))
244256

245-
def data(self, feed, data_id=None):
257+
def data(self, feed, data_id=None, max_results=DEFAULT_PAGE_LIMIT):
246258
"""Retrieve data from a feed. If data_id is not specified then all the data
247259
for the feed will be returned in an array.
248260
:param string feed: Name/Key/ID of Adafruit IO feed.
249261
:param string data_id: ID of the piece of data to delete.
262+
:param int max_results: The maximum number of results to return. To
263+
return all data, set to None.
250264
"""
251-
if data_id is None:
252-
path = "feeds/{0}/data".format(feed)
253-
return list(map(Data.from_dict, self._get(path)))
254-
path = "feeds/{0}/data/{1}".format(feed, data_id)
255-
return Data.from_dict(self._get(path))
265+
if max_results is None:
266+
res = self._get(f'feeds/{feed}/details')
267+
max_results = res['details']['data']['count']
268+
if data_id:
269+
path = "feeds/{0}/data/{1}".format(feed, data_id)
270+
return Data.from_dict(self._get(path))
271+
272+
params = {'limit': max_results} if max_results else None
273+
data = []
274+
path = "feeds/{0}/data".format(feed)
275+
while len(data) < max_results:
276+
data.extend(list(map(Data.from_dict, self._get(path,
277+
params=params))))
278+
nlink = self.get_next_link()
279+
if not nlink:
280+
break
281+
# Parse the link for the query parameters
282+
params = parse_qs(urlparse(nlink).query)
283+
if max_results:
284+
params['limit'] = max_results - len(data)
285+
return data
286+
287+
def get_next_link(self):
288+
"""Parse the `next` page URL in the pagination Link header.
289+
290+
This is necessary because of a bug in the API's implementation of the
291+
link header. If that bug is fixed, the link would be accesible by
292+
response.links['next']['url'] and this method would be broken.
293+
294+
:return: The url for the next page of data
295+
:rtype: str
296+
"""
297+
if not self._last_response:
298+
return
299+
link_header = self._last_response.headers['link']
300+
res = re.search('rel="next", <(.+?)>', link_header)
301+
if not res:
302+
return
303+
return res.groups()[0]
256304

257305
def create_data(self, feed, data):
258306
"""Create a new row of data in the specified feed.

docs/data.rst

+13
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ You can get all of the data for a feed by using the ``data(feed)`` method. The r
3333
for d in data:
3434
print('Data value: {0}'.format(d.value))
3535
36+
By default, the maximum number of data points returned is 1000. This limit can be changed by using the max_results parameter.
37+
38+
.. code-block:: python
39+
40+
# Get less than the default number of data points
41+
data = aio.data('Test', max_results=100)
42+
43+
# Get more than the default number of data points
44+
data = aio.data('Test', max_results=2000)
45+
46+
# Get all of the points
47+
data = aio.data('Test', max_results=None)
48+
3649
You can also get a specific value by ID by using the ``feeds(feed, data_id)`` method. This will return a single piece of feed data with the provided data ID if it exists in the feed. The returned object will be an instance of the Data class.
3750

3851

tests/test_client.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def ensure_block_deleted(self, client, dashboard, block):
6464

6565
def empty_feed(self, client, feed):
6666
# Remove all the data from a specified feed (but don't delete the feed).
67-
data = client.data(feed)
67+
data = client.data(feed, max_results=None)
6868
for d in data:
6969
client.delete(feed, d.id)
7070

@@ -406,4 +406,4 @@ def test_layout_update_layout(self):
406406

407407

408408
if __name__ == "__main__":
409-
unittest.main()
409+
unittest.main()

0 commit comments

Comments
 (0)