This repository was archived by the owner on Jun 9, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 32
mailroom app with basic logging #187
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| License | ||
| ------- | ||
|
|
||
| Licensed under the Creative Commons Attribution-ShareAlike 4.0 International Public License. | ||
|
|
||
| https://creativecommons.org/licenses/by-sa/4.0/legalcode |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| The mailroom app for python class |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| #!/usr/bin/env python | ||
|
|
||
| """ | ||
| Entry point for the mailroom app | ||
| """ | ||
|
|
||
| import mailroom.cli | ||
|
|
||
| if __name__ == "__main__": | ||
| mailroom.cli.main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #!/usr/bin/env python | ||
|
|
||
| """ | ||
| mailroom package | ||
| """ | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| __version__ = "0.1.1" | ||
|
|
||
| DATA_DIR = Path(__file__).parent / "data" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| #!/usr/bin/env python | ||
| """ | ||
| Interface to mailroom package | ||
| """ | ||
|
|
||
| from __future__ import print_function | ||
|
|
||
| import sys | ||
| import math | ||
| import logging | ||
| from mailroom import model, DATA_DIR | ||
|
|
||
| logging.getLogger(__name__) | ||
|
|
||
| MENU = """ | ||
| Choose an option: | ||
|
|
||
| 1. Send Thank You | ||
| 2. Create Report | ||
| 3. Quit | ||
|
|
||
| > """ | ||
|
|
||
| DB = model.DonorDB.load_from_file(DATA_DIR / "sample_data.json") | ||
|
|
||
| def get_selection(): | ||
| """ Display menu option and get user input """ | ||
| logging.info("Get menu selections") | ||
| selection = input(MENU) | ||
| logging.info("User selected option %s", selection) | ||
| return selection.strip() | ||
|
|
||
|
|
||
| def send_thank_you(): | ||
| """ Record a donation and send thank you message """ | ||
| while True: | ||
| logging.debug("Getting user input for name") | ||
| name = input("Enter donor's name > ").strip() | ||
| break | ||
|
|
||
| while True: | ||
| amount = input("Enter a donation amount > ").strip() | ||
| try: | ||
| logging.debug("Getting user input for donation amount") | ||
| amount = float(amount) | ||
| if math.isnan(amount) or math.isinf(amount) or round(amount, 2) == 0.00: | ||
| logging.warn("User did not enter a valid amount") | ||
| raise ValueError | ||
| break | ||
| except ValueError: | ||
| print("Invalid amount '{}' entered.".format(amount)) | ||
|
|
||
| donor = DB.get_donor(name) | ||
| if donor is None: | ||
| logging.info("Donor info collected. Adding donor") | ||
| donor = DB.add_donor(name) | ||
|
|
||
| donor.add_donation(amount) | ||
| print(DB.send_letter(donor)) | ||
|
|
||
|
|
||
| def print_report(): | ||
| """ Print out donor report """ | ||
| logging.info("Print out the user report") | ||
| print(DB.get_donor_report()) | ||
|
|
||
|
|
||
| def quit_program(): | ||
| """ Exits program """ | ||
| logging.info("Exiting program") | ||
| sys.exit(0) | ||
|
|
||
|
|
||
| def main(): | ||
| """ Entry point for the entire application """ | ||
| menu_dict = {"1": send_thank_you, | ||
| "2": print_report, | ||
| "3": quit_program} | ||
|
|
||
| while True: | ||
| selection = get_selection() | ||
| print(selection) | ||
|
|
||
| try: | ||
| menu_dict[selection]() | ||
| except KeyError: | ||
| print("ERROR: Selection '{}' is invalid!".format(selection)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"George Washington":[1],"John Adams":[3],"Thomas Jefferson":[3],"John Quincy Adams":[2],"James Madison":[2]} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| #!/usr/bin/env python | ||
| """ | ||
| Data model for mailroom package | ||
| """ | ||
| import json | ||
| import logging | ||
|
|
||
| logging.getLogger(__name__) | ||
|
|
||
| THANK_YOU_LETTER = """ | ||
| Dear {0:s}, | ||
|
|
||
| Thank you for your donation of ${1:.2f}. | ||
| """ | ||
|
|
||
| REPORT_HEADER = "{donor: <50}| {total: <12}| {num: <10}| {avg: <12}" | ||
| REPORT_LINE = "{0: <50} ${1: 12.2f}{2: 12d}{3: 14.2f}" | ||
|
|
||
| class Donor(object): | ||
| """ | ||
| Donor class responsible for handling donor information | ||
| """ | ||
| _donorname = None | ||
|
|
||
| def __init__(self, donor_name, initial_donations=None): | ||
| logging.info("Set up donor '%s'", donor_name) | ||
| self.name = donor_name | ||
| if initial_donations is None: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preferred way of saying this is |
||
| logging.info("Donor %s has not initial donations", donor_name) | ||
| initial_donations = [] | ||
| self.donations = list(initial_donations) | ||
|
|
||
|
|
||
| @property | ||
| def name(self): | ||
| """ donor name getter """ | ||
| return self._donorname | ||
|
|
||
|
|
||
| @name.setter | ||
| def name(self, donor_name): | ||
| """ donor name setter | ||
| :param donor_name: donor name | ||
| :type: str | ||
| """ | ||
| self._donorname = donor_name | ||
|
|
||
|
|
||
| @property | ||
| def total_donations(self): | ||
| """ donation getter """ | ||
| logging.info("Get the total donations for %s", self.name) | ||
| return sum(self.donations) | ||
|
|
||
|
|
||
| @property | ||
| def avg_donations(self): | ||
| """ get average donations """ | ||
| logging.info("Get the average donations for %s", self.name) | ||
| return self.total_donations / len(self.donations) | ||
|
|
||
|
|
||
| @property | ||
| def num_donations(self): | ||
| """ get number of donations """ | ||
| logging.info("Getting the number of donations for %s", self.name) | ||
| return len(self.donations) | ||
|
|
||
|
|
||
| @property | ||
| def last_donation(self): | ||
| """ get the last donation made by donor """ | ||
| logging.info("Get the last donation for %s", self.name) | ||
| try: | ||
| return self.donations[-1] | ||
| except IndexError: | ||
| logging.warn("Donor %s has not made any donations", self.name) | ||
| return None | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Python returns None by default. |
||
|
|
||
|
|
||
| def add_donation(self, amount): | ||
| """ add donation to the list of donations """ | ||
| logging.info("Donor %s made a donation of %d", self.name, amount) | ||
| self.donations.append(amount) | ||
|
|
||
|
|
||
| class DonorDB(object): | ||
| """ | ||
| Database that holds all donor information | ||
| """ | ||
|
|
||
| def __init__(self, donors=None): | ||
| if donors is None: | ||
| donors = {} | ||
| self.donor_data = {d.name: d for d in donors} | ||
|
|
||
|
|
||
| def save_to_file(self, filename): | ||
| """ | ||
| output donor information to file | ||
| """ | ||
| output = {k: v.donations for k, v in self.donor_data.items()} | ||
| with open(filename, "w") as outfile: | ||
| json.dump(output, outfile) | ||
|
|
||
|
|
||
| @classmethod | ||
| def load_from_file(cls, filename): | ||
| """ | ||
| Load DB from JSON file | ||
| """ | ||
| with open(filename) as infile: | ||
| donors = json.load(infile) | ||
|
|
||
| return cls([Donor(*d) for d in donors.items()]) | ||
|
|
||
|
|
||
| @property | ||
| def donors(self): | ||
| """ | ||
| get donation values | ||
| """ | ||
| return self.donor_data.values() | ||
|
|
||
|
|
||
| def get_donor(self, name): | ||
| """ | ||
| get donor by name | ||
|
|
||
| :param name: name | ||
|
|
||
| :return: donor object; None if not found | ||
| :rtype: Donor | ||
| """ | ||
| logging.info("Getting donor object for donor %s", name) | ||
| return self.donor_data.get(name) | ||
|
|
||
|
|
||
| def add_donor(self, name): | ||
| """ | ||
| Add new donor to DB | ||
|
|
||
| :param name: donor name | ||
|
|
||
| :return: donor object | ||
| :rtype: Donor | ||
| """ | ||
| logging.info("Adding donor %s", name) | ||
| donor = Donor(name) | ||
| self.donor_data[name] = donor | ||
| return donor | ||
|
|
||
|
|
||
| @staticmethod | ||
| def send_letter(donor): | ||
| """ | ||
| Generate thank you letter | ||
|
|
||
| :param donor: donor object | ||
|
|
||
| :return: formatted thank you letter | ||
| :rtype: String | ||
| """ | ||
| logging.info("Generating thank you letter for %s", donor.name) | ||
| return THANK_YOU_LETTER.format(donor.name, donor.last_donation) | ||
|
|
||
|
|
||
| def get_donor_report(self): | ||
| """ | ||
| Generate sorted list of donors from largest donation total to the least | ||
|
|
||
| :return: formatted donation report | ||
| :rtype: String | ||
| """ | ||
| logging.info("Generating the donor report") | ||
| donor_dict = {} | ||
|
|
||
| for donor in self.donor_data.values(): | ||
| donor_dict.setdefault(donor.total_donations, []).append(donor) | ||
|
|
||
| report = "\n" | ||
| report += (REPORT_HEADER.format(donor="Donor Name", | ||
| total="Total Given", | ||
| num="Num Gifts", | ||
| avg="Average Gift")) | ||
| report += "\n" | ||
| report += "-" * 90 | ||
| report += "\n" | ||
|
|
||
|
|
||
| for amount in reversed(sorted(donor_dict)): | ||
| for donor in donor_dict[amount]: | ||
| report += (REPORT_LINE.format(donor.name, | ||
| donor.total_donations, | ||
| donor.num_donations, | ||
| donor.avg_donations)) | ||
| report += "\n" | ||
|
|
||
| report += "\n\n" | ||
| return report | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| [report] | ||
| exclude_lines = | ||
| if __name__ == .__main__.: |
Empty file.
1 change: 1 addition & 0 deletions
1
students/mattmaeda/mailroom_app/mailroom/test/mock_load_file.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"George Washington":[1]} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would consider breaking this into two functions, get_donor_name and get_donation_amount, or something like that. Your function would look something like this then: