Skip to content

Commit a2ee0f1

Browse files
author
Rasmus Lager
committed
generator to get all of entities with start, stop. should have query support in future
1 parent 44d5569 commit a2ee0f1

File tree

3 files changed

+107
-1
lines changed

3 files changed

+107
-1
lines changed

ganapi/api.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import json
22
import requests
33
from gan_exception import GanException
4+
5+
46
class Api(object):
57
"""
68
Handles connection to the API
@@ -12,6 +14,11 @@ class Api(object):
1214
"""
1315
default_base_uri = 'https://api.getanewsletter.com/v3/'
1416

17+
"""
18+
The amount of entities to get in each request while using all
19+
:var batch_size int
20+
"""
21+
batch_size = 25
1522
"""
1623
Initializes the API connection
1724
:param string token The security token.

ganapi/entity_manager.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import urllib
22
from helpers import PaginatedResultSet
33
from api import GanException
4+
import urlparse
5+
import math
46

57
class EntityManager(object):
68
"""
@@ -185,6 +187,61 @@ def query(self, filters=dict(), as_json=False):
185187
return response.json()
186188
return PaginatedResultSet(self, response.json())
187189

190+
def all(self, start=0, stop=float('inf')):
191+
"""
192+
Method to get a generator with all or between(start, stop) entities of type.
193+
Will use Api.batch_size for pagination parameter to the api.
194+
When using start argument it will jump to the correct page after first request
195+
if the start item is not on the first page.
196+
:param start Start from entity index.
197+
:param stop Stop at entity index.
198+
:raises StopIteration if end of entities or too high start.
199+
:raises AssertionError if start or stop are invalid.
200+
:raises HTTPError if the data can not be fetched.
201+
:return: generator with entities of type.
202+
"""
203+
204+
def _calc_item_page(index, total, page_size):
205+
# Calculate item page based on index
206+
return math.ceil(float(((index + 1) % total)) / page_size)
207+
208+
if start:
209+
assert isinstance(start, int)
210+
if stop != float('inf'):
211+
assert isinstance(stop, int)
212+
assert start <= stop
213+
214+
uri = u'{base_path}/?paginate_by={batch_size}'.format(base_path=str.rstrip(self.base_path),
215+
batch_size=self.api.batch_size)
216+
count = None
217+
count_read = 0
218+
while uri and (count is None or count_read < count) and count_read <= stop:
219+
results = self.api.call('GET', uri).json()
220+
count = results.get('count', 0)
221+
222+
if start > count:
223+
raise StopIteration
224+
225+
for entity in results.get('results', []):
226+
if start <= count_read <= stop:
227+
yield self.construct_entity(entity).set_persisted()
228+
count_read += 1
229+
230+
if self.api.batch_size < start and start > count_read + 1:
231+
# Go straight to supposed page of the start item.
232+
start_page = int(_calc_item_page(start, count, self.api.batch_size))
233+
uri = u'{base_path}/?paginate_by={batch_size}&page={page}'.format(base_path=str.rstrip(self.base_path),
234+
batch_size=self.api.batch_size,
235+
page=start_page)
236+
# Set new count_read for all skipped pages.
237+
count_read = (start_page - 1) * self.api.batch_size
238+
else:
239+
if results.get('next'):
240+
uri = u'{base_path}/?{params}'.format(base_path=str.rstrip(self.base_path),
241+
params=urlparse.urlparse(results.get('next')).query)
242+
243+
return
244+
188245
def delete(self, entity):
189246
"""
190247
Deletes an entity.

ganapi/test_attribute_manager.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
from api import Api
33
from attribute import Attribute
44
from attribute_manager import AttributeManager
5-
from httmock import HTTMock, all_requests
5+
from httmock import HTTMock, all_requests, urlmatch
66
from requests import HTTPError
77

88

99
class ContactManagerTest(unittest.TestCase):
1010
def setUp(self):
1111
gan_token = 'h027MapNNujPH0gV+sXAdmzZTDffHOpJEHaBtrD3NXtNqI4dT3NLXhyTwiZr7PUOGZJNSGv/b9xVyaguX0nDrONGhudPkxtl5EoXrM4SOZHswebpSy2ehh0edrGVF7dVJVZLIlRwgViY3n3/2hMQ5Njp9JFywnOy7gMeaoKw0hYLRbd+wVqvl2oOnspXwGTTcZ9Y+cdP8jIhUUoXOieXst0IXVclAHXa+K1d15gKLcpmXzK+jx14wGEmb4t8MSU'
1212
self.api = Api(token=gan_token)
13+
self.api.batch_size = 2
1314
self.attribute_manager = AttributeManager(self.api)
1415
self.start_path = '/v3'
1516

@@ -122,3 +123,44 @@ def test_get_paginated_attributes(self):
122123
self.assertEqual(len(paginated_result_set.entities), 6)
123124

124125
self.assertRaises(StopIteration, paginated_result_set.next)
126+
127+
@all_requests
128+
def get_all_attr_mock(self, url, request):
129+
status_code = 200
130+
if url.query == 'paginate_by=2':
131+
content = '{"count":8,"next":"https://api.getanewsletter.com/v3/attributes/?page=2&paginate_by=2","previous":null,"results":[{"url":"https://api.getanewsletter.com/v3/attributes/attribute/","name":"attr0","code":"attribute","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/attribute2/","name":"attr1","code":"attribute2","usage_count":0}]}'
132+
elif url.query in ['page=2&paginate_by=2'] :
133+
content = '{"count":8,"next":"https://api.getanewsletter.com/v3/attributes/?page=3&paginate_by=2","previous":"https://api.getanewsletter.com/v3/attributes/?paginate_by=2","results":[{"url":"https://api.getanewsletter.com/v3/attributes/bu/","name":"attr2","code":"bu","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu1/","name":"attr3","code":"bu1","usage_count":0}]}'
134+
135+
elif url.query == 'page=3&paginate_by=2' or url.query == 'paginate_by=2&page=3':
136+
content = '{"count":8,"next":"https://api.getanewsletter.com/v3/attributes/?page=4&paginate_by=2","previous":"https://api.getanewsletter.com/v3/attributes/?page=2&paginate_by=2","results":[{"url":"https://api.getanewsletter.com/v3/attributes/bu2/","name":"attr4","code":"bu2","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu3/","name":"bu3","code":"bu3","usage_count":0}]}'
137+
138+
elif url.query == 'page=4&paginate_by=2':
139+
content = '{"count":8,"next":"https://api.getanewsletter.com/v3/attributes/?page=5&paginate_by=2","previous":"https://api.getanewsletter.com/v3/attributes/?page=3&paginate_by=2","results":[{"url":"https://api.getanewsletter.com/v3/attributes/bu4/","name":"bu4","code":"bu4","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu5/","name":"bu5","code":"bu5","usage_count":0}]}'
140+
else:
141+
return {'status_code': 404}
142+
143+
return {'content': content,
144+
'status_code': status_code}
145+
146+
def test_get_all_attributes(self):
147+
with HTTMock(self.get_all_attr_mock):
148+
all_attributes = self.attribute_manager.all()
149+
attrs = [attr for attr in all_attributes]
150+
self.assertEqual(len(attrs), 8)
151+
152+
def test_get_between_attributes(self):
153+
with HTTMock(self.get_all_attr_mock):
154+
self.assertEqual(self.api.batch_size, 2)
155+
# should get entities from page 3 and 4
156+
attributes_between = self.attribute_manager.all(start=4, stop=7)
157+
attrs = [attr for attr in attributes_between]
158+
self.assertEqual(len(attrs), 4)
159+
self.assertEqual(attrs[0].name, 'attr4')
160+
self.assertTrue(isinstance(attrs[0], Attribute))
161+
162+
def test_get_too_high_start(self):
163+
with HTTMock(self.get_all_attr_mock):
164+
attributes_between = self.attribute_manager.all(start=9)
165+
attrs = [attr for attr in attributes_between]
166+
self.assertEqual(len(attrs), 0)

0 commit comments

Comments
 (0)