This file defines python classes for every JSON object returned in responses from the endpoints of the Cicero API. When an API request is submitted and data received (using the CiceroRestConnection class in cicero_rest_connection.py, a RootCiceroObject (defined here) is instantiated from the API's JSON response. The RootCiceroObject instantiates a ResponseObject (which often instantiates a GeocodingResultsObject, which follows with one of the GeocodingCandidate objects, etc etc) and so on in a cascading fashion, until a class has been instantiated for every corresponding JSON object in the API response. (Currently, the only exception to this is the "data" JSON object found in most nonlegislative district objects, but this can still be accessed - just with python ['dictionary']['notation'].)
In other words, at the end we are left with a RootCiceroObject instance containing instances of other classes defined in this file.
An abstract base class, AbstractCiceroObject, defines str and repr methods which all classes use. The private function _copy_keys facilitates initializing class attributes from JSON values.
def _copy_keys(lhs, rhs, keys):
for k in keys:
lhs[k] = rhs[k]
class AbstractCiceroObject(object):
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.__dict__)
def __str__(self):
return str(self.__dict__)
This class represents the attributes of any external identifiers that most officials have - Facebook page, Twitter, Project VoteSmart, etc
class IdentifierObject(AbstractCiceroObject):
def __init__(self, identifier_dict):
_copy_keys(self.__dict__, identifier_dict,
('valid_from', 'valid_to', 'sk', 'id', 'official',
'last_update_date', 'identifier_type', 'identifier_value'))
This class represents the attributes of a committee a legislator belongs to. Committees are an antiquated feature of Cicero and few officials are associated with them, so this information may not be up to date.
class CommitteeObject(AbstractCiceroObject):
def __init__(self, comm_dict):
_copy_keys(self.__dict__, comm_dict,
('valid_from', 'description', 'valid_to', 'sk',
'last_update_date', 'id'))
This class represents the attributes of a country object within a GovernmentObject (itself in turn within a ChamberObject), OR a "representing_country" object within an OfficeObject (itself within an OfficialObject).
response
response
response
response
response
response
class CountryObject(AbstractCiceroObject):
def __init__(self, country_dict):
_copy_keys(self.__dict__, country_dict,
('status', 'name_short', 'gmi_3', 'valid_from',
'name_short_iso', 'name_short_local', 'valid_to', 'id',
'sk', 'name_short_un', 'fips', 'last_update_date', 'iso_3',
'iso_2', 'iso_3_numeric', 'name_long_local', 'name_long'))
This class represents a government, within an official's ChamberObject.
response
response
response
response
class GovernmentObject(AbstractCiceroObject):
def __init__(self, gov_dict):
_copy_keys(self.__dict__, gov_dict,
('city', 'state', 'name', 'notes', 'type'))
self.country = CountryObject(gov_dict['country'])
This class represents the chamber an official serves in (legislature, senate, house of representatives, etc).
response
response
response
response
class ChamberObject(AbstractCiceroObject):
def __init__(self, chamber_dict):
_copy_keys(self.__dict__, chamber_dict,
('id', 'type', 'official_count', 'is_chamber_complete',
'has_geographic_representation', 'name_native_language',
'name_formal', 'name', 'url', 'contact_email', 'contact_phone',
'election_rules', 'election_frequency',
'term_length', 'term_limit', 'redistricting_rules',
'inauguration_rules', 'vacancy_rules', 'last_update_date',
'legislature_update_date', 'notes', 'remarks'))
self.government = GovernmentObject(chamber_dict['government'])
This class represents an election event object.
class ElectionEventObject(AbstractCiceroObject):
def __init__(self, election_event_dict):
_copy_keys(self.__dict__, election_event_dict,
('is_approximate', 'last_update_date', 'remarks',
'election_date_text', 'valid_from', 'is_national',
'is_state', 'is_by_election', 'is_referendum', 'valid_to',
'label', 'sk', 'id', 'is_primary_election', 'urls',
'is_runoff_election', 'is_transnational'))
because .chambers is a list of ChamberObjects, we can't use _copy_keys and will define it here import ipdb; ipdb.set_trace()
self.chambers = [ChamberObject(c) for c in election_event_dict['chambers']]
This class represents a legislative or nonlegislative district object. The same object is returned with either the official API call or either legislative or nonlegislative district calls.
.data (dictionary) - additional data/info for this district. Keys differ for each district type (ie, census, school, watershed, etc). Therefore, unlike the rest of the wrapper which can be navigated using dot.style.notation, one must navigate the values in "data" ['like']['a']['dictionary'].
.id (integer) - Cicero unique ID
response
response
response
response
class DistrictObject(AbstractCiceroObject):
def __init__(self, district_dict):
_copy_keys(self.__dict__, district_dict,
('district_type', 'city', 'valid_from', 'country',
'district_id', 'valid_to', 'label', 'sk', 'subtype',
'state', 'last_update_date', 'data', 'id'))
This class represents details about the political office an elected official holds.
class OfficeObject(AbstractCiceroObject):
def __init__(self, office_dict):
_copy_keys(self.__dict__, office_dict,
('valid_from', 'representing_state', 'notes', 'title',
'valid_to', 'sk', 'last_update_date', 'election_rules',
'id', 'representing_city'))
self.district = DistrictObject(office_dict['district'])
self.representing_country = CountryObject(office_dict['representing_country'])
self.chamber = ChamberObject(office_dict['chamber'])
This class represents details about physical addresses an official may have, such as their district office, capitol office, phone and fax numbers, etc.
class AddressObject(AbstractCiceroObject):
def __init__(self, address_dict):
_copy_keys(self.__dict__, address_dict,
('county', 'postal_code', 'phone_1', 'phone_2', 'fax_1',
'fax_2', 'city', 'state', 'address_1', 'address_2',
'address_3'))
convenience function to print out what could be used as an envelope address label
def get_formatted_mailing_address(self, county=False):
mailing_address = ""
def add_part(template, string):
return template % string if string else ""
mailing_address += add_part("%s\n", self.address_1)
mailing_address += add_part("%s\n", self.address_2)
mailing_address += add_part("%s\n", self.address_3)
mailing_address += add_part("%s, ", self.city)
if user has set county=True and wants the county in the address
if county:
mailing_address += add_part("%s, ", self.county)
mailing_address += add_part("%s ", self.state)
mailing_address += add_part("%s", self.postal_code)
return mailing_address
This class is the main class for details about a particular elected official.
class OfficialObject(AbstractCiceroObject):
def __init__(self, official_dict):
_copy_keys(self.__dict__, official_dict,
('last_name', 'initial_term_start_date',
'current_term_start_date', 'web_form_url', 'last_update_date',
'salutation', 'id', 'photo_origin_url', 'middle_initial',
'first_name', 'valid_from', 'notes', 'name_suffix',
'valid_to', 'sk', 'term_end_date', 'urls', 'party',
'email_addresses'))
self.addresses = [AddressObject(a) for a in official_dict['addresses']]
self.office = OfficeObject(official_dict['office'])
self.committees = [CommitteeObject(c) for c in official_dict['committees']]
self.identifiers = [IdentifierObject(i) for i in official_dict['identifiers']]
def find_identifier(self, identifier_type):
iterate through self.identifiers and search on type If one or more of same type, return all those IdentifierObjects in a list
return [identifier for identifier in self.identifiers
if identifier.identifier_type == identifier_type]
The CountObject class contains pagination details for responses. The Cicero database is a large one, with many more officials, districts, and election events than is practical to return in one ReST response. So, features are included to page and iterate through many results. The CountObject signposts this and lets you know where in the results this particular response is.
class CountObject(AbstractCiceroObject):
def __init__(self, count_dict):
_copy_keys(self.__dict__, count_dict,
('to', 'total'))
self.from_ = count_dict['from'] # get around "from" being reserve word
The GeocodingCandidate class is what the DistrictGeocodingCandidate and OfficialGeocodingCandidate classes inherit from, and defines base fields for describing a geocoded address candidate. It is erroneous for the default GeocodingCandidate class to appear "in the wild," instead of its inheriting classes DistrictGeocodingCandidate and OfficialGeocodingCandidate.
class GeocodingCandidate(AbstractCiceroObject):
def __init__(self, geocoding_candidate_dict):
_copy_keys(self.__dict__, geocoding_candidate_dict,
('match_addr', 'wkid', 'locator', 'score', 'locator_type',
'y', 'x', 'geoservice'))
self.count = CountObject(geocoding_candidate_dict['count'])
The DistrictGeocodingCandidate class defines base fields for describing a geocoded address candidate, and a .districts attribute containing the main list of DistrictObjects and district information. DistrictGeocodingCandidates are only returned in /legislative_district and /nonlegislative_district calls using geocoding.
Cicero documentation: + on geocoding + on the /district call
class DistrictGeocodingCandidate(GeocodingCandidate):
def __init__(self, district_geocoding_candidate_dict):
super(DistrictGeocodingCandidate, self).__init__(
district_geocoding_candidate_dict)
self.districts = [DistrictObject(d) for d in
district_geocoding_candidate_dict['districts']]
The OfficialGeocodingCandidate class defines base fields for describing a geocoded address candidate, and a .officials attribute containing the main list of OfficialObjects and official information. OfficialGeocodingCandidates are only returned in /official calls using geocoding.
Cicero documentation: + for geocoding + for /official call
class OfficialGeocodingCandidate(GeocodingCandidate):
def __init__(self, official_geocoding_candidate_dict):
super(OfficialGeocodingCandidate, self).__init__(
official_geocoding_candidate_dict)
self.officials = [OfficialObject(o) for o in
official_geocoding_candidate_dict['officials']]
convenience "simplified view" function
def who_are_the_officials(self):
officials_list = []
for official in self.officials:
officials_list.append([official.last_name,
official.office.chamber.name_formal,
official.office.district.district_type,
official.office.district.district_id,
official.party])
return officials_list
The ElectionEventGeocodingCandidate class defines base fields for describing a geocoded address candidate, and an .election_events attribute containing the main list of ElectionEventObjects. ElectionEventGeocodingCandidates are only returned in /election_event calls using geocoding.
Geocoding with election events is best described as an "alpha" feature of the Cicero API, currently (as of Oct 8 2013). Consider this in the context of election events and the /election_event endpoint as a whole being a "beta" feature - you have been warned!
However, unlike non-geocoding /election_event calls, which are "free" (ie, don't use up any API credits) due to their "beta" status, calls to the /election_event endpoint which do take advantage of location or geocoding parameters will be charged API credits accordingly. Geocoding an address or location through one of the Cicero API's geocoding providers is a direct cost for us, so while we are happy to provide free election events without geocoding as we improve the feature, we must charge credits for all calls to all endpoints that use geocoding.
When using geocoding, elections for the chambers of the address's country will appear, as well as elections for that state, city, etc. For example, searching for election_events while geocoding an address in the US State of Alaska, will also return all election_events in all other US States (like Idaho and Ohio, as examples) for US House and Senate, because Alaska is a part of those chambers, even though Alaska does not vote for other state's representatives and senators. On the other hand, election events in non-US countries like France, Great Britain, or Canada would not be returned with geocoding. Likewise, gubernatorial elections for other states than Alaska would not be returned, and if the geocoded address was in Fairbanks, then Anchorage municipal elections would not be returned.
Note these issues apply only to geocoding with election_events, not necessarily non-geocoded /election_event calls.
Cicero documentation: for geocoding for election events
class ElectionEventGeocodingCandidate(GeocodingCandidate):
def __init__(self, election_event_geocoding_candidate_dict):
super(ElectionEventGeocodingCandidate, self).__init__(
election_event_geocoding_candidate_dict)
self.election_events = [
ElectionEventObject(e) for e in
election_event_geocoding_candidate_dict['election_events']]
The ElectionEventNonGeocodingResultsObject class is a container for a list of returned ElectionEventObjects and a CountObject enumerating how many and which have been returned in this response. ElectionEventNonGeocodingResultsObjects are only returned in /election_event calls that do NOT use geocoding.
Please review the issues outlined in the docstring of the ElectionEventGeocodingCandidate class. Non-geocoding /election_event calls are somewhat more robust than geocoding calls, but are still a "beta" feature. Unlike /election_event call that do use geocoding, however, non-geocoding calls will not charge an API credit for the time being. (as of Oct 9 2013)
class ElectionEventNonGeocodingResultsObject(AbstractCiceroObject):
def __init__(self, election_event_nongeocoding_results_dict):
if 'count' in election_event_nongeocoding_results_dict:
self.count = CountObject(
election_event_nongeocoding_results_dict['count'])
self.election_events = [
ElectionEventObject(e) for e in
election_event_nongeocoding_results_dict['election_events']]
The s class is a container for a list of returned DistrictObjects and a CountObject enumerating how many and which have been returned in this response. DistrictNonGeocodingResultsObject objects are only returned in /legislative_district or /nonlegislative_district calls that do NOT use geocoding.
class DistrictNonGeocodingResultsObject(AbstractCiceroObject):
def __init__(self, district_nongeocoding_results_dict):
if 'count' in district_nongeocoding_results_dict:
self.count = CountObject(district_nongeocoding_results_dict['count'])
self.districts = [
DistrictObject(d) for d in
district_nongeocoding_results_dict['districts']]
The OfficialNonGeocodingResultsObject class is a container for a list of returned OfficialObjects and a CountObject enumerating how many and which have been returned in this response. OfficialNonGeocodingResultsObject objects are only returned in /official calls that do NOT use geocoding.
class OfficialNonGeocodingResultsObject(AbstractCiceroObject):
def __init__(self, official_nongeocoding_results_dict):
if 'count' in official_nongeocoding_results_dict:
self.count = CountObject(official_nongeocoding_results_dict['count'])
self.officials = [OfficialObject(o) for o in
official_nongeocoding_results_dict['officials']]
convenience "simplified view" function
def who_are_the_officials(self):
officials_list = []
for official in self.officials:
officials_list.append([official.last_name,
official.office.chamber.name_formal,
official.office.district.district_type,
official.party])
return officials_list
The GeocodingResultsObject class defines a "geocoding candidates" list of either OfficialGeocodingCandidate, DistrictGeocodingCandidate, or ElectionEventGeocodingCandidate objects, depending on the type of response.
class GeocodingResultsObject(AbstractCiceroObject):
def __init__(self, geocoding_results_dict):
self.candidates = []
for candidate in geocoding_results_dict['candidates']:
if 'officials' in candidate:
self.candidates.append(OfficialGeocodingCandidate(candidate))
elif 'districts' in candidate:
self.candidates.append(DistrictGeocodingCandidate(candidate))
elif 'election_events' in candidate:
self.candidates.append(ElectionEventGeocodingCandidate(candidate))
else:
we shouldn't normally get to this case but if needed, this is the base candidate
self.candidates.append(GeocodingCandidate(candidate))
Always nested within a MapObject, the ExtentObject class represents the details necessary to construct the geographic bounding box containing the returned map. By default these coordinates are in the Web Mercator coordinate system (EPSG:900913).
class ExtentObject(AbstractCiceroObject):
def __init__(self, extent_dict):
_copy_keys(self.__dict__, extent_dict,
('x_min', 'y_min', 'x_max', 'y_max', 'srid'))
The MapObject class contains attributes to construct or access a returned map from the /map call.
class MapObject(AbstractCiceroObject):
def __init__(self, map_dict):
self.url = map_dict['url']
if 'img_src' in map_dict:
self.img_src = map_dict['img_src']
self.extent = ExtentObject(map_dict['extent'])
The MapResultsObject class is a container for a list of MapObjects.
(Cicero documentation)[https://cicero.azavea.com/docs/map.html]
class MapsResultsObject(AbstractCiceroObject):
def __init__(self, map_result_dict):
self.maps = [MapObject(m) for m in
map_result_dict['maps']]
The DistrictTypeObject is returned from the /district_type call, and contains information about each district_type available in Cicero.
(Cicero documentation)[https://cicero.azavea.com/docs/district_type.html]
class DistrictTypeObject(AbstractCiceroObject):
def __init__(self, district_type_dict):
_copy_keys(self.__dict__, district_type_dict,
('name_short', 'notes', 'acknowledgements',
'is_legislative', 'name_long'))
The DistrictTypeResultsObject is a container for a list of DistrictTypeObjects.
class DistrictTypeResultsObject(AbstractCiceroObject):
def __init__(self, district_type_result_dict):
self.district_types = [DistrictTypeObject(dt) for dt in
district_type_result_dict['district_types']]
The CreditBatchObject is returned from the /account/credits_remaining call, and contains information about each batch of credits a user has.
class CreditBatchObject(AbstractCiceroObject):
def __init__(self, credit_batch_dict):
_copy_keys(self.__dict__, credit_batch_dict,
('discount', 'expiration_time', 'cost', 'credits_remaining',
'credits_purchased'))
The AccountCreditsRemainingResultsObject is returned from the /account/credits_remaining call, and contains information about a user's balance and overdraft limit, as well as a list of available credit batches.
(Cicero documentation)[https://cicero.azavea.com/docs/credits_remaining.html]
class AccountCreditsRemainingResultsObject(AbstractCiceroObject):
def __init__(self, credits_remaining_result_dict):
_copy_keys(self.__dict__, credits_remaining_result_dict,
('credit_balance', 'overdraft_limit'))
self.usable_batches = [CreditBatchObject(batch) for batch in
credits_remaining_result_dict['usable_batches']]
The ActivityTypeObject is returned from the /account/usage call, and contains information about how a user has been using their API credits.
class ActivityTypeObject(AbstractCiceroObject):
def __init__(self, activity_dict):
_copy_keys(self.__dict__, activity_dict,
('count', 'type', 'credits_used'))
The AccountUsageObject is returned from the /account/usage call, and contains information about how many API calls and credits a user has made in a given month.
(Cicero documentation)[https://cicero.azavea.com/docs/usage.html]
class AccountUsageObject(AbstractCiceroObject):
def __init__(self, monthly_account_usage_dict):
_copy_keys(self.__dict__, monthly_account_usage_dict,
('count', 'credits_used', 'year', 'month'))
self.activity_types = [ActivityTypeObject(a) for a in
monthly_account_usage_dict['activity_types']]
The VersionObject is returned from the /version call, and contains the version number of the Cicero API currently being used.
class VersionObject(AbstractCiceroObject):
def __init__(self, version_dict):
self.version = version_dict['version']
The base ResponseObject is returned from every API call, and contains errors, messages, and the results of the API call.
class ResponseObject(AbstractCiceroObject):
def __init__(self, response_dict):
self.errors = response_dict['errors']
self.messages = response_dict['messages']
r = response_dict['results']
if 'candidates' in r:
self.results = GeocodingResultsObject(r)
elif 'officials' in r:
self.results = OfficialNonGeocodingResultsObject(r)
elif 'districts' in r:
self.results = DistrictNonGeocodingResultsObject(r)
elif 'election_events' in r:
self.results = ElectionEventNonGeocodingResultsObject(r)
elif 'maps' in r:
self.results = MapsResultsObject(r)
elif 'district_types' in r:
self.results = DistrictTypeResultsObject(r)
elif 'credit_balance' in r:
self.results = AccountCreditsRemainingResultsObject(r)
elif 'version' in r:
self.results = VersionObject(r)
elif isinstance(r, list) and 'activity_types' in r[0]:
self.results = [AccountUsageObject(month) for month in r]
the base case, though in normal operation we shouldn't get here
else:
self.results = r
The base RootCiceroObject is returned from every API call, and contains the ResponseObject.
class RootCiceroObject(AbstractCiceroObject):
def __init__(self, python_dict_from_cicero_json):
self.response = ResponseObject(python_dict_from_cicero_json['response'])