├── everypolitician ├── __init__.py └── lib.py ├── .gitignore ├── tox.ini ├── requirements.txt ├── .travis.yml ├── test-data ├── example-period.csv └── example-countries.json ├── setup.py ├── README.rst └── test_basics.py /everypolitician/__init__.py: -------------------------------------------------------------------------------- 1 | from .lib import * 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.tox 2 | /.cache 3 | /.__pycache__ 4 | *~ 5 | *.pyc 6 | /.coverage 7 | /htmlcov 8 | /everypolitician.egg-info 9 | /dist 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py35 3 | [testenv] 4 | deps = 5 | pytest 6 | mock 7 | pytest-cov 8 | commands = py.test --cov=everypolitician --cov-append 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | everypolitician-popolo==0.0.11 2 | funcsigs==1.0.2 3 | mock==2.0.0 4 | pbr==1.10.0 5 | pkg-resources==0.0.0 6 | py==1.4.31 7 | pytest==3.0.0 8 | requests==2.11.1 9 | six==1.10.0 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | matrix: 4 | include: 5 | - python: 3.5 6 | env: TOXENV=py35 7 | - python: 2.7 8 | env: TOXENV=py27 9 | 10 | sudo: false 11 | 12 | install: 13 | - pip install tox python-coveralls 14 | 15 | script: 16 | - tox 17 | - coverage html 18 | 19 | after_success: 20 | - coveralls 21 | -------------------------------------------------------------------------------- /test-data/example-period.csv: -------------------------------------------------------------------------------- 1 | id,name,sort_name,email,twitter,facebook,group,group_id,area_id,area,chamber,term,start_date,end_date,image,gender 2 | b882751f-4014-4f6f-b3cf-e0a5d6d3c605,ADELA ROSA SEGARRA,"SEGARRA, ADELA ROSA",asegarra@diputados.gob.ar,,,FRENTE PARA LA VICTORIA - PJ,frente_para_la_victoria_-_pj,area/buenos_aires,BUENOS AIRES,Cámara de Diputados,133,,2015-12-09,http://www4.hcdn.gob.ar/fotos/asegarra.jpg,female 3 | 8efb1e0e-8454-4c6b-9f87-0d4fef875fd2,ADRIAN PEREZ,"PEREZ, ADRIAN",aperez@diputados.gob.ar,adrianperezARG,,FRENTE RENOVADOR,frente_renovador,area/buenos_aires,BUENOS AIRES,Cámara de Diputados,133,,,http://www4.hcdn.gob.ar/fotos/aperez.jpg,male 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os.path import join, dirname 2 | from setuptools import setup, find_packages 3 | 4 | with open(join(dirname(__file__), 'README.rst')) as f: 5 | readme_text = f.read() 6 | 7 | setup( 8 | name = "everypolitician", 9 | version = "0.0.13", 10 | packages = find_packages(), 11 | author = "Mark Longair", 12 | author_email = "mark@mysociety.org", 13 | description = "Navigate countries and legislatures from EveryPolitician", 14 | long_description = readme_text, 15 | license = "AGPL", 16 | keywords = "politics data civic-tech", 17 | install_requires = [ 18 | 'requests', 19 | 'six >= 1.9.0', 20 | 'everypolitician-popolo >= 0.0.11', 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | EveryPolitician 2 | =============== 3 | 4 | A Python library for easy access to EveryPolitician data. This is 5 | essentially a Python port of 6 | `everypolitican-ruby `__. 7 | 8 | This has been tested with Python 2.7 and Python 3.5. 9 | 10 | Installation 11 | ============ 12 | 13 | You can install this package from PyPi with: 14 | 15 | .. code:: bash 16 | 17 | pip install everypolitician 18 | 19 | Usage 20 | ===== 21 | 22 | Creating a instance of the ``EveryPolitican`` class allows you to access 23 | information on countries, their legislatures and legislative periods. 24 | Each country and legislature has a slug that can be used to reference 25 | them via the ``country`` and ``legislature`` methods: 26 | 27 | .. code:: python 28 | 29 | from everypolitician import EveryPolitician 30 | ep = EveryPolitician() 31 | 32 | australia = ep.country('Australia') 33 | senate = australia.legislature('Senate') 34 | senate # => 35 | 36 | united_kingdom = ep.country('UK') 37 | house_of_commons = united_kingdom.legislature('Commons') 38 | 39 | american_samoa = ep.country('American-Samoa') 40 | house_of_representatives = american_samoa.legislature('House') 41 | 42 | for country in ep.countries(): 43 | print country.name, 'has', len(country.legislatures()), 'legislatures' 44 | 45 | By default this will get the EveryPolitician data and returns the most 46 | recent data. This data is found from the index file, called 47 | ``countries.json``, which links to specific versions of other data 48 | files. 49 | 50 | If you want want to point to a different ``countries.json`` file, you 51 | can override the default URL by specifying the ``countries_json_url`` 52 | keyword argument when creating the ``EveryPolitician`` object, e.g.: 53 | 54 | .. code:: python 55 | 56 | EveryPolitician(countries_json_url='https://cdn.rawgit.com/everypolitician/everypolitician-data/080cb46/countries.json') 57 | 58 | The example above is using a specific commit (indicated by the hash 59 | ``080cb46``). If you want to use a local copy of ``countries.json`` you 60 | can create the object with the ``countries_json_filename`` keyword 61 | argument instead, e.g.: 62 | 63 | .. code:: python 64 | 65 | EveryPolitician(countries_json_filename='/home/mark/tmp/countries.json') 66 | 67 | For more about ``countries.json``, see `this 68 | description `__. 69 | 70 | Remember that EveryPolitician data is frequently updated — see this 71 | information about `using EveryPolitician 72 | data `__. 73 | 74 | More information on `the EveryPolitician 75 | site `__. 76 | 77 | Development 78 | ----------- 79 | 80 | After cloning the repo, you can run the tests on Python 2.7 and Python 81 | 3.5 by running: 82 | 83 | .. code:: bash 84 | 85 | tox 86 | 87 | Or you can create a virtualenv and install the package's dependencies 88 | with: 89 | 90 | .. code:: bash 91 | 92 | pip install -e . 93 | 94 | And run the tests on the Python version your virtualenv was based on 95 | with: 96 | 97 | pytest 98 | 99 | Contributing 100 | ------------ 101 | 102 | Bug reports and pull requests are welcome on GitHub at 103 | https://github.com/everypolitician/everypolitician. 104 | 105 | License 106 | ------- 107 | 108 | The gem is available as open source under the terms of the `MIT 109 | License `__. 110 | -------------------------------------------------------------------------------- /everypolitician/lib.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import csv 4 | from datetime import datetime 5 | import io 6 | import json 7 | 8 | import requests 9 | import six 10 | 11 | from popolo_data.importer import Popolo 12 | 13 | 14 | DEFAULT_COUNTRIES_JSON_URL = \ 15 | 'https://raw.githubusercontent.com/everypolitician/' \ 16 | 'everypolitician-data/master/countries.json' 17 | 18 | 19 | class NotFound(Exception): 20 | pass 21 | 22 | 23 | @six.python_2_unicode_compatible 24 | class EveryPolitician(object): 25 | """A class to load, parses and make accessible the EP countries.json file""" 26 | 27 | def __init__(self, countries_json_url=None, countries_json_filename=None): 28 | """Initialize from either a remote or local countries.json file""" 29 | self.countries_json_filename = None 30 | self.countries_json_url = None 31 | self._countries_json_data = None 32 | if countries_json_filename is None: 33 | # Then get the data from a URL: 34 | if countries_json_url is None: 35 | countries_json_url = DEFAULT_COUNTRIES_JSON_URL 36 | self.countries_json_url = countries_json_url 37 | else: 38 | # Otherwise, use the local file: 39 | self.countries_json_filename = countries_json_filename 40 | 41 | def countries_json_data(self): 42 | """Return countries JSON data parsed into Python data structures""" 43 | if self._countries_json_data is not None: 44 | return self._countries_json_data 45 | if self.countries_json_filename is not None: 46 | with open(self.countries_json_filename) as f: 47 | self._countries_json_data = json.load(f) 48 | else: 49 | r = requests.get(self.countries_json_url) 50 | r.raise_for_status() 51 | self._countries_json_data = r.json() 52 | return self._countries_json_data 53 | 54 | def countries(self): 55 | """Return a list of all known countries""" 56 | return [ 57 | Country(country_data) for country_data 58 | in self.countries_json_data() 59 | ] 60 | 61 | def country(self, country_slug): 62 | """Return an Country object from a country slug""" 63 | for c in self.countries(): 64 | if c.slug == country_slug: 65 | return c 66 | raise NotFound("Couldn't find the country with slug '{0}'".format( 67 | country_slug)) 68 | 69 | def country_legislature(self, country_slug, legislature_slug): 70 | """Return a tuple of Country and Legislature objects from their slugs""" 71 | country = self.country(country_slug) 72 | legislature = country.legislature(legislature_slug) 73 | return country, legislature 74 | 75 | def __repr__(self): 76 | if self.countries_json_filename is None: 77 | if self.countries_json_url == DEFAULT_COUNTRIES_JSON_URL: 78 | return str('EveryPolitician()') 79 | fmt = str('EveryPolitician(countries_json_url="{}")') 80 | return fmt.format(self.countries_json_url) 81 | else: 82 | fmt = str('EveryPolitician(countries_json_filename="{}")') 83 | return fmt.format(self.countries_json_filename) 84 | 85 | def __str__(self): 86 | return ''.format( 87 | self.countries_json_url or self.countries_json_filename) 88 | 89 | 90 | @six.python_2_unicode_compatible 91 | class Country(object): 92 | """A class that represents a country from the countries.json file""" 93 | 94 | def __init__(self, country_data): 95 | for k in ('name', 'code', 'slug'): 96 | setattr(self, k, country_data[k]) 97 | self.country_data = country_data 98 | 99 | def legislatures(self): 100 | """Return all the legislatures known for this country 101 | 102 | A legislature is a chamber of a parliament, e.g. the House of 103 | Commons in the UK.""" 104 | return [ 105 | Legislature(legislature_data, self) for legislature_data 106 | in self.country_data['legislatures'] 107 | ] 108 | 109 | def legislature(self, legislature_slug): 110 | """Return a legislature in this country from its slug""" 111 | for l in self.legislatures(): 112 | if l.slug == legislature_slug: 113 | return l 114 | raise NotFound("Couldn't find the legislature with slug '{0}'".format( 115 | legislature_slug)) 116 | 117 | def houses(self, type_of_house): 118 | return [ 119 | l for l in self.legislatures() 120 | if l.type in (type_of_house, 'unicameral legislature')] 121 | 122 | def house_most_recent(self, type_of_house): 123 | houses = self.houses(type_of_house) 124 | if not houses: 125 | raise NotFound("No house of type {0}".format(type_of_house)) 126 | if len(houses) == 1: 127 | return houses[0] 128 | return max( 129 | houses, 130 | key=lambda h: h.latest_legislative_period().start_date) 131 | 132 | def lower_house(self): 133 | """A shortcut method to return the most recently active lower house""" 134 | return self.house_most_recent('lower house') 135 | 136 | def upper_house(self): 137 | """A shortcut method to return the most recently active lower house""" 138 | return self.house_most_recent('upper house') 139 | 140 | def __repr__(self): 141 | fmt = str('') 142 | if six.PY2: 143 | return fmt.format(self.name.encode('utf-8')) 144 | return fmt.format(self.name) 145 | 146 | def __str__(self): 147 | return ''.format(self.name) 148 | 149 | 150 | @six.python_2_unicode_compatible 151 | class Legislature(object): 152 | """A class that represents a legislature of a country""" 153 | 154 | def __init__(self, legislature_data, country): 155 | for k in ('name', 'slug', 'person_count', 'sha', 'statement_count', 156 | 'popolo_url', 'type'): 157 | setattr(self, k, legislature_data[k]) 158 | self.lastmod = datetime.utcfromtimestamp( 159 | float(legislature_data['lastmod'])) 160 | self.legislature_data = legislature_data 161 | self.country = country 162 | self.cached_popolo = None 163 | 164 | def popolo(self): 165 | if self.cached_popolo is None: 166 | self.cached_popolo = Popolo.from_url(self.popolo_url) 167 | return self.cached_popolo 168 | 169 | def directory(self): 170 | """Return the directory path in the everypolitician-data repository""" 171 | split_path = self.legislature_data['sources_directory'].split('/') 172 | return '/'.join(split_path[1:3]) 173 | 174 | def legislative_periods(self): 175 | """Return all the known legislative periods for this legislature""" 176 | return [ 177 | LegislativePeriod(lp_data, self, self.country) 178 | for lp_data in self.legislature_data['legislative_periods'] 179 | ] 180 | 181 | def latest_legislative_period(self): 182 | """Return the most recent legislative period for this legislature""" 183 | return max( 184 | self.legislative_periods(), 185 | key=lambda lp: lp.start_date) 186 | 187 | def __repr__(self): 188 | fmt = str('') 189 | if six.PY2: 190 | return fmt.format( 191 | self.name.encode('utf-8'), self.country.name.encode('utf-8')) 192 | return fmt.format(self.name, self.country.name) 193 | 194 | def __str__(self): 195 | return '' \ 196 | .format(self.name, self.country.name) 197 | 198 | 199 | def unicode_dict(d): 200 | """Return a new dict where all the text has been decoded to unicode 201 | 202 | This is only for Python 2.""" 203 | return { k.decode('utf-8'): v.decode('utf-8') for k, v in d.items() } 204 | 205 | 206 | class LegislativePeriod(object): 207 | 208 | def __init__(self, legislative_period_data, legislature, country): 209 | for k in ('id', 'name', 'slug'): 210 | setattr(self, k, legislative_period_data[k]) 211 | self.legislature = legislature 212 | self.country = country 213 | self.legislative_period_data = legislative_period_data 214 | 215 | @property 216 | def start_date(self): 217 | """Return the start date of the legislative period 218 | 219 | If this is unknown, it returns None.""" 220 | return self.legislative_period_data.get('start_date') 221 | 222 | @property 223 | def end_date(self): 224 | """Return the end date of the legislative period 225 | 226 | If this is unknown, it returns None.""" 227 | return self.legislative_period_data.get('end_date') 228 | 229 | @property 230 | def csv_url(self): 231 | """Return the URL to CSV of members during this legislative period""" 232 | return 'https://raw.githubusercontent.com/everypolitician' \ 233 | '/everypolitician-data/{0}/{1}'.format( 234 | self.legislature.sha, 235 | self.legislative_period_data['csv'] 236 | ) 237 | 238 | def csv(self): 239 | """Return parsed data from the CSV of members during the period 240 | 241 | This returns a list of one dict per row of the CSV file, where 242 | the keys are the column headers.""" 243 | r = requests.get(self.csv_url) 244 | r.raise_for_status() 245 | if six.PY2: 246 | f = io.BytesIO(r.text.encode('utf-8')) 247 | reader = csv.DictReader(f) 248 | return [ 249 | unicode_dict(d) for d in reader 250 | ] 251 | else: 252 | f = io.StringIO(r.text) 253 | reader = csv.DictReader(f) 254 | return [d for d in reader] 255 | -------------------------------------------------------------------------------- /test-data/example-countries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Åland", 4 | "country": "Åland", 5 | "code": "AX", 6 | "slug": "Aland", 7 | "legislatures": [ 8 | { 9 | "name": "Lagting", 10 | "slug": "Lagting", 11 | "sources_directory": "data/Aland/Lagting/sources", 12 | "popolo": "data/Aland/Lagting/ep-popolo-v1.0.json", 13 | "popolo_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/00212a86d5e0c3bbfc22289fd7c714d17c6ced85/data/Aland/Lagting/ep-popolo-v1.0.json", 14 | "names": "data/Aland/Lagting/names.csv", 15 | "lastmod": "1477620836", 16 | "person_count": 60, 17 | "sha": "00212a86d5e0c3bbfc22289fd7c714d17c6ced85", 18 | "legislative_periods": [ 19 | { 20 | "end_date": "2019-10-31", 21 | "id": "term/2015", 22 | "name": "2015–2019", 23 | "start_date": "2015-11-02", 24 | "slug": "2015", 25 | "csv": "data/Aland/Lagting/term-2015.csv", 26 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/421c2375c000ba8b869b679151042e24dbaad760/data/Aland/Lagting/term-2015.csv" 27 | }, 28 | { 29 | "end_date": "2015", 30 | "id": "term/2011", 31 | "name": "2011–2015", 32 | "start_date": "2011", 33 | "slug": "2011", 34 | "csv": "data/Aland/Lagting/term-2011.csv", 35 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/2a9e6f9ae533bf8eb6109a1e6c5451ce9f539cc2/data/Aland/Lagting/term-2011.csv" 36 | }, 37 | { 38 | "end_date": "2011", 39 | "id": "term/2007", 40 | "name": "2007–2011", 41 | "start_date": "2007", 42 | "slug": "2007", 43 | "csv": "data/Aland/Lagting/term-2007.csv", 44 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/2a9e6f9ae533bf8eb6109a1e6c5451ce9f539cc2/data/Aland/Lagting/term-2007.csv" 45 | } 46 | ], 47 | "statement_count": 3305, 48 | "type": "unicameral legislature" 49 | } 50 | ] 51 | }, 52 | { 53 | "name": "Argentina", 54 | "country": "Argentina", 55 | "code": "AR", 56 | "slug": "Argentina", 57 | "legislatures": [ 58 | { 59 | "name": "Cámara de Diputados", 60 | "slug": "Diputados", 61 | "sources_directory": "data/Argentina/Diputados/sources", 62 | "popolo": "data/Argentina/Diputados/ep-popolo-v1.0.json", 63 | "popolo_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/d3afadff7d5a08e1745b7e48782a869ec4979e78/data/Argentina/Diputados/ep-popolo-v1.0.json", 64 | "names": "data/Argentina/Diputados/names.csv", 65 | "lastmod": "1477635781", 66 | "person_count": 390, 67 | "sha": "d3afadff7d5a08e1745b7e48782a869ec4979e78", 68 | "legislative_periods": [ 69 | { 70 | "id": "term/133", 71 | "name": "133º", 72 | "start_date": "2015", 73 | "slug": "133", 74 | "csv": "data/Argentina/Diputados/term-133.csv", 75 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/d3afadff7d5a08e1745b7e48782a869ec4979e78/data/Argentina/Diputados/term-133.csv" 76 | } 77 | ], 78 | "statement_count": 14952, 79 | "type": "lower house" 80 | }, 81 | { 82 | "name": "Cámara de Senadores", 83 | "slug": "Senado", 84 | "sources_directory": "data/Argentina/Senado/sources", 85 | "popolo": "data/Argentina/Senado/ep-popolo-v1.0.json", 86 | "popolo_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/c323b935f2dce83fcdfcbb5c2f94614a25207d98/data/Argentina/Senado/ep-popolo-v1.0.json", 87 | "names": "data/Argentina/Senado/names.csv", 88 | "lastmod": "1477314249", 89 | "person_count": 72, 90 | "sha": "c323b935f2dce83fcdfcbb5c2f94614a25207d98", 91 | "legislative_periods": [ 92 | { 93 | "end_date": "2017-12-09", 94 | "id": "term/2015", 95 | "name": "2015–", 96 | "start_date": "2015-12-10", 97 | "slug": "2015", 98 | "csv": "data/Argentina/Senado/term-2015.csv", 99 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/c323b935f2dce83fcdfcbb5c2f94614a25207d98/data/Argentina/Senado/term-2015.csv" 100 | } 101 | ], 102 | "statement_count": 4024, 103 | "type": "upper house" 104 | } 105 | ] 106 | }, 107 | { 108 | "name": "British Virgin Islands", 109 | "country": "British Virgin Islands", 110 | "code": "VG", 111 | "slug": "British-Virgin-Islands", 112 | "legislatures": [ 113 | { 114 | "name": "House of Assembly", 115 | "slug": "Assembly", 116 | "sources_directory": "data/British_Virgin_Islands/Assembly/sources", 117 | "popolo": "data/British_Virgin_Islands/Assembly/ep-popolo-v1.0.json", 118 | "popolo_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/e319dc099d97111b978ae25c9c547b30cf723294/data/British_Virgin_Islands/Assembly/ep-popolo-v1.0.json", 119 | "names": "data/British_Virgin_Islands/Assembly/names.csv", 120 | "lastmod": "1477214837", 121 | "person_count": 23, 122 | "sha": "e319dc099d97111b978ae25c9c547b30cf723294", 123 | "legislative_periods": [ 124 | { 125 | "id": "term/2015", 126 | "name": "3rd Assembly", 127 | "start_date": "2015-06-08", 128 | "slug": "2015", 129 | "csv": "data/British_Virgin_Islands/Assembly/term-2015.csv", 130 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/76647a7d8f88b0fe0ae294cb36459d3b5468e30f/data/British_Virgin_Islands/Assembly/term-2015.csv" 131 | }, 132 | { 133 | "end_date": "2015", 134 | "id": "term/2011", 135 | "name": "2nd Assembly", 136 | "start_date": "2011", 137 | "slug": "2011", 138 | "csv": "data/British_Virgin_Islands/Assembly/term-2011.csv", 139 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/61e40dda7a1796b65bd1a072e683adcf6ead6a47/data/British_Virgin_Islands/Assembly/term-2011.csv" 140 | }, 141 | { 142 | "end_date": "2011", 143 | "id": "term/2007", 144 | "name": "1st Assembly", 145 | "start_date": "2007", 146 | "slug": "2007", 147 | "csv": "data/British_Virgin_Islands/Assembly/term-2007.csv", 148 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/61e40dda7a1796b65bd1a072e683adcf6ead6a47/data/British_Virgin_Islands/Assembly/term-2007.csv" 149 | } 150 | ], 151 | "statement_count": 1178, 152 | "type": "unicameral legislature" 153 | }, 154 | { 155 | "name": "Legislative Council", 156 | "slug": "Council", 157 | "sources_directory": "data/British_Virgin_Islands/Council/sources", 158 | "popolo": "data/British_Virgin_Islands/Council/ep-popolo-v1.0.json", 159 | "popolo_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/0d6b4b49128cd3a8fd5427d83ede8e867035aede/data/British_Virgin_Islands/Council/ep-popolo-v1.0.json", 160 | "names": "data/British_Virgin_Islands/Council/names.csv", 161 | "lastmod": "1477214906", 162 | "person_count": 36, 163 | "sha": "0d6b4b49128cd3a8fd5427d83ede8e867035aede", 164 | "legislative_periods": [ 165 | { 166 | "end_date": "2007-08-20", 167 | "id": "term/2003", 168 | "name": "15th Council", 169 | "start_date": "2003-06-16", 170 | "slug": "2003", 171 | "csv": "data/British_Virgin_Islands/Council/term-2003.csv", 172 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/44dcc4ac9c1e095b1002cce6c7d6328385da689e/data/British_Virgin_Islands/Council/term-2003.csv" 173 | }, 174 | { 175 | "end_date": "2003", 176 | "id": "term/1999", 177 | "name": "14th Council", 178 | "start_date": "1999", 179 | "slug": "1999", 180 | "csv": "data/British_Virgin_Islands/Council/term-1999.csv", 181 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/44dcc4ac9c1e095b1002cce6c7d6328385da689e/data/British_Virgin_Islands/Council/term-1999.csv" 182 | }, 183 | { 184 | "end_date": "1999", 185 | "id": "term/1995", 186 | "name": "13th Council", 187 | "start_date": "1995", 188 | "slug": "1995", 189 | "csv": "data/British_Virgin_Islands/Council/term-1995.csv", 190 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/bcc28cf1f81836150f1ba11c4240ba396d158902/data/British_Virgin_Islands/Council/term-1995.csv" 191 | }, 192 | { 193 | "end_date": "1995", 194 | "id": "term/1990", 195 | "name": "12th Council", 196 | "start_date": "1990", 197 | "slug": "1990", 198 | "csv": "data/British_Virgin_Islands/Council/term-1990.csv", 199 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/44dcc4ac9c1e095b1002cce6c7d6328385da689e/data/British_Virgin_Islands/Council/term-1990.csv" 200 | }, 201 | { 202 | "end_date": "1990", 203 | "id": "term/1986", 204 | "name": "11th Council", 205 | "start_date": "1986", 206 | "slug": "1986", 207 | "csv": "data/British_Virgin_Islands/Council/term-1986.csv", 208 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/bcc28cf1f81836150f1ba11c4240ba396d158902/data/British_Virgin_Islands/Council/term-1986.csv" 209 | }, 210 | { 211 | "end_date": "1986", 212 | "id": "term/1983", 213 | "name": "10th Council", 214 | "start_date": "1983", 215 | "slug": "1983", 216 | "csv": "data/British_Virgin_Islands/Council/term-1983.csv", 217 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/bcc28cf1f81836150f1ba11c4240ba396d158902/data/British_Virgin_Islands/Council/term-1983.csv" 218 | }, 219 | { 220 | "end_date": "1983", 221 | "id": "term/1979", 222 | "name": "9th Council", 223 | "start_date": "1979", 224 | "slug": "1979", 225 | "csv": "data/British_Virgin_Islands/Council/term-1979.csv", 226 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/44dcc4ac9c1e095b1002cce6c7d6328385da689e/data/British_Virgin_Islands/Council/term-1979.csv" 227 | }, 228 | { 229 | "end_date": "1979", 230 | "id": "term/1975", 231 | "name": "8th Council", 232 | "start_date": "1975", 233 | "slug": "1975", 234 | "csv": "data/British_Virgin_Islands/Council/term-1975.csv", 235 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/44dcc4ac9c1e095b1002cce6c7d6328385da689e/data/British_Virgin_Islands/Council/term-1975.csv" 236 | }, 237 | { 238 | "end_date": "1975", 239 | "id": "term/1971", 240 | "name": "7th Council", 241 | "start_date": "1971", 242 | "slug": "1971", 243 | "csv": "data/British_Virgin_Islands/Council/term-1971.csv", 244 | "csv_url": "https://cdn.rawgit.com/everypolitician/everypolitician-data/bcc28cf1f81836150f1ba11c4240ba396d158902/data/British_Virgin_Islands/Council/term-1971.csv" 245 | } 246 | ], 247 | "statement_count": 2060, 248 | "type": "unicameral legislature" 249 | } 250 | ] 251 | } 252 | ] 253 | -------------------------------------------------------------------------------- /test_basics.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | import json 6 | from os.path import dirname, join 7 | import re 8 | from unittest import TestCase 9 | 10 | from mock import patch 11 | import pytest 12 | import six 13 | from six import text_type 14 | 15 | from everypolitician import EveryPolitician, NotFound 16 | from popolo_data.importer import Popolo 17 | 18 | 19 | class FakeResponse(object): 20 | 21 | def __init__(self, json_data, text, status_code): 22 | self.json_data = json_data 23 | self.text = text 24 | self.status_code = status_code 25 | 26 | def json(self): 27 | return self.json_data 28 | 29 | def text(self): 30 | return self.text 31 | 32 | def raise_for_status(self): 33 | pass 34 | 35 | 36 | URL_DATA = { 37 | 'https://raw.githubusercontent.com/everypolitician/everypolitician-data/master/countries.json': 38 | 'example-countries.json', 39 | 'https://raw.githubusercontent.com/everypolitician/everypolitician-data/d3afadff7d5a08e1745b7e48782a869ec4979e78/data/Argentina/Diputados/term-133.csv': 40 | 'example-period.csv' 41 | } 42 | 43 | 44 | def fake_requests_get(url): 45 | leafname = URL_DATA.get(url) 46 | if not leafname: 47 | raise Exception("The URL {0} hasn't been faked".format(url)) 48 | filename = join(dirname(__file__), 'test-data', leafname) 49 | if leafname.endswith('.json'): 50 | with open(filename) as f: 51 | return FakeResponse(json.load(f), None, 200) 52 | else: 53 | with open(filename, 'rb') as f: 54 | return FakeResponse(None, f.read().decode('utf-8'), 200) 55 | 56 | 57 | @patch('everypolitician.lib.requests.get', side_effect=fake_requests_get) 58 | class TestDataLoading(TestCase): 59 | 60 | def test_create_ep(self, patched_requests_get): 61 | ep = EveryPolitician() 62 | assert str(ep) == \ 63 | '' 64 | 65 | def test_ep_repr(self, patched_requests_get): 66 | ep = EveryPolitician() 67 | assert repr(ep) == 'EveryPolitician()' 68 | 69 | def test_ep_repr_custom_url(self, patched_requests_get): 70 | ep = EveryPolitician(countries_json_url='foobar') 71 | assert repr(ep) == 'EveryPolitician(countries_json_url="foobar")' 72 | 73 | def test_ep_from_local_file(self, patched_requests_get): 74 | filename = join( 75 | dirname(__file__), 'test-data', 'example-countries.json') 76 | ep = EveryPolitician(countries_json_filename=filename) 77 | assert re.search( 78 | r'EveryPolitician\(countries_json_filename=".*example-countries.json"\)', 79 | repr(ep)) 80 | 81 | def test_countries_from_local_file(self, patched_requests_get): 82 | filename = join( 83 | dirname(__file__), 'test-data', 'example-countries.json') 84 | ep = EveryPolitician(countries_json_filename=filename) 85 | countries = ep.countries() 86 | assert len(countries) == 3 87 | 88 | def test_countries(self, patched_requests_get): 89 | ep = EveryPolitician() 90 | countries = ep.countries() 91 | assert len(countries) == 3 92 | assert text_type(countries[0]) == '' 93 | assert text_type(countries[1]) == '' 94 | assert text_type(countries[2]) == '' 95 | 96 | def test_json_only_fetched_once(self, patched_requests_get): 97 | ep = EveryPolitician() 98 | ep.countries() 99 | ep.countries() 100 | assert patched_requests_get.call_count == 1 101 | 102 | def test_get_a_single_country_bad_case(self, patched_requests_get): 103 | ep = EveryPolitician() 104 | with pytest.raises(NotFound): 105 | ep.country('argentina') 106 | 107 | def test_get_a_single_country(self, patched_requests_get): 108 | ep = EveryPolitician() 109 | country = ep.country('Argentina') 110 | assert country.name == 'Argentina' 111 | 112 | def test_get_a_country_and_legislature(self, patched_requests_get): 113 | ep = EveryPolitician() 114 | country, legislature = ep.country_legislature('Argentina', 'Diputados') 115 | assert country.name == 'Argentina' 116 | assert legislature.name == 'Cámara de Diputados' 117 | 118 | def test_get_a_country_legislature_c_not_found(self, patched_requests_get): 119 | ep = EveryPolitician() 120 | with pytest.raises(NotFound): 121 | ep.country_legislature('Argentina', 'FOO') 122 | 123 | def test_get_a_country_legislature_l_not_found(self, patched_requests_get): 124 | ep = EveryPolitician() 125 | with pytest.raises(NotFound): 126 | ep.country_legislature('FOO', 'Diputados') 127 | 128 | def test_get_a_country_legislature_neither_found(self, patched_requests_get): 129 | ep = EveryPolitician() 130 | with pytest.raises(NotFound): 131 | ep.country_legislature('FOO', 'FOO') 132 | 133 | 134 | class TestCountryMethods(TestCase): 135 | 136 | def setUp(self): 137 | with patch('everypolitician.lib.requests.get', side_effect=fake_requests_get): 138 | self.ep = EveryPolitician() 139 | self.country_aland = self.ep.country('Aland') 140 | self.country_argentina = self.ep.country('Argentina') 141 | self.country_bv = self.ep.country('British-Virgin-Islands') 142 | 143 | def test_country_repr(self): 144 | if six.PY2: 145 | assert repr(self.country_aland) == b'' 146 | else: 147 | assert repr(self.country_aland) == '' 148 | 149 | def test_get_legislatures(self): 150 | ls = self.country_aland.legislatures() 151 | assert len(ls) == 1 152 | 153 | def test_most_recent_house_no_house_matches(self): 154 | with pytest.raises(NotFound): 155 | print(self.country_argentina.house_most_recent('random_house')) 156 | 157 | def test_most_recent_house_where_multiple_match_lower_house(self): 158 | assert self.country_bv.house_most_recent('lower_house').name == 'House of Assembly' 159 | 160 | def test_lower_house_shortcut(self): 161 | assert self.country_argentina.lower_house().name == 'Cámara de Diputados' 162 | 163 | def test_upper_house_shortcut(self): 164 | assert self.country_argentina.upper_house().name == 'Cámara de Senadores' 165 | 166 | 167 | class TestCountryHousesMethod(TestCase): 168 | 169 | def test_finds_unicameral_legislature_for_lower_house(self): 170 | with patch('everypolitician.lib.requests.get', side_effect=fake_requests_get): 171 | ep = EveryPolitician() 172 | country = ep.country('Aland') 173 | houses = country.houses('lower house') 174 | assert len(houses) == 1 175 | assert houses[0].name == 'Lagting' 176 | 177 | def test_finds_lower_house_if_present(self): 178 | with patch('everypolitician.lib.requests.get', side_effect=fake_requests_get): 179 | ep = EveryPolitician() 180 | country = ep.country('Argentina') 181 | houses = country.houses('lower house') 182 | assert len(houses) == 1 183 | assert houses[0].name == 'Cámara de Diputados' 184 | 185 | def test_finds_upper_house_if_present(self): 186 | with patch('everypolitician.lib.requests.get', side_effect=fake_requests_get): 187 | ep = EveryPolitician() 188 | country = ep.country('Argentina') 189 | houses = country.houses('upper house') 190 | assert len(houses) == 1 191 | assert houses[0].name == 'Cámara de Senadores' 192 | 193 | def test_no_matches_for_unknown_house_type(self): 194 | with patch('everypolitician.lib.requests.get', side_effect=fake_requests_get): 195 | ep = EveryPolitician() 196 | country = ep.country('Argentina') 197 | houses = country.houses('quiet area') 198 | assert len(houses) == 0 199 | 200 | 201 | class TestLeglislatureMethods(TestCase): 202 | 203 | def setUp(self): 204 | with patch('everypolitician.lib.requests.get', side_effect=fake_requests_get): 205 | self.ep = EveryPolitician() 206 | self.country = self.ep.country('Argentina') 207 | self.legislatures = self.country.legislatures() 208 | 209 | def test_legislature_repr(self): 210 | if six.PY2: 211 | assert repr(self.legislatures[0]) == b'' 212 | else: 213 | assert repr(self.legislatures[1]) == '' 214 | 215 | def test_legislature_str(self): 216 | assert text_type(self.legislatures[1]) == '' 217 | 218 | def test_legislature_popolo_url(self): 219 | l = self.legislatures[1] 220 | assert l.popolo_url == 'https://cdn.rawgit.com/everypolitician/' \ 221 | 'everypolitician-data/c323b935f2dce83fcdfcbb5c2f94614a25207d98/' \ 222 | 'data/Argentina/Senado/ep-popolo-v1.0.json' 223 | 224 | def test_directory(self): 225 | l = self.legislatures[0] 226 | assert l.directory() == 'Argentina/Diputados' 227 | 228 | @patch('everypolitician.lib.Popolo') 229 | def test_popolo_call(self, mocked_popolo_class): 230 | mocked_popolo_class.from_url.return_value = Popolo({ 231 | 'persons': [ 232 | {'name': 'Joe Bloggs'} 233 | ] 234 | }) 235 | l = self.legislatures[0] 236 | popolo = l.popolo() 237 | mocked_popolo_class.from_url.assert_called_with( 238 | u'https://cdn.rawgit.com/everypolitician/everypolitician-data/' 239 | u'd3afadff7d5a08e1745b7e48782a869ec4979e78/data/Argentina/' 240 | u'Diputados/ep-popolo-v1.0.json') 241 | assert len(popolo.persons) == 1 242 | assert popolo.persons.first.name == 'Joe Bloggs' 243 | 244 | @patch('everypolitician.lib.Popolo') 245 | def test_popolo_data_only_fetched_once(self, mocked_popolo_class): 246 | mocked_popolo_class.from_url.return_value = Popolo({ 247 | 'persons': [ 248 | {'name': 'Joe Bloggs'} 249 | ] 250 | }) 251 | l = self.legislatures[0] 252 | l.popolo() 253 | l.popolo() 254 | mocked_popolo_class.from_url.assert_called_once_with( 255 | u'https://cdn.rawgit.com/everypolitician/everypolitician-data/' 256 | u'd3afadff7d5a08e1745b7e48782a869ec4979e78/data/Argentina/' 257 | u'Diputados/ep-popolo-v1.0.json') 258 | 259 | @patch('everypolitician.lib.requests.get', side_effect=fake_requests_get) 260 | class TestLegislativePeriod(TestCase): 261 | 262 | def setUp(self): 263 | with patch('everypolitician.lib.requests.get', side_effect=fake_requests_get): 264 | self.ep = EveryPolitician() 265 | self.country = self.ep.country('Argentina') 266 | self.legislature = self.country.legislature('Diputados') 267 | self.period = self.legislature.legislative_periods()[0] 268 | 269 | def test_start_date(self, patched_requests_get): 270 | assert self.period.start_date == "2015" 271 | 272 | def test_end_date(self, patched_requests_get): 273 | assert self.period.end_date is None 274 | 275 | def test_csv(self, patched_requests_get): 276 | assert self.period.csv() == \ 277 | [{u'area': u'BUENOS AIRES', 278 | u'area_id': u'area/buenos_aires', 279 | u'chamber': u'C\xe1mara de Diputados', 280 | u'email': u'asegarra@diputados.gob.ar', 281 | u'end_date': u'2015-12-09', 282 | u'facebook': u'', 283 | u'gender': u'female', 284 | u'group': u'FRENTE PARA LA VICTORIA - PJ', 285 | u'group_id': u'frente_para_la_victoria_-_pj', 286 | u'id': u'b882751f-4014-4f6f-b3cf-e0a5d6d3c605', 287 | u'image': u'http://www4.hcdn.gob.ar/fotos/asegarra.jpg', 288 | u'name': u'ADELA ROSA SEGARRA', 289 | u'sort_name': u'SEGARRA, ADELA ROSA', 290 | u'start_date': u'', 291 | u'term': u'133', 292 | u'twitter': u''}, 293 | {u'area': u'BUENOS AIRES', 294 | u'area_id': u'area/buenos_aires', 295 | u'chamber': u'C\xe1mara de Diputados', 296 | u'email': u'aperez@diputados.gob.ar', 297 | u'end_date': u'', 298 | u'facebook': u'', 299 | u'gender': u'male', 300 | u'group': u'FRENTE RENOVADOR', 301 | u'group_id': u'frente_renovador', 302 | u'id': u'8efb1e0e-8454-4c6b-9f87-0d4fef875fd2', 303 | u'image': u'http://www4.hcdn.gob.ar/fotos/aperez.jpg', 304 | u'name': u'ADRIAN PEREZ', 305 | u'sort_name': u'PEREZ, ADRIAN', 306 | u'start_date': u'', 307 | u'term': u'133', 308 | u'twitter': u'adrianperezARG'}] 309 | --------------------------------------------------------------------------------