Skip to content

Commit 8b488ac

Browse files
author
Rasmus Lager
committed
introduced PaginatedResultSet when querying for entities and tests for attributes
1 parent b296d54 commit 8b488ac

File tree

5 files changed

+92
-17
lines changed

5 files changed

+92
-17
lines changed

ganapi/api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ def call(self, method, resource_path, payload=None):
5050
payload = json.dumps(payload)
5151

5252
if method == 'GET':
53-
response = requests.get(uri, headers=self.headers, data=payload)
53+
response = requests.get(uri, headers=self.headers)
5454
elif method == 'POST':
5555
response = requests.post(uri, headers=self.headers, data=payload)
5656
elif method == 'DELETE':
57-
response = requests.delete(uri, headers=self.headers, data=payload)
57+
response = requests.delete(uri, headers=self.headers)
5858
elif method == 'PUT':
5959
response = requests.put(uri, headers=self.headers, data=payload)
6060
elif method == 'PATCH':

ganapi/entity_manager.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import urllib
2+
from helpers import PaginatedResultSet
23

34

45
class EntityManager(object):
@@ -16,12 +17,12 @@ class EntityManager(object):
1617
"""
1718
base_path = None
1819
"""
19-
The class of the entity (e.g. 'Gan\Contact').
20+
The class of the entity (e.g. 'Contact').
2021
:var entity_class string
2122
"""
2223
entity_class = None
2324
"""
24-
List listing all writable fields. It is required by the default normalizeEntity()
25+
List listing all writable fields. It is required by the default normalize_entity()
2526
method that is used to clean up the entity before sending it to the API.
2627
:var writable_fields list
2728
"""
@@ -40,7 +41,7 @@ def get_path(self, id):
4041
Method used by the entity manager to construct the resource path.
4142
4243
This method would simply concatenate the lookup id at the end of
43-
the base path: presource/<id>/. You may have to override it if you
44+
the base path: resource/<id>/. You may have to override it if you
4445
have an unusual resource path scheme (for example the the subscribers'
4546
endpoint: lists/<hash>/subscribers/<email>/).
4647
@@ -102,7 +103,6 @@ def normalize_entity(self, entity):
102103
data = {}
103104
for property in self.writable_fields:
104105
if getattr(entity, property):
105-
# setattr(entity, property, data[property])
106106
data[property] = getattr(entity, property)
107107
return data
108108

@@ -170,18 +170,13 @@ def query(self, filters=dict()):
170170
page and paginate_by which are common.
171171
172172
:param filters dict of query parameters (e.g. {'search_email': 'test@', 'page': 2})
173-
:returns array The result array of entities.
173+
:returns class PaginatedResultSet which can iterate over pages PaginatedResultSet.entities is the current page array of entities.
174174
:raises RequestException if there is an error from the API.
175175
"""
176176
uri = u'{base_path}/?{encoded_filters}'.format(base_path=str.rstrip(self.base_path),
177177
encoded_filters=urllib.urlencode(filters))
178-
179178
response = self.api.call('GET', uri)
180-
result = []
181-
for data in response.json().get('results'):
182-
result.append(self.construct_entity(data).set_persisted())
183-
184-
return result
179+
return PaginatedResultSet(self, response.json())
185180

186181
def delete(self, entity):
187182
"""

ganapi/helpers.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import urlparse
2+
3+
4+
class PaginatedResultSet():
5+
"""
6+
Class to iterate result pages when searching for entities by query.
7+
8+
:param manager EntityManager of Entity queried.
9+
:param data json data to populate class with entities and previous/next
10+
:raises
11+
"""
12+
def __init__(self, manager, data):
13+
self.manager = manager
14+
self.entities = []
15+
for result in data.get('results'):
16+
self.entities.append(self.manager.construct_entity(result).set_persisted())
17+
self.count = data.get('count')
18+
self.next_link = data.get('next')
19+
self.previous_link = data.get('previous')
20+
21+
def __iter__(self):
22+
return self
23+
24+
def url_params_to_dict(self, url):
25+
"""
26+
:param url: url to get parameters from
27+
:return: parsed parameters as dict
28+
"""
29+
params = urlparse.urlparse(url).query
30+
parsed = urlparse.parse_qsl(params)
31+
return dict(parsed)
32+
33+
def next(self):
34+
"""
35+
Get next page of entity results
36+
:return: PaginatedResultSet of next result page
37+
:raises: StopIteration if no next is available
38+
"""
39+
if self.next_link:
40+
query_dict = self.url_params_to_dict(self.next_link)
41+
return self.manager.query(filters=query_dict)
42+
else:
43+
raise StopIteration
44+
45+
def previous(self):
46+
"""
47+
Get next page of entity results
48+
:return: PaginatedResultSet of previous result page
49+
:raises: StopIteration if no previous is available
50+
"""
51+
if self.previous_link:
52+
query_dict = self.url_params_to_dict(self.previous_link)
53+
return self.manager.query(filters=query_dict)
54+
else:
55+
raise StopIteration

ganapi/test_attribute_manager.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,29 @@ def test_delete_attribute(self):
7878
attribute = Attribute()
7979
attribute.code = 'changed-attribute'
8080
with HTTMock(self.delete_attribute_mock):
81-
self.attribute_manager.delete(attribute)
81+
self.attribute_manager.delete(attribute)
82+
83+
@all_requests
84+
def get_paginated_attributes_mock(self, url, request):
85+
content = '{"count":16,"next":"https://api.getanewsletter.com/v3/attributes/?page=2","previous":null,"results":[{"url":"https://api.getanewsletter.com/v3/attributes/attribute/","name":"attribute","code":"attribute","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/attribute2/","name":"Attribute2","code":"attribute2","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu/","name":"bu","code":"bu","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu1/","name":"bu1","code":"bu1","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu2/","name":"bu2","code":"bu2","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu3/","name":"bu3","code":"bu3","usage_count":0},{"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},{"url":"https://api.getanewsletter.com/v3/attributes/bu6/","name":"bu6","code":"bu6","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu7/","name":"bu7","code":"bu7","usage_count":0}]}'
86+
status_code = 200
87+
return {'content': content,
88+
'status_code': status_code}
89+
90+
def get_paginated_attributes_page2_mock(self, url, request):
91+
content = '{"count":16,"next":null,"previous":"https://api.getanewsletter.com/v3/attributes/","results":[{"url":"https://api.getanewsletter.com/v3/attributes/bu8/","name":"bu8","code":"bu8","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu9/","name":"bu9","code":"bu9","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu10/","name":"bu10","code":"bu10","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu11/","name":"bu11","code":"bu11","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu12/","name":"bu12","code":"bu12","usage_count":0},{"url":"https://api.getanewsletter.com/v3/attributes/bu13/","name":"bu13","code":"bu13","usage_count":0}]}'
92+
status_code = 200
93+
return {'content': content,
94+
'status_code': status_code}
95+
96+
def test_get_paginated_attributes(self):
97+
with HTTMock(self.get_paginated_attributes_mock):
98+
paginated_result_set = self.attribute_manager.query({})
99+
self.assertEqual(len(paginated_result_set.entities), 10)
100+
self.assertTrue(isinstance(paginated_result_set.entities[0], Attribute))
101+
102+
with HTTMock(self.get_paginated_attributes_page2_mock):
103+
next_result_set = paginated_result_set.next()
104+
self.assertEqual(len(next_result_set.entities), 6)
105+
106+
self.assertRaises(StopIteration, next_result_set.next)

ganapi/test_contact_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import unittest
22
from api import Api
33
from contact import Contact
4-
54
from contact_manager import ContactManager
65
from httmock import HTTMock, all_requests
76

@@ -118,8 +117,9 @@ def test_find_contact_by_query(self):
118117
}
119118
with HTTMock(self.find_contact_by_query_mock):
120119
result = self.contact_manager.query(filters=query)
121-
self.assertEqual(len(result), 1)
122-
self.assertEqual(result[0].email, 'test@example.com')
120+
contacts = result.entities
121+
self.assertEqual(len(contacts), 1)
122+
self.assertEqual(contacts[0].email, 'test@example.com')
123123

124124
@all_requests
125125
def delete_contact_mock(self, url, request):

0 commit comments

Comments
 (0)