Skip to content

Commit d37c99a

Browse files
committed
changing shit. and refactoring
1 parent dbd1251 commit d37c99a

8 files changed

+207
-35
lines changed

elex1/election_results.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
import urllib
1919
from operator import itemgetter
2020
from collections import defaultdict
21-
from os.path import abspath, dirname, join
21+
from os.path import dirname, join
2222

2323

2424
# Download CSV of fake Virginia election results to root of project
2525
url = "https://docs.google.com/spreadsheet/pub?key=0AhhC0IWaObRqdGFkUW1kUmp2ZlZjUjdTYV9lNFJ5RHc&output=csv"
26-
filename = join(dirname(dirname(abspath(__file__))), 'fake_va_elec_results.csv')
26+
filename = join(dirname(dirname(__file__)), 'fake_va_elec_results.csv')
2727
urllib.urlretrieve(url, filename)
2828

2929
# Create reader for ingesting CSV as array of dicts
@@ -40,10 +40,13 @@
4040
# Convert total votes to an integer
4141
row['votes'] = int(row['votes'])
4242

43-
# Store county-level results by office/district pair, then by candidate party and raw name
44-
race_key = (row['office'], row['district'])
43+
# Store county-level results by slugified office and district (if there is one),
44+
# then by candidate party and raw name
45+
race_key = row['office']
46+
if row['district']:
47+
race_key += "-%s" % row['district']
4548
# Create unique candidate key from party and name, in case multiple candidates have same
46-
cand_key = (row['party'], row['candidate'])
49+
cand_key = "-".join((row['party'], row['candidate']))
4750
# Below, setdefault initializes empty dict and list for the respective keys if they don't already exist.
4851
race = results[race_key]
4952
race.setdefault(cand_key, []).append(row)
@@ -94,7 +97,7 @@
9497

9598

9699
# Write CSV of results
97-
outfile = join(dirname(abspath(__file__)), 'summary_results.csv')
100+
outfile = join(dirname(__file__), 'summary_results.csv')
98101
with open(outfile, 'wb') as fh:
99102
# We'll limit the output to cleanly parsed, standardized values
100103
fieldnames = [

elex2/election_results.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
import urllib
1616
from operator import itemgetter
1717
from collections import defaultdict
18-
from os.path import abspath, dirname, join
18+
from os.path import dirname, join
1919

2020

2121
def main():
2222
# Download CSV of fake Virginia election results to root of project
23-
path = join(dirname(dirname(abspath(__file__))), 'fake_va_elec_results.csv')
23+
path = join(dirname(dirname(__file__)), 'fake_va_elec_results.csv')
2424
download_results(path)
2525
# Process data
2626
results = parse_and_clean(path)
@@ -60,10 +60,13 @@ def parse_and_clean(path):
6060
# Convert total votes to an integer
6161
row['votes'] = int(row['votes'])
6262

63-
# Store county-level results by office/district pair, then by candidate party and raw name
64-
race_key = (row['office'], row['district'])
63+
# Store county-level results by slugified office and district (if there is one),
64+
# then by candidate party and raw name
65+
race_key = row['office']
66+
if row['district']:
67+
race_key += "-%s" % row['district']
6568
# Create unique candidate key from party and name, in case multiple candidates have same
66-
cand_key = (row['party'], row['candidate'])
69+
cand_key = "-".join((row['party'], row['candidate']))
6770
# Below, setdefault initializes empty dict and list for the respective keys if they don't already exist.
6871
race = results[race_key]
6972
race.setdefault(cand_key, []).append(row)
@@ -131,7 +134,7 @@ def write_csv(summary):
131134
as this module.
132135
133136
"""
134-
outfile = join(dirname(abspath(__file__)), 'summary_results.csv')
137+
outfile = join(dirname((__file__)), 'summary_results.csv')
135138
with open(outfile, 'wb') as fh:
136139
# Limit output to cleanly parsed, standardized values
137140
fieldnames = [

elex2/tests/__init__.py

Whitespace-only changes.

elex2/tests/sample_results.csv

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
date,office,district,county,candidate,party,votes
2+
2012-11-06,President,,Some County,"Smith, Joe",GOP,10
3+
2012-11-06,President,,Some County,"Doe, Jane",DEM,11
4+
2012-11-06,President,,Another County,"Smith, Joe",GOP,5
5+
2012-11-06,President,,Another County,"Doe, Jane",DEM,5
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"President": {
3+
"DEM-Doe, Jane": [
4+
{
5+
"candidate": "Doe, Jane",
6+
"county": "Some County",
7+
"date": "2012-11-06",
8+
"district": "",
9+
"first_name": "Jane",
10+
"last_name": "Doe",
11+
"office": "President",
12+
"party": "DEM",
13+
"votes": 11
14+
},
15+
{
16+
"candidate": "Doe, Jane",
17+
"county": "Another County",
18+
"date": "2012-11-06",
19+
"district": "",
20+
"first_name": "Jane",
21+
"last_name": "Doe",
22+
"office": "President",
23+
"party": "DEM",
24+
"votes": 5
25+
}
26+
],
27+
"GOP-Smith, Joe": [
28+
{
29+
"candidate": "Smith, Joe",
30+
"county": "Some County",
31+
"date": "2012-11-06",
32+
"district": "",
33+
"first_name": "Joe",
34+
"last_name": "Smith",
35+
"office": "President",
36+
"party": "GOP",
37+
"votes": 10
38+
},
39+
{
40+
"candidate": "Smith, Joe",
41+
"county": "Another County",
42+
"date": "2012-11-06",
43+
"district": "",
44+
"first_name": "Joe",
45+
"last_name": "Smith",
46+
"office": "President",
47+
"party": "GOP",
48+
"votes": 5
49+
}
50+
]
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"President": {
3+
"DEM-Doe, Jane": [
4+
{
5+
"candidate": "Doe, Jane",
6+
"county": "Some County",
7+
"date": "2012-11-06",
8+
"district": "",
9+
"first_name": "Jane",
10+
"last_name": "Doe",
11+
"office": "President",
12+
"party": "DEM",
13+
"votes": 10
14+
},
15+
{
16+
"candidate": "Doe, Jane",
17+
"county": "Another County",
18+
"date": "2012-11-06",
19+
"district": "",
20+
"first_name": "Jane",
21+
"last_name": "Doe",
22+
"office": "President",
23+
"party": "DEM",
24+
"votes": 5
25+
}
26+
],
27+
"GOP-Smith, Joe": [
28+
{
29+
"candidate": "Smith, Joe",
30+
"county": "Some County",
31+
"date": "2012-11-06",
32+
"district": "",
33+
"first_name": "Joe",
34+
"last_name": "Smith",
35+
"office": "President",
36+
"party": "GOP",
37+
"votes": 10
38+
},
39+
{
40+
"candidate": "Smith, Joe",
41+
"county": "Another County",
42+
"date": "2012-11-06",
43+
"district": "",
44+
"first_name": "Joe",
45+
"last_name": "Smith",
46+
"office": "President",
47+
"party": "GOP",
48+
"votes": 5
49+
}
50+
]
51+
}
52+
}

elex2/tests/test_parser.py

+14-23
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,20 @@
1+
from os.path import dirname, join
12
from unittest import TestCase
23

3-
from .election_results import clean_office, clean_party, percent
4+
from elex2.election_results import parse_and_clean
45

56

67
class TestDataCleaners(TestCase):
78

8-
def test_clean_office_rep(self):
9-
self.assertEquals(clean_office('U.S. Rep - 1'), ('U.S. House of Representatives', 1))
10-
11-
def test_clean_office_other(self):
12-
self.assertEquals(clean_office('U.S. Senate'), ('U.S. Senate', ''))
13-
14-
def test_clean_party_gop(self):
15-
self.assertEquals(clean_party('GOP'), 'REP')
16-
17-
def test_clean_party_dem(self):
18-
self.assertEquals(clean_party('Democratic'), 'DEM')
19-
20-
def test_clean_party_others(self):
21-
self.assertEquals(clean_party('Green'), 'GREEN')
22-
23-
24-
25-
class TestPercentFunc(TestCase):
26-
27-
def test_percent(self):
28-
"test_percent returns percentage as string"
29-
self.assertEquals(percent(50, 100), '50')
9+
class TestParser(TestCase):
10+
11+
def test_name_parsing(self):
12+
"Parser should split full candidate name into first and last names"
13+
path = join(dirname(__file__), 'sample_results.csv')
14+
results = parse_and_clean(path)
15+
race_key = 'President'
16+
cand_key = 'GOP-Smith, Joe'
17+
# Get one county result
18+
smith = results[race_key][cand_key][0]
19+
self.assertEqual(smith['first_name'], 'Joe')
20+
self.assertEqual(smith['last_name'], 'Smith')

elex2/tests/test_summary.py

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from os.path import dirname, join
2+
from unittest import TestCase
3+
import json
4+
5+
from elex2.election_results import summarize
6+
7+
8+
class TestSummaryResults(TestCase):
9+
10+
# Read the results of the parse_and_clean function stored in a test fixture
11+
json_file = open(join(dirname(__file__), 'sample_results_parsed.json'), 'rb')
12+
SAMPLE_RESULTS = json.load(json_file)
13+
# Q: Why aren't we just using the parse_and_clean method instead of
14+
# using a snapshot of that function's output?
15+
# A: To achieve better test isolation!
16+
17+
# Q: Why aren't reading in the JSON in a setUp method?
18+
# A: setUp is called before each test method. This ensures we only
19+
# incur the overhead of reading in the JSON once. In python2.7 or newer,
20+
# you should use the setUpClass method instead of a class attribute.
21+
# http://docs.python.org/2/library/unittest.html#unittest.TestCase.setUpClass
22+
23+
# We will, however, use the setUp method to call the summarize
24+
# funciton afresh before each of our test methods.
25+
def setUp(self):
26+
results = summarize(self.SAMPLE_RESULTS)
27+
self.race = results['President']
28+
29+
def test_racewide_vote_total(self):
30+
"Summary results should be annotated with total votes cast in race"
31+
self.assertEqual(self.race['all_votes'], 31)
32+
33+
def test_candiate_vote_totals(self):
34+
"Summary candidates should reflect total votes from all counties"
35+
# Loop through candidates and find Smith rather than relying on
36+
# default sorting of candidates, which would make this test brittle
37+
# the implementation changed.
38+
smith = [cand for cand in self.race['candidates'] if cand['last_name'] == 'Smith'][0]
39+
self.assertEqual(smith['votes'], 15)
40+
41+
def test_winner_has_flag(self):
42+
"Winner flag should be assigned to candidates with most votes"
43+
doe = [cand for cand in self.race['candidates'] if cand['last_name'] == 'Doe'][0]
44+
self.assertEqual(doe['winner'], 'X')
45+
46+
def test_loser_has_no_winner_flag(self):
47+
"Winner flag should be not be assigned to candidate with that does not have highest vote total"
48+
smith = [cand for cand in self.race['candidates'] if cand['last_name'] == 'Smith'][0]
49+
self.assertEqual(smith['winner'], '')
50+
51+
52+
class TestTieRace(TestCase):
53+
54+
# Q: Why do we need a new class and fixture for this race?
55+
# A: So that we can change the vote counts so that we have a tie, of course!
56+
# We don't *need* a new test class, but hey, why not?
57+
json_file = open(join(dirname(__file__), 'sample_results_parsed_tie_race.json'), 'rb')
58+
SAMPLE_RESULTS = json.load(json_file)
59+
60+
def test_tie_race_winner_flags(self):
61+
"Winner flag should not be assigned to any candidate in a tie race"
62+
pass
63+
results = summarize(self.SAMPLE_RESULTS)
64+
race = results['President']
65+
for cand in race['candidates']:
66+
self.assertEqual(cand['winner'], '')

0 commit comments

Comments
 (0)