Skip to content

Commit d82b917

Browse files
authored
Merge pull request UWPCE-PythonCert#187 from mattmaeda/master
mailroom app with basic logging
2 parents 67adf62 + 17c3eba commit d82b917

File tree

15 files changed

+670
-0
lines changed

15 files changed

+670
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
License
2+
-------
3+
4+
Licensed under the Creative Commons Attribution-ShareAlike 4.0 International Public License.
5+
6+
https://creativecommons.org/licenses/by-sa/4.0/legalcode
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The mailroom app for python class
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Entry point for the mailroom app
5+
"""
6+
7+
import mailroom.cli
8+
9+
if __name__ == "__main__":
10+
mailroom.cli.main()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
mailroom package
5+
"""
6+
7+
from pathlib import Path
8+
9+
__version__ = "0.1.1"
10+
11+
DATA_DIR = Path(__file__).parent / "data"
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env python
2+
"""
3+
Interface to mailroom package
4+
"""
5+
6+
from __future__ import print_function
7+
8+
import sys
9+
import math
10+
import logging
11+
from mailroom import model, DATA_DIR
12+
13+
logging.getLogger(__name__)
14+
15+
MENU = """
16+
Choose an option:
17+
18+
1. Send Thank You
19+
2. Create Report
20+
3. Quit
21+
22+
> """
23+
24+
DB = model.DonorDB.load_from_file(DATA_DIR / "sample_data.json")
25+
26+
def get_selection():
27+
""" Display menu option and get user input """
28+
logging.info("Get menu selections")
29+
selection = input(MENU)
30+
logging.info("User selected option %s", selection)
31+
return selection.strip()
32+
33+
34+
def send_thank_you():
35+
""" Record a donation and send thank you message """
36+
while True:
37+
logging.debug("Getting user input for name")
38+
name = input("Enter donor's name > ").strip()
39+
break
40+
41+
while True:
42+
amount = input("Enter a donation amount > ").strip()
43+
try:
44+
logging.debug("Getting user input for donation amount")
45+
amount = float(amount)
46+
if math.isnan(amount) or math.isinf(amount) or round(amount, 2) == 0.00:
47+
logging.warn("User did not enter a valid amount")
48+
raise ValueError
49+
break
50+
except ValueError:
51+
print("Invalid amount '{}' entered.".format(amount))
52+
53+
donor = DB.get_donor(name)
54+
if donor is None:
55+
logging.info("Donor info collected. Adding donor")
56+
donor = DB.add_donor(name)
57+
58+
donor.add_donation(amount)
59+
print(DB.send_letter(donor))
60+
61+
62+
def print_report():
63+
""" Print out donor report """
64+
logging.info("Print out the user report")
65+
print(DB.get_donor_report())
66+
67+
68+
def quit_program():
69+
""" Exits program """
70+
logging.info("Exiting program")
71+
sys.exit(0)
72+
73+
74+
def main():
75+
""" Entry point for the entire application """
76+
menu_dict = {"1": send_thank_you,
77+
"2": print_report,
78+
"3": quit_program}
79+
80+
while True:
81+
selection = get_selection()
82+
print(selection)
83+
84+
try:
85+
menu_dict[selection]()
86+
except KeyError:
87+
print("ERROR: Selection '{}' is invalid!".format(selection))
88+
89+
90+
if __name__ == "__main__":
91+
main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"George Washington":[1],"John Adams":[3],"Thomas Jefferson":[3],"John Quincy Adams":[2],"James Madison":[2]}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
#!/usr/bin/env python
2+
"""
3+
Data model for mailroom package
4+
"""
5+
import json
6+
import logging
7+
8+
logging.getLogger(__name__)
9+
10+
THANK_YOU_LETTER = """
11+
Dear {0:s},
12+
13+
Thank you for your donation of ${1:.2f}.
14+
"""
15+
16+
REPORT_HEADER = "{donor: <50}| {total: <12}| {num: <10}| {avg: <12}"
17+
REPORT_LINE = "{0: <50} ${1: 12.2f}{2: 12d}{3: 14.2f}"
18+
19+
class Donor(object):
20+
"""
21+
Donor class responsible for handling donor information
22+
"""
23+
_donorname = None
24+
25+
def __init__(self, donor_name, initial_donations=None):
26+
logging.info("Set up donor '%s'", donor_name)
27+
self.name = donor_name
28+
if initial_donations is None:
29+
logging.info("Donor %s has not initial donations", donor_name)
30+
initial_donations = []
31+
self.donations = list(initial_donations)
32+
33+
34+
@property
35+
def name(self):
36+
""" donor name getter """
37+
return self._donorname
38+
39+
40+
@name.setter
41+
def name(self, donor_name):
42+
""" donor name setter
43+
:param donor_name: donor name
44+
:type: str
45+
"""
46+
self._donorname = donor_name
47+
48+
49+
@property
50+
def total_donations(self):
51+
""" donation getter """
52+
logging.info("Get the total donations for %s", self.name)
53+
return sum(self.donations)
54+
55+
56+
@property
57+
def avg_donations(self):
58+
""" get average donations """
59+
logging.info("Get the average donations for %s", self.name)
60+
return self.total_donations / len(self.donations)
61+
62+
63+
@property
64+
def num_donations(self):
65+
""" get number of donations """
66+
logging.info("Getting the number of donations for %s", self.name)
67+
return len(self.donations)
68+
69+
70+
@property
71+
def last_donation(self):
72+
""" get the last donation made by donor """
73+
logging.info("Get the last donation for %s", self.name)
74+
try:
75+
return self.donations[-1]
76+
except IndexError:
77+
logging.warn("Donor %s has not made any donations", self.name)
78+
return None
79+
80+
81+
def add_donation(self, amount):
82+
""" add donation to the list of donations """
83+
logging.info("Donor %s made a donation of %d", self.name, amount)
84+
self.donations.append(amount)
85+
86+
87+
class DonorDB(object):
88+
"""
89+
Database that holds all donor information
90+
"""
91+
92+
def __init__(self, donors=None):
93+
if donors is None:
94+
donors = {}
95+
self.donor_data = {d.name: d for d in donors}
96+
97+
98+
def save_to_file(self, filename):
99+
"""
100+
output donor information to file
101+
"""
102+
output = {k: v.donations for k, v in self.donor_data.items()}
103+
with open(filename, "w") as outfile:
104+
json.dump(output, outfile)
105+
106+
107+
@classmethod
108+
def load_from_file(cls, filename):
109+
"""
110+
Load DB from JSON file
111+
"""
112+
with open(filename) as infile:
113+
donors = json.load(infile)
114+
115+
return cls([Donor(*d) for d in donors.items()])
116+
117+
118+
@property
119+
def donors(self):
120+
"""
121+
get donation values
122+
"""
123+
return self.donor_data.values()
124+
125+
126+
def get_donor(self, name):
127+
"""
128+
get donor by name
129+
130+
:param name: name
131+
132+
:return: donor object; None if not found
133+
:rtype: Donor
134+
"""
135+
logging.info("Getting donor object for donor %s", name)
136+
return self.donor_data.get(name)
137+
138+
139+
def add_donor(self, name):
140+
"""
141+
Add new donor to DB
142+
143+
:param name: donor name
144+
145+
:return: donor object
146+
:rtype: Donor
147+
"""
148+
logging.info("Adding donor %s", name)
149+
donor = Donor(name)
150+
self.donor_data[name] = donor
151+
return donor
152+
153+
154+
@staticmethod
155+
def send_letter(donor):
156+
"""
157+
Generate thank you letter
158+
159+
:param donor: donor object
160+
161+
:return: formatted thank you letter
162+
:rtype: String
163+
"""
164+
logging.info("Generating thank you letter for %s", donor.name)
165+
return THANK_YOU_LETTER.format(donor.name, donor.last_donation)
166+
167+
168+
def get_donor_report(self):
169+
"""
170+
Generate sorted list of donors from largest donation total to the least
171+
172+
:return: formatted donation report
173+
:rtype: String
174+
"""
175+
logging.info("Generating the donor report")
176+
donor_dict = {}
177+
178+
for donor in self.donor_data.values():
179+
donor_dict.setdefault(donor.total_donations, []).append(donor)
180+
181+
report = "\n"
182+
report += (REPORT_HEADER.format(donor="Donor Name",
183+
total="Total Given",
184+
num="Num Gifts",
185+
avg="Average Gift"))
186+
report += "\n"
187+
report += "-" * 90
188+
report += "\n"
189+
190+
191+
for amount in reversed(sorted(donor_dict)):
192+
for donor in donor_dict[amount]:
193+
report += (REPORT_LINE.format(donor.name,
194+
donor.total_donations,
195+
donor.num_donations,
196+
donor.avg_donations))
197+
report += "\n"
198+
199+
report += "\n\n"
200+
return report
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[report]
2+
exclude_lines =
3+
if __name__ == .__main__.:

students/mattmaeda/mailroom_app/mailroom/test/__init__.py

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"George Washington":[1]}

0 commit comments

Comments
 (0)