Skip to content

Commit 21cb728

Browse files
committed
Merge branch 'feature/pagination' of github.com:jcarbaugh/python-sunlight into jcarbaugh-feature/pagination
2 parents 6667318 + 7cb90c9 commit 21cb728

File tree

4 files changed

+140
-3
lines changed

4 files changed

+140
-3
lines changed

examples/paging

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env python
2+
from __future__ import print_function
3+
4+
import logging
5+
6+
from sunlight import congress, capitolwords
7+
from sunlight.pagination import PagingService, logger
8+
9+
10+
logger.setLevel(logging.DEBUG)
11+
12+
# try to create a paging service with an unpageable one
13+
try:
14+
capitolwords = PagingService(capitolwords)
15+
except ValueError, ve:
16+
print('ValueError: %s' % ve.message)
17+
18+
# create a pageable service
19+
congress = PagingService(congress)
20+
21+
print(len(list(congress.legislators(limit=1000)))) # page more than known results
22+
print(len(list(congress.legislators(limit=5)))) # page less than a single page
23+
print(len(list(congress.legislators(limit=55)))) # page more than a single page
24+
25+
# bypass unpageable methods
26+
print(len(congress.all_legislators_in_office()))
27+
28+
# page from an arbitrary page
29+
print(len(list(congress.legislators(limit=100, page=3))))

sunlight/pagination.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import time
2+
import logging
3+
4+
logger = logging.getLogger('sunlight.paginator')
5+
6+
7+
def pageable(func):
8+
func.is_pageable = True
9+
return func
10+
11+
12+
class PagingService(object):
13+
14+
limit_attr = 'limit'
15+
page_attr = 'page'
16+
per_page_attr = 'per_page'
17+
18+
delay = 0.1
19+
20+
def __init__(self, service=None, delay=None):
21+
22+
if service:
23+
self.service = service
24+
else:
25+
self.service = self.service_class()
26+
27+
if not getattr(self.service, 'is_pageable', False):
28+
raise ValueError('service must be a pagable service')
29+
30+
if delay is not None:
31+
self.delay = delay
32+
33+
def __getattr__(self, name):
34+
35+
attr = getattr(self.service, name, None)
36+
37+
if callable(attr) and getattr(attr, 'is_pageable', False):
38+
39+
def pagingfunc(*args, **kwargs):
40+
41+
count = 0
42+
43+
page = int(kwargs.get(self.page_attr, 1))
44+
per_page = int(kwargs.get(self.per_page_attr, 50))
45+
limit = int(kwargs.pop(self.limit_attr, per_page))
46+
47+
per_page = min(limit, per_page)
48+
49+
kwargs[self.per_page_attr] = per_page
50+
kwargs[self.page_attr] = 1
51+
52+
stopthepresses = False
53+
54+
while 1:
55+
56+
logger.debug('loading %s page %d' % (name, page))
57+
58+
kwargs[self.page_attr] = page
59+
resp = attr(*args, **kwargs)
60+
61+
if not resp:
62+
logger.debug('! %s returned 0 results this iteration, stopping' % name)
63+
stopthepresses = True
64+
65+
for rec in resp:
66+
67+
yield rec
68+
69+
count += 1
70+
71+
if count >= limit:
72+
logger.debug('! count exceeded limit, stopping')
73+
stopthepresses = True
74+
break
75+
76+
if count % per_page != 0:
77+
logger.debug('! %s returned less than number of requested results, stopping' % name)
78+
stopthepresses = True
79+
80+
if stopthepresses:
81+
break
82+
83+
page += 1
84+
time.sleep(self.delay)
85+
86+
return pagingfunc
87+
88+
return attr

sunlight/service.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ class Service:
4444
Base class for all the API implementations, as well as a bunch of common
4545
code on how to actually fetch text over the network.
4646
"""
47+
48+
is_pageable = False
49+
4750
@debug_cache
4851
def get(self, top_level_object, **kwargs):
4952
"""

sunlight/services/congress.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"""
1010

1111
import sunlight.service
12-
from sunlight.service import EntityList
13-
from sunlight.service import EntityDict
12+
from sunlight.service import EntityDict, EntityList
13+
from sunlight.pagination import pageable
1414
import json
1515

1616

@@ -51,6 +51,9 @@ class Congress(sunlight.service.Service):
5151
is the place to look for help on field names and examples.
5252
"""
5353

54+
is_pageable = True
55+
56+
@pageable
5457
def legislators(self, **kwargs):
5558
"""
5659
Search and filter for members of Congress.
@@ -60,6 +63,7 @@ def legislators(self, **kwargs):
6063
"""
6164
return self.get('legislators', **kwargs)
6265

66+
@pageable
6367
def legislator(self, identifier, id_type=LEGISLATOR_ID_TYPES[0], **kwargs):
6468
"""
6569
Retrieve a member of Congress by a unique identifier. Defaults to
@@ -91,7 +95,6 @@ def legislator(self, identifier, id_type=LEGISLATOR_ID_TYPES[0], **kwargs):
9195
return EntityDict(results[0], results._meta)
9296
return None
9397

94-
9598
def all_legislators_in_office(self, **kwargs):
9699
"""
97100
Returns all legislators currently in office (non-paginated response).
@@ -104,6 +107,7 @@ def all_legislators_in_office(self, **kwargs):
104107
})
105108
return self.get('legislators', **kwargs)
106109

110+
@pageable
107111
def locate_legislators_by_lat_lon(self, lat, lon, **kwargs):
108112
"""
109113
Find members of Congress by a latitude and longitude.
@@ -117,6 +121,7 @@ def locate_legislators_by_lat_lon(self, lat, lon, **kwargs):
117121
})
118122
return self.get('legislators/locate', **kwargs)
119123

124+
@pageable
120125
def locate_legislators_by_zip(self, zipcode, **kwargs):
121126
"""
122127
Find members of Congress by zip code.
@@ -129,6 +134,7 @@ def locate_legislators_by_zip(self, zipcode, **kwargs):
129134
})
130135
return self.get('legislators/locate', **kwargs)
131136

137+
@pageable
132138
def bills(self, **kwargs):
133139
"""
134140
Search and filter through bills in Congress.
@@ -138,6 +144,7 @@ def bills(self, **kwargs):
138144
"""
139145
return self.get('bills', **kwargs)
140146

147+
@pageable
141148
def bill(self, bill_id, **kwargs):
142149
"""
143150
Retrieve a bill by bill_id.
@@ -153,6 +160,7 @@ def bill(self, bill_id, **kwargs):
153160
return EntityDict(results[0], results._meta)
154161
return None
155162

163+
@pageable
156164
def search_bills(self, query, **kwargs):
157165
"""
158166
Search the full text of legislation, and other fields.
@@ -165,6 +173,7 @@ def search_bills(self, query, **kwargs):
165173
})
166174
return self.get('bills/search', **kwargs)
167175

176+
@pageable
168177
def upcoming_bills(self, **kwargs):
169178
"""
170179
Search and filter through upcoming bills in the House and Senate.
@@ -177,6 +186,7 @@ def upcoming_bills(self, **kwargs):
177186
"""
178187
return self.get('upcoming_bills', **kwargs)
179188

189+
@pageable
180190
def locate_districts_by_lat_lon(self, lat, lon, **kwargs):
181191
"""
182192
Find congressional districts by a latitude and longitude.
@@ -190,6 +200,7 @@ def locate_districts_by_lat_lon(self, lat, lon, **kwargs):
190200
})
191201
return self.get('/districts/locate', **kwargs)
192202

203+
@pageable
193204
def locate_districts_by_zip(self, zipcode, **kwargs):
194205
"""
195206
Find congressional districts by a latitude and longitude.
@@ -202,6 +213,7 @@ def locate_districts_by_zip(self, zipcode, **kwargs):
202213
})
203214
return self.get('/districts/locate', **kwargs)
204215

216+
@pageable
205217
def committees(self, **kwargs):
206218
"""
207219
Search and filter through committees in the House and Senate.
@@ -211,6 +223,7 @@ def committees(self, **kwargs):
211223
"""
212224
return self.get('committees', **kwargs)
213225

226+
@pageable
214227
def amendments(self, **kwargs):
215228
"""
216229
Search and filter through amendments in Congress.
@@ -220,6 +233,7 @@ def amendments(self, **kwargs):
220233
"""
221234
return self.get('amendments', **kwargs)
222235

236+
@pageable
223237
def votes(self, **kwargs):
224238
"""
225239
Search and filter through votes in Congress.
@@ -229,6 +243,7 @@ def votes(self, **kwargs):
229243
"""
230244
return self.get('votes', **kwargs)
231245

246+
@pageable
232247
def floor_updates(self, **kwargs):
233248
"""
234249
Search and filter through floor updates in the House and Senate.
@@ -238,6 +253,7 @@ def floor_updates(self, **kwargs):
238253
"""
239254
return self.get('floor_updates', **kwargs)
240255

256+
@pageable
241257
def hearings(self, **kwargs):
242258
"""
243259
Search and filter through committee hearings in the House and Senate.
@@ -247,6 +263,7 @@ def hearings(self, **kwargs):
247263
"""
248264
return self.get('hearings', **kwargs)
249265

266+
@pageable
250267
def nominations(self, **kwargs):
251268
"""
252269
Search and filter through presidential nominations in Congress.

0 commit comments

Comments
 (0)