Skip to content

Commit d628d86

Browse files
committed
Add a generator to iterate through paginated methods
1 parent e6e9bba commit d628d86

File tree

2 files changed

+75
-10
lines changed

2 files changed

+75
-10
lines changed

scuttle/versions/v1.py

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ def wrapper(method, instance, args, kwargs):
1818
return instance.request(endpoint_url, *method(*args, **kwargs))
1919
return wrapper
2020

21+
def get_default_data(**kwargs):
22+
"""Returns a POST data dict using default values."""
23+
limit = kwargs.get('limit', 20)
24+
offset = kwargs.get('offset', 0)
25+
direction = kwargs.get('direction', 'asc')
26+
if not isinstance(limit, int):
27+
raise TypeError("`limit` must be int")
28+
if not isinstance(offset, int):
29+
raise TypeError("`offset` must be int")
30+
if not direction in ('asc', 'desc'):
31+
raise ValueError("`direction` must be one of 'asc', 'desc'")
32+
return { 'limit': limit, 'offset': offset, 'direction': direction }
33+
2134
class PaginatedMethod:
2235
"""Object representing a method that has a POST paginated version (with
2336
args like limit, offset, direction) as well as optionally a GET
@@ -38,12 +51,8 @@ def __call__(self, *args, **kwargs):
3851
)
3952
return self._method(*args, **kwargs)
4053

41-
def verbose(self, *args, limit=None, offset=None, direction=None):
42-
data = {
43-
'limit': 20 if limit is None else limit,
44-
'offset': 0 if offset is None else offset,
45-
'direction': 'asc' if direction is None else direction,
46-
}
54+
def verbose(self, *args, **kwargs):
55+
data = get_default_data(**kwargs)
4756
return self.__call__(*args, data=data, __verbose=True)
4857

4958
class Api(BaseApi):
@@ -64,6 +73,49 @@ def __init__(self, *args):
6473
self.wikidotuser_revisions = PaginatedMethod(self._wikidotuser_revisions)
6574
self.tags_pages = PaginatedMethod(self._tags_pages, True)
6675

76+
def verbose(self, method: Callable, *args, **kwargs):
77+
"""Returns a generator that iterates over a paginated method.
78+
79+
callable `method`: The paginated method to iterate.
80+
Remaining arguments will be passed to this method.
81+
82+
83+
Pass this function int `limit`, an initial int `offset`, and str
84+
`direction`. Each time the returned generator is called, it will
85+
increment `offset` by `limit` and return the method for the resulting
86+
set of parameters. Effectively, applied to a paginated method, this
87+
generator is the same as turning the page.
88+
89+
wiki = scuttle(domain, API_KEY, 1)
90+
generator = wiki.verbose(wiki.thread_posts, thread_id, limit=5)
91+
for posts in generator:
92+
print(len(posts)) # will be 5, except at very end
93+
94+
Note that at the end of the data, the length of the final 'page' will
95+
very likely be less than `limit`.
96+
"""
97+
print("hello!")
98+
print(method, isinstance(method, PaginatedMethod))
99+
if not isinstance(method, PaginatedMethod):
100+
raise TypeError("Iterated method must be a paginated method")
101+
data = get_default_data(**kwargs)
102+
103+
def paginated_generator():
104+
limit: int = data['limit']
105+
length: int = limit
106+
offset: int = data['offset']
107+
direction: str = data['direction']
108+
while True:
109+
result = method.verbose(*args, limit=limit, offset=offset,
110+
direction=direction)
111+
yield result
112+
if length < data['limit']:
113+
return
114+
length = len(result)
115+
offset += length
116+
117+
return paginated_generator()
118+
67119
@endpoint("wikis")
68120
def wikis(self):
69121
return None, None

test/test_api.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,31 @@ def test_pagination():
3535
page_slug = "main"
3636
page_id = wiki.page_by_slug(page_slug)['id']
3737
# non-paginated revisions - should just be metadata
38-
print("DOING non-paginated")
3938
non_paginated_revisions = wiki.page_revisions(page_id)
40-
print("DONE non-paginated")
4139
assert len(non_paginated_revisions) > 100
4240
assert 'content' not in non_paginated_revisions[0].keys()
4341
# paginated revisions - should include revision content
44-
print("DOING paginated")
4542
paginated_revisions = wiki.page_revisions.verbose(page_id)
46-
print("DONE paginated")
4743
assert 'content' in paginated_revisions[0].keys()
4844
assert len(paginated_revisions) == 20
4945

46+
def test_pagination_generator():
47+
wiki = scuttle.scuttle('en', API_KEY, 1)
48+
# make a generator
49+
page_slug = "main"
50+
page_id = wiki.page_by_slug(page_slug)['id']
51+
gen1 = wiki.verbose(wiki.page_revisions, page_id, limit=100)
52+
assert int(next(gen1)[0]['metadata']['wikidot_metadata']['revision_number']) == 0
53+
assert int(next(gen1)[0]['metadata']['wikidot_metadata']['revision_number']) == 100
54+
# make another generator, see if they interfere
55+
gen2 = wiki.verbose(wiki.page_revisions, page_id, limit=10, offset=10)
56+
assert int(next(gen2)[0]['metadata']['wikidot_metadata']['revision_number']) == 10
57+
assert int(next(gen2)[0]['metadata']['wikidot_metadata']['revision_number']) == 20
58+
assert int(next(gen1)[0]['metadata']['wikidot_metadata']['revision_number']) == 200
59+
# check errors
60+
with pytest.raises(TypeError):
61+
wiki.verbose(len)
62+
5063
def test_page():
5164
wiki = scuttle.scuttle('en', API_KEY, 1)
5265
pages = wiki.pages()

0 commit comments

Comments
 (0)