-
Notifications
You must be signed in to change notification settings - Fork 387
Expand file tree
/
Copy pathcollection.py
More file actions
156 lines (126 loc) · 5.17 KB
/
collection.py
File metadata and controls
156 lines (126 loc) · 5.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
from pyactiveresource.collection import Collection
class PaginatedCollection(Collection):
"""
A subclass of Collection which allows cycling through pages of
data through cursor-based pagination.
:next_page_url contains a url for fetching the next page
:previous_page_url contains a url for fetching the previous page
You can use next_page_url and previous_page_url to fetch the next page
of data by calling Resource.find(from_=page.next_page_url)
"""
def __init__(self, *args, **kwargs):
"""If given a Collection object as an argument, inherit its metadata."""
metadata = kwargs.pop("metadata", None)
obj = args[0]
if isinstance(obj, Collection):
if metadata:
metadata.update(obj.metadata)
else:
metadata = obj.metadata
super(PaginatedCollection, self).__init__(obj, metadata=metadata)
else:
super(PaginatedCollection, self).__init__(metadata=metadata or {}, *args, **kwargs)
if not ("resource_class" in self.metadata):
raise AttributeError('Cursor-based pagination requires a "resource_class" attribute in the metadata.')
self.metadata["pagination"] = self.__parse_pagination()
self.next_page_url = self.metadata["pagination"].get("next", None)
self.previous_page_url = self.metadata["pagination"].get("previous", None)
self._next = None
self._previous = None
self._current_iter = None
self._no_iter_next = kwargs.pop("no_iter_next", True)
def __parse_pagination(self):
if "headers" not in self.metadata:
return {}
values = self.metadata["headers"].get("Link", self.metadata["headers"].get("link", None))
if values is None:
return {}
result = {}
for value in values.split(", "):
link, rel = value.split("; ")
result[rel.split('"')[1]] = link[1:-1]
return result
def has_previous_page(self):
"""Returns true if the current page has any previous pages before it."""
return bool(self.previous_page_url)
def has_next_page(self):
"""Returns true if the current page has any pages beyond the current position."""
return bool(self.next_page_url)
def previous_page(self, no_cache=False):
"""Returns the previous page of items.
Args:
no_cache: If true the page will not be cached.
Returns:
A PaginatedCollection object with the new data set.
"""
if self._previous:
return self._previous
elif not self.has_previous_page():
raise IndexError("No previous page")
return self.__fetch_page(self.previous_page_url, no_cache)
def next_page(self, no_cache=False):
"""Returns the next page of items.
Args:
no_cache: If true the page will not be cached.
Returns:
A PaginatedCollection object with the new data set.
"""
if self._next:
return self._next
elif not self.has_next_page():
raise IndexError("No next page")
return self.__fetch_page(self.next_page_url, no_cache)
def __fetch_page(self, url, no_cache=False):
next = self.metadata["resource_class"].find(from_=url)
if not no_cache:
self._next = next
self._next._previous = self
next._no_iter_next = self._no_iter_next
return next
def __iter__(self):
"""Iterates through all items, also fetching other pages."""
for item in super(PaginatedCollection, self).__iter__():
yield item
if self._no_iter_next:
return
try:
if not self._current_iter:
self._current_iter = self
self._current_iter = self.next_page()
for item in self._current_iter:
yield item
except IndexError:
return
def __len__(self):
"""If fetched count all the pages."""
if self._next:
count = len(self._next)
else:
count = 0
return count + super(PaginatedCollection, self).__len__()
class PaginatedIterator:
"""
This class implements an iterator over paginated collections which aims to
be more memory-efficient by not keeping more than one page in memory at a
time.
>>> from shopify import Product, PaginatedIterator
>>> for page in PaginatedIterator(Product.find()):
... for item in page:
... do_something(item)
...
# every page and the page items are iterated
"""
def __init__(self, collection):
if not isinstance(collection, PaginatedCollection):
raise TypeError("PaginatedIterator expects a PaginatedCollection instance")
self.collection = collection
self.collection._no_iter_next = True
def __iter__(self):
"""Iterate over pages, returning one page at a time."""
current_page = self.collection
while True:
yield current_page
try:
current_page = current_page.next_page(no_cache=True)
except IndexError:
return