Skip to content

Commit 1801a19

Browse files
authored
Merge pull request #94 from z3c0/test (v0.9.59)
v0.9.59
2 parents 3cb77a4 + 705b986 commit 1801a19

File tree

10 files changed

+163
-29
lines changed

10 files changed

+163
-29
lines changed

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,11 @@ Currently, the only public datasets supported by Vistos are the [Biographical Di
148148

149149
The `CongressMember` class exists for querying data from the perspective of members. `CongressMember` is a much faster option for when you know the specific member(s) you would like to download data for.
150150

151-
`CongressMember` takes a Bioguide ID as an argument, and attempts to retrieve the specified data for the member associated with the given ID.
151+
`CongressMember` takes a Bioguide ID as an argument, and attempts to retrieve data for the member associated with the given ID.
152152

153153
#### `.load()` <a name="member_load"></a>
154154

155-
Manually load datasets specified when instantiating `CongressMember`
155+
The `load` method manually load the datasets specified when instantiating `CongressMember`
156156

157157
``` python
158158
member = v.CongressMember('P000587', load_immediately=False)
@@ -179,7 +179,7 @@ print(member.govinfo['title'])
179179
Senator Edward M. Kennedy, Biography
180180
```
181181

182-
Due to limitations in the GovInfo API, directly retrieving data about a member is not possible. Vistos attempts to work around these limitations by first requesting the members Bioguide data, and using the information found there to narrow down where to locate the member's data within the GovInfo API.
182+
Due to limitations in the GovInfo API, directly retrieving data about a member is not possible. Vistos attempts to work around these limitations by first requesting the members Bioguide data, and using the information found there to narrow down where to locate the member's data within the Congressional Directories of the Congresses the member belonged to.
183183

184184
#### `.bioguide_id` <a name="member_bioguide_id"></a>
185185

@@ -444,9 +444,11 @@ def main():
444444
print(f'[{bioguide_id}] Downloading...', end='\r')
445445
try:
446446
member.load()
447-
print(f'[{bioguide_id}] Downloaded ')
447+
print(f'[{bioguide_id}] Downloaded ', end='')
448448
except v.gpo.BioguideConnectionError:
449-
print(f'[{bioguide_id}] Download Failed')
449+
print(f'[{bioguide_id}] Download Failed', end='')
450+
finally:
451+
print()
450452

451453
# Split terms from the rest of the data
452454
member_header_record = dict(member.bioguide)
@@ -510,7 +512,7 @@ If you'd like to contribute to the project, or know of a useful data source, fee
510512

511513
## Known Issues and Workarounds <a name="issues"></a>
512514

513-
1. Querying GovInfo not returning results for some congress persons.
515+
1. Querying GovInfo does not return results for some congress persons.
514516

515517
When using the `CongressMember` class, some congress persons will not return GovInfo data. This is due to their GovInfo data missing a Bioguide ID, which is used for finding individual records within a Congressional Directory package. Should you need data for one of these members, the only workaround (currently) is to instead query GovInfo for the entire Congress that they served for, using the `Congress` class.
516518

tests/test_integration.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,12 @@ def test_bioguide_query_object(self):
187187
self.assertEqual(query.last_name, 'wren')
188188
self.assertEqual(query.first_name, 'thomas')
189189

190+
def test_congress_bills(self):
191+
congress = v.Congress(105, self.GOVINFO_API_KEY)
192+
congress.load_bills()
193+
self.assertIsNotNone(congress.bills)
194+
self.assertEqual(len(congress.bills), 13126)
195+
190196

191197
if __name__ == '__main__':
192198
VistosIntegrationTests.GOVINFO_API_KEY = config('GOVINFO_API_KEY')

vistos/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.9.56
1+
0.9.60

vistos/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"""vistos (V)"""
33
from os import path as _path
44

5-
from vistos.src import (Congress, CongressMember, search_bioguide_members,
6-
search_govinfo_members, gpo)
5+
from vistos.src import (Congress, CongressMember, CongressBills,
6+
search_bioguide_members, search_govinfo_members, gpo)
77

8-
__all__ = ['gpo', 'Congress', 'CongressMember', 'search_bioguide_members',
9-
'search_govinfo_members']
8+
__all__ = ['gpo', 'Congress', 'CongressMember', 'CongressBills',
9+
'search_bioguide_members', 'search_govinfo_members']
1010

1111
VERSION = open(_path.join(_path.dirname(__file__), 'VERSION'), 'r').read()

vistos/src/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Backbone for V module"""
2-
from vistos.src.duo import (Congress, CongressMember, search_bioguide_members,
3-
search_govinfo_members)
2+
from vistos.src.duo import (Congress, CongressMember, CongressBills,
3+
search_bioguide_members, search_govinfo_members)
44

5-
__all__ = ['Congress', 'CongressMember', 'search_bioguide_members',
6-
'search_govinfo_members']
5+
__all__ = ['Congress', 'CongressMember', 'CongressBills',
6+
'search_bioguide_members', 'search_govinfo_members']

vistos/src/duo.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@ def search_govinfo_members(govinfo_api_key, first_name=None, last_name=None,
3939
return members_list
4040

4141

42+
class CongressBills(list):
43+
"""An object for query bills data for a single Congress"""
44+
45+
def __init__(self, congress_number, govinfo_api_key, bill_type=None,
46+
load_immediately=True):
47+
self._bills = None
48+
49+
self._number = gpo.convert_to_congress_number(congress_number)
50+
self._years = gpo.get_congress_years(self._number)
51+
52+
self._load_bills = \
53+
gpo.govinfo.create_bills_func(govinfo_api_key, self._number)
54+
55+
if load_immediately:
56+
self.load()
57+
58+
def load(self):
59+
for bill in self._load_bills():
60+
self.append(bill)
61+
62+
4263
class CongressMember:
4364
"""An object for querying data for a single Congress member"""
4465

@@ -245,6 +266,8 @@ def __init__(self, number_or_year=None, govinfo_api_key=None,
245266

246267
if govinfo_exists:
247268
self._enable_govinfo(govinfo_api_key)
269+
self._bills = \
270+
CongressBills(self._number, govinfo_api_key, None, False)
248271
else:
249272
include_bioguide = True
250273

@@ -267,8 +290,8 @@ def load(self):
267290

268291
def load_bills(self):
269292
"""Manually load bills issued during the current Congress"""
270-
if self.load_bills is not None:
271-
self._bills = self._load_bills()
293+
if self._bills is not None:
294+
self._bills.load()
272295

273296
def _enable_govinfo(self, key):
274297
"""Enable loading govinfo when load() is called"""
@@ -335,11 +358,13 @@ def bioguide(self):
335358

336359
@property
337360
def govinfo(self):
338-
"""returns GovInfo data as `GovInfoCongressRecord`"""
361+
"""returns GovInfo Congressional Directory data as a
362+
`GovInfoCongressRecord`"""
339363
return self._gi
340364

341365
@property
342366
def bills(self):
367+
"""Return GovInfo Bills data"""
343368
return self._bills
344369

345370
@bioguide.setter

vistos/src/gpo/bioguideretro.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def __init__(self, xml_data) -> None:
8888
str(xml_data.find('term-state').text).upper()
8989

9090
party = xml_data.find('term-party').text
91-
if party == 'NA' or party.strip() == '':
91+
if party == 'NA' or (party and party.strip() == ''):
9292
self[_fields.Term.PARTY] = None
9393
else:
9494
self[_fields.Term.PARTY] = str(party).lower()

vistos/src/gpo/fields.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class Term:
3636

3737
class Bill:
3838
"""Headers for GovInfoBillRecord"""
39+
BILL_ID = 'bill_id'
3940
CONGRESS = 'congress_number'
4041
TITLE = 'title'
4142
SHORT_TITLE = 'short_title'

vistos/src/gpo/govinfo.py

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,105 @@ class GovInfoBillRecord(dict):
1919

2020
def __init__(self, bill_govinfo: dict):
2121
super().__init__()
22+
self[_fields.Bill.BILL_ID] = (bill_govinfo['congress'] + '-' +
23+
bill_govinfo['session'] + '-' +
24+
bill_govinfo['billType'] + '-' +
25+
bill_govinfo['billNumber'] + '-' +
26+
bill_govinfo['billVersion'])
27+
2228
self[_fields.Bill.TITLE] = bill_govinfo['title']
2329
try:
2430
self[_fields.Bill.SHORT_TITLE] = \
2531
bill_govinfo['shortTitle'][0]['title']
2632
except (KeyError, IndexError):
2733
self[_fields.Bill.SHORT_TITLE] = None
2834

35+
self[_fields.Bill.CONGRESS] = bill_govinfo['congress']
36+
2937
self[_fields.Bill.DATE_ISSUED] = bill_govinfo['dateIssued']
3038
self[_fields.Bill.PAGES] = bill_govinfo['pages']
31-
self[_fields.Bill.GOVERNMENT_AUTHOR] = \
32-
bill_govinfo['governmentAuthor2']
39+
self[_fields.Bill.SESSION] = int(bill_govinfo['session'])
40+
self[_fields.Bill.BILL_NUMBER] = int(bill_govinfo['billNumber'])
3341
self[_fields.Bill.DOC_CLASS_NUMBER] = bill_govinfo['suDocClassNumber']
3442
self[_fields.Bill.BILL_TYPE] = bill_govinfo['billType']
43+
self[_fields.Bill.SESSION] = int(bill_govinfo['session'])
44+
self[_fields.Bill.BILL_NUMBER] = int(bill_govinfo['billNumber'])
3545
self[_fields.Bill.BILL_VERSION] = bill_govinfo['billVersion']
36-
self[_fields.Bill.IS_APPROPRATION] = bill_govinfo['isAppropriation']
37-
self[_fields.Bill.IS_PRIVATE] = bill_govinfo['isPrivate']
38-
self[_fields.Bill.COMMITTEES] = bill_govinfo['committess']
39-
self[_fields.Bill.MEMBERS] = bill_govinfo['members']
46+
self[_fields.Bill.IS_APPROPRATION] = \
47+
bool(bill_govinfo['isAppropriation'])
48+
self[_fields.Bill.IS_PRIVATE] = bool(bill_govinfo['isPrivate'])
49+
50+
try:
51+
self[_fields.Bill.GOVERNMENT_AUTHOR] = \
52+
bill_govinfo['governmentAuthor2']
53+
except KeyError:
54+
self[_fields.Bill.GOVERNMENT_AUTHOR] = None
55+
56+
try:
57+
self[_fields.Bill.COMMITTEES] = bill_govinfo['committees']
58+
except KeyError:
59+
self[_fields.Bill.COMMITTEES] = None
60+
61+
try:
62+
self[_fields.Bill.MEMBERS] = bill_govinfo['members']
63+
except KeyError:
64+
self[_fields.Bill.MEMBERS] = None
65+
66+
@property
67+
def title(self) -> str:
68+
return self[_fields.Bill.TITLE]
69+
70+
@property
71+
def short_title(self) -> str:
72+
return self[_fields.Bill.SHORT_TITLE]
73+
74+
@property
75+
def date_issued(self) -> str:
76+
return self[_fields.Bill.DATE_ISSUED]
77+
78+
@property
79+
def number_of_pages(self) -> int:
80+
return self[_fields.Bill.PAGES]
81+
82+
@property
83+
def government_author(self) -> str:
84+
return self[_fields.Bill.GOVERNMENT_AUTHOR]
85+
86+
@property
87+
def doc_class_number(self) -> str:
88+
return self[_fields.Bill.DOC_CLASS_NUMBER]
89+
90+
@property
91+
def bill_type(self) -> str:
92+
return self[_fields.Bill.BILL_TYPE]
93+
94+
@property
95+
def session(self) -> int:
96+
return self[_fields.Bill.SESSION]
97+
98+
@property
99+
def bill_number(self) -> int:
100+
return self[_fields.Bill.BILL_NUMBER]
101+
102+
@property
103+
def bill_version(self) -> str:
104+
return self[_fields.Bill.BILL_VERSION]
105+
106+
@property
107+
def is_appropration(self) -> bool:
108+
return self[_fields.Bill.IS_APPROPRATION]
109+
110+
@property
111+
def is_private(self) -> bool:
112+
return self[_fields.Bill.IS_PRIVATE]
113+
114+
@property
115+
def committees(self) -> List:
116+
return self[_fields.Bill.COMMITTEES]
117+
118+
@property
119+
def members(self) -> List:
120+
return self[_fields.Bill.MEMBERS]
40121

41122

42123
class GovInfoCongressRecord(dict):
@@ -53,7 +134,7 @@ def __init__(self, number: int, start_year: int,
53134

54135
@property
55136
def number(self) -> int:
56-
"""The number of the given Congress0"""
137+
"""The number of the given Congress"""
57138
return self[_fields.Congress.NUMBER]
58139

59140
@property
@@ -258,8 +339,10 @@ def _get_bills(api_key: str, congress: int):
258339
bill_records = []
259340
for package in packages:
260341
package_link = package['packageLink']
261-
package_data = _get_text_from(package_link)
262-
bill_records.append(GovInfoBillRecord(package_data))
342+
package_endpoint = f'{package_link}?api_key={api_key}'
343+
package_data = _get_text_from(package_endpoint)
344+
package_json = _json.loads(package_data)
345+
bill_records.append(GovInfoBillRecord(package_json))
263346

264347
return bill_records
265348

@@ -546,6 +629,9 @@ def _get_text_from(endpoint: str) -> str:
546629
try:
547630
response = _requests.get(_endpoint_url(endpoint))
548631
response_text = response.text
632+
633+
if response.status_code == 404:
634+
raise _requests.exceptions.ConnectionError()
549635
break
550636
except _requests.exceptions.ConnectionError:
551637
if attempts < _util.MAX_REQUEST_ATTEMPTS:
@@ -646,7 +732,9 @@ def _granule_content_endpoint(api_key: str, package_id: str,
646732

647733
def _endpoint_url(endpoint) -> str:
648734
"""Creates a URL endpoint based on the path given"""
649-
return _util.GOVINFO_API_URL_STR + endpoint
735+
if _util.GOVINFO_API_URL_STR not in endpoint:
736+
return _util.GOVINFO_API_URL_STR + endpoint
737+
return endpoint
650738

651739

652740
def _query_string(**kwargs) -> str:

vistos/src/gpo/option.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ def is_valid_bioguide_state(state: Optional[str]) -> bool:
3131
return state in valid_states
3232

3333

34+
class Bill:
35+
class Type:
36+
HOUSE_BILL = 'hr'
37+
SENATE_BILL = 's'
38+
HOUSE_JOINT_RESOLUTION = 'hjres'
39+
SENATE_JOINT_RESOLUTION = 'sjres'
40+
HOUSE_CONCURRENT_RESOLUTION = 'hconres'
41+
SENATE_CONCURRENT_RESOLUTION = 'sconres'
42+
HOUSE_SIMPLE_RESOLUTION = 'hres'
43+
SENATE_SIMPLE_RESOLUTION = 'sres'
44+
45+
3446
class Position:
3547
"""Available options for member positions"""
3648
REPRESENTATIVE = 'Representative'

0 commit comments

Comments
 (0)