From 16d50d7e3023778ffc1d86b4774bf8b0003403cd Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sat, 25 Jan 2020 09:16:02 -0500 Subject: [PATCH 01/16] Updated documentation As part of re-organizing the code, want to document all the attributes I'm tracking in each class to make it easier to maintain. --- donation.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/donation.py b/donation.py index 4b4c04b..b85c836 100644 --- a/donation.py +++ b/donation.py @@ -1,9 +1,21 @@ +"""A class to hold the Donation attributes and methods.""" + class Donation: """Donation Attributes. Class exists to provide attributes for a donation based on what comes in from the JSON so that it doesn't have to be traversed each time a donor action needs to be taken. + + API Variables: + name: the name of the donor for this donation. + If the donor wished to stay anonymous, + the variable is set to "Anonymous" + + message: the message associated with the donation. + + amount: the amount of the donation. If they blocked it from showing + it is set to 0. """ def __init__(self, name, message, amount): From fe955a5a7bf952faf63b9a5389a54c3f8ed4959d Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sat, 25 Jan 2020 09:45:54 -0500 Subject: [PATCH 02/16] Documentation and funciton scope Mostly added a bunch of docstrings, but also made 2 functions private since they're only called by this class. --- call_settings.py | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/call_settings.py b/call_settings.py index 6c36b13..6366bd1 100644 --- a/call_settings.py +++ b/call_settings.py @@ -1,3 +1,5 @@ +"""Contains the programming logic for the settings window in the GUI.""" + import sys import json from PyQt5.QtWidgets import QDialog, QApplication, QFileDialog @@ -7,7 +9,15 @@ class MyForm(QDialog): + """Class for the settings Window.""" + def __init__(self, participant_conf): + """Init for the settings Window. + + Grabs the data from the participant.conf file and + uses that to pre-populate the fields in the settings + window. + """ super().__init__() self.ui = Ui_Dialog() self.ui.setupUi(self) @@ -25,21 +35,31 @@ def __init__(self, participant_conf): self.ui.pushButtonRevert.clicked.connect(self.revert) self.ui.pushButtonSave.clicked.connect(self.save) self.ui.pushButton_persistentsave.clicked.connect(self.persistent_save) - self.ui.pushButtonSelectFolder.clicked.connect(self.selectfolder) - self.ui.pushButton_tracker_image.clicked.connect(lambda: self.selectfile("image")) - self.ui.pushButton_sound.clicked.connect(lambda: self.selectfile("sound")) - #self.ui.spinBox_DonorsToDisplay.valueChanged.connect(self.donorschanged) + self.ui.pushButtonSelectFolder.clicked.connect(self._selectfolder) + self.ui.pushButton_tracker_image.clicked.connect(lambda: self._selectfile("image")) + self.ui.pushButton_sound.clicked.connect(lambda: self._selectfile("sound")) + # self.ui.spinBox_DonorsToDisplay.valueChanged.connect(self.donorschanged) if self.donors_to_display is None: self.ui.spinBox_DonorsToDisplay.setValue(0) else: self.ui.spinBox_DonorsToDisplay.setValue(int(self.donors_to_display)) def reload_config(self): + """Reload the values from the config file. + + Called by gui.py before loading the settings window. + """ (self.ExtraLifeID, self.textFolder, self.CurrencySymbol, self.TeamID, self.TrackerImage, self.DonationSound, self.donors_to_display) = self.participant_conf.get_GUI_values() def revert(self): + """ + Revert the values in the settings window. + + If the user made mistakes while editing the config this will + take them back to the values since they opened the window. + """ self.ui.lineEditParticipantID.setText(self.ExtraLifeID) self.ui.labelTextFolder.setText(self.textFolder) self.ui.lineEditCurrencySymbol.setText(self.CurrencySymbol) @@ -72,18 +92,23 @@ def _elements_to_save(self): return config def save(self): + """Save the values in the window. + + Calls the write_config method from extralife_IO.ParticipantConf + """ config = self._elements_to_save() self.participant_conf.write_config(config, True) def persistent_save(self): + """Use xdg_config, saves a persistent config to the XDG spot.""" config = self._elements_to_save() self.participant_conf.write_config(config, False) - def selectfolder(self): + def _selectfolder(self): directory = QFileDialog.getExistingDirectory(self, "Get Folder") self.ui.labelTextFolder.setText(directory) - def selectfile(self, whichfile): + def _selectfile(self, whichfile): the_file = QFileDialog.getOpenFileName(self, "Select File") if whichfile == "image": self.ui.label_tracker_image.setText(the_file[0]) @@ -92,6 +117,7 @@ def selectfile(self, whichfile): def main(participant_conf): + """Launch it.""" w = MyForm(participant_conf) w.exec() From 4d304f56bea088f08ab456aabcaa3766573af2a6 Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sat, 25 Jan 2020 10:06:51 -0500 Subject: [PATCH 03/16] Documentation and function scope Again, added docstrings and made some variables private variables. Props to Kdevelop for making that incredibly easy by showing when functions were being called outside the class. --- call_tracker.py | 49 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/call_tracker.py b/call_tracker.py index 273e038..e24a4f9 100644 --- a/call_tracker.py +++ b/call_tracker.py @@ -1,3 +1,5 @@ +"""A window that displays the last donation. Useful during streaming.""" + import sys from PyQt5.QtWidgets import QDialog, QApplication, QGraphicsScene, QGraphicsPixmapItem from tracker import * @@ -8,7 +10,14 @@ class MyForm(QDialog): + """The class to hold the tracker window.""" + def __init__(self, participant_conf): + """Set up the window. + + Loads in the image and sound file the user specified in the + participant.conf settings file. + """ super().__init__() # config stuff self.participant_conf = participant_conf @@ -17,39 +26,46 @@ def __init__(self, participant_conf): self.ui.setupUi(self) self.scene = QGraphicsScene(self) self.pixmap = QtGui.QPixmap() - self.loadimage() + self._loadimage() self.ui.graphicsView.setScene(self.scene) self.donation_player = QMediaPlayer() - self.loadsound() + self._loadsound() # timer to update the main text self.timer = QtCore.QTimer(self) self.timer.setSingleShot(False) self.timer.setInterval(20000) # milliseconds - self.timer.timeout.connect(self.loadAndUnload) + self.timer.timeout.connect(self._loadAndUnload) self.timer.start() - def loadimage(self): + def _loadimage(self): self.tracker_image = self.participant_conf.get_tracker_image() self.pixmap.load(self.tracker_image) self.item = QGraphicsPixmapItem(self.pixmap.scaledToHeight(131)) - def loadsound(self): + def _loadsound(self): sound_to_play = self.participant_conf.get_tracker_sound() self.donation_sound= QMediaContent(QUrl.fromLocalFile(sound_to_play)) self.donation_player.setMedia(self.donation_sound) def loadAndUnloadTest(self): - self.loadimage() - self.loadElements() - self.loadsound() + """Trigger the tracker functionality. + + This causes the image and sound to load so that the + user can test to see how it's going to look on their OBS + or XSplit screen as well as to make sure they can hear + the sound. Called by gui.py. + """ + self._loadimage() + self._loadElements() + self._loadsound() self.donation_player.play() unloadtimer = QtCore.QTimer(self) unloadtimer.setSingleShot(True) unloadtimer.setInterval(5000) # milliseconds - unloadtimer.timeout.connect(self.unloadElements) + unloadtimer.timeout.connect(self._unloadElements) unloadtimer.start() - def loadAndUnload(self): + def _loadAndUnload(self): self.folders = self.participant_conf.get_text_folder_only() IPC = "0" try: @@ -61,17 +77,17 @@ def loadAndUnload(self): Have you updated the settings? Have you hit the 'run' button?""") if IPC == "1": - self.loadimage() - self.loadElements() - self.loadsound() + self._loadimage() + self._loadElements() + self._loadsound() self.donation_player.play() unloadtimer = QtCore.QTimer(self) unloadtimer.setSingleShot(True) unloadtimer.setInterval(5000) # milliseconds - unloadtimer.timeout.connect(self.unloadElements) + unloadtimer.timeout.connect(self._unloadElements) unloadtimer.start() - def loadElements(self): + def _loadElements(self): self.scene.addItem(self.item) # want to also play a sound try: @@ -82,13 +98,14 @@ def loadElements(self): except FileNotFoundError: self.ui.Donation_label.setText("TEST 1...2...3...") - def unloadElements(self): + def _unloadElements(self): self.scene.removeItem(self.item) self.ui.Donation_label.setText("") IPC.writeIPC(self.folders, "0") def main(participant_conf): + """Run it.""" w = MyForm(participant_conf) w.exec() From fb48cd0b30eeb97aca297a649415290a107efbe4 Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sat, 25 Jan 2020 10:13:00 -0500 Subject: [PATCH 04/16] updated to reflect current situation Thanks to refactoring, there are new files that need to pass tests and flake8 linting. --- CONTRIBUTING.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6dfc50d..edae484 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,17 +1,25 @@ Master will always contain perfectly working code aligned with the latest release. -If you wish to contribute, please do so off of the devel branch and file a pull request with your changes. +If you wish to contribute, please do so off of the devel branch and file a pull request with your changes. If I have another branch pushed to Github that may temporarily be the branch to develop from. Check with me. -The code is up to PEP8 standards I have written unit tests. The following files should be without errors on flake8's test suite (don't worry about >80 char errors): +The code is up to PEP8 standards and I have written unit tests. The following files should be without errors on flake8's test suite (don't worry about >80 char errors): + +- donation.py + +- donor.py - extralifedonations.py +- extralife_IO.py + - IPC.py - readparticipantconf.py - team.py +All the unit tests in extralifeunittests.py should pass. + Because the PyQt classes and functions mimic their C/C++ classes and functions, they may violate PEP8 and other Python conventions, so flake8 does not have to pass on files related to the GUI. However, they should pass pydocstyle. The Github actions related to linting and unit tests must pass in order for a pull request to be accepted. (Subject, of course, to a waiver by me) From c50bcf15e86d9012c376b5a5034fd1b61234ff73 Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sat, 25 Jan 2020 10:19:28 -0500 Subject: [PATCH 05/16] Clarification on canonical list of lint files --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index edae484..9587451 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,8 @@ The code is up to PEP8 standards and I have written unit tests. The following fi - team.py +If that list above becomes out of date, the canonical list of files that need to pass flake8 can be found in the repo under .github/workflows/linttest.yml. + All the unit tests in extralifeunittests.py should pass. Because the PyQt classes and functions mimic their C/C++ classes and functions, they may violate PEP8 and other Python conventions, so flake8 does not have to pass on files related to the GUI. However, they should pass pydocstyle. From e57903f9f88347c451195f303229b70831e0cb1a Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sat, 25 Jan 2020 10:26:32 -0500 Subject: [PATCH 06/16] Listed variables For better tracking of what's going on as time goes on and if new contributors come onto the project. --- donor.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/donor.py b/donor.py index 599d6d2..11e62fb 100644 --- a/donor.py +++ b/donor.py @@ -7,6 +7,14 @@ class Donor: Class exists to provide attributes for a donor based on what comes in from the JSON so that it doesn't have to be traversed each time a donor action needs to be taken. + + API Variables: + name: donor's name if provided, else Anonymous + donor_id: the ID assigned by the API (currently not used) + image_url: the URL for the donor's avatar (currently not used) + amount: the sum of all donations the donor has made this campaign + number_of_dononations: the number of donations the donor has made + this campaign """ def __init__(self, json): @@ -16,7 +24,8 @@ def __init__(self, json): def json_to_attributes(self, json): """Convert JSON to Donor attributes. - May be overwritten by child classes.""" + May be overwritten by child classes. + """ if json.get('displayName') is not None: self.name = json.get('displayName') else: From be8513b4b61ecb363f3c2cfc76e14088a3d89137 Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sat, 25 Jan 2020 10:29:57 -0500 Subject: [PATCH 07/16] A couple doc string fixes --- extralife_IO.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extralife_IO.py b/extralife_IO.py index f8cb8df..4a27510 100644 --- a/extralife_IO.py +++ b/extralife_IO.py @@ -102,7 +102,8 @@ def write_config(self, config, default): """Write config to file. At this point, only called from GUI. Commandline - user is expected to edit file manually.""" + user is expected to edit file manually. + """ if default: with open('participant.conf', 'w') as outfile: json.dump(config, outfile) @@ -138,6 +139,7 @@ def get_GUI_values(self): self.fields["donors_to_display"]) def get_if_in_team(self): + """Return True if participant is in a team.""" # debug # print(self.fields["team_id"]) if self.fields["team_id"] is None: From 342675ed8aa31324f8f701451eb147ed9d458868 Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sat, 25 Jan 2020 10:46:20 -0500 Subject: [PATCH 08/16] Doc string updates --- gui.py | 21 +++++++++++++++------ team.py | 12 ++++++++++-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/gui.py b/gui.py index 5648903..b9903f8 100644 --- a/gui.py +++ b/gui.py @@ -1,4 +1,4 @@ -#should change from line edits to labels +"""The main GUI window.""" from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox, QInputDialog @@ -22,17 +22,26 @@ class ELDonationGUI(QMainWindow, design.Ui_MainWindow): + """The main gui Window.""" def __init__(self): - # Super allows us to access variables, methods etc in the design.py file + """Setup the GUI. + + We have a QTimer to allow the text on the GUI + to update without blocking the user from interacting + with the GUI. + + Then we instantiate the other windows: + tracker and settings + And connect the buttons. + """ super(self.__class__, self).__init__() # deal with version mismatch self.version_mismatch = participant_conf.get_version_mismatch() self.version_check() - self.setupUi(self) # This is defined in design.py file automatically - # It sets up layout and widgets that are defined + self.setupUi(self) # timer to update the main text self.timer = QtCore.QTimer(self) @@ -141,12 +150,12 @@ def getsomeText(self): def runbutton(self): print("run button") - # need to add some code to keep it from starting more than one thread. + # need to add some code to keep it from starting more than one thread. self.thread1 = donationGrabber() self.thread1.start() def stopbutton(self): - self.thread1.stop() + self.thread1.stop() class donationGrabber (threading.Thread): diff --git a/team.py b/team.py index de5c377..40b9098 100644 --- a/team.py +++ b/team.py @@ -1,11 +1,13 @@ -""" Contains classes pertaining to teams.""" +"""Contains classes pertaining to teams.""" import extralife_IO import donor class Team: """Hold Team Data.""" + def __init__(self, team_ID, folder, currency_symbol): + """Set the team variables.""" # urls self.team_url = f"https://www.extra-life.org/api/teams/{team_ID}" self.team_participant_url = f"https://www.extra-life.org/api/teams/{team_ID}/participants" @@ -51,7 +53,10 @@ def get_top_5_participants(self): self.top_5_participant_list = [TeamParticipant(self.top5_team_participant_json[participant]) for participant in range(0, len(self.top5_team_participant_json))] def _top_participant(self): - """ Get Top Team Participant. This should just grab element 0 from above instead of hitting API twice""" + """Get Top Team Participant. + + This should just grab element 0 from above instead of hitting API twice + """ if len(self.top_5_participant_list) == "0": print("No participants") else: @@ -68,10 +73,12 @@ def write_text_files(self, dictionary): extralife_IO.write_text_files(dictionary, self.output_folder) def team_run(self): + """Get team info from API.""" self.get_team_json() self.write_text_files(self.team_info) def participant_run(self): + """Get and calculate team partipant info.""" self.get_participants() self.get_top_5_participants() self._participant_calculations() @@ -80,6 +87,7 @@ def participant_run(self): class TeamParticipant(donor.Donor): """Participant Attributes.""" + def json_to_attributes(self, json): """Convert JSON to Team Participant attributes.""" if json.get('displayName') is not None: From 1505cf4bdd7ea55a6d6d8da0aa1cd510b9c8ca35 Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sat, 25 Jan 2020 11:14:34 -0500 Subject: [PATCH 09/16] Mostly doc string update But also made a note for a place where I need to fix the JSON API retrieval because right now it will cause the program to error out if it can't get the JSON at that place - something I've had to deal with since the DDOS of 2019. --- team.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/team.py b/team.py index 40b9098..8baa5c6 100644 --- a/team.py +++ b/team.py @@ -7,7 +7,28 @@ class Team: """Hold Team Data.""" def __init__(self, team_ID, folder, currency_symbol): - """Set the team variables.""" + """Set the team variables. + + API Variables: + team_url and team_participant_url: self-explanatory + team_info: a dictionary to hold the following: + - Team_goal: fundraising goal + - Team_captain: team captain's name + - Team_totalRaised: total amount raised by team + - Team_numDonations: total number of donations to the team + participant_list: a list of the most recent participants + top_5_participant_list: a list of the top 5 team participants by + amount donated. + + Helper Variables: + output_folder: the folder that will contain the txt files for the user + (comes in via the init function from the participant) + currency_symbol: for formatting text (comes in via init function) + participant_calculation_dict: dictionary holding output for txt files: + - Team_Top5Participants: top 5 participants by donation amount + - Team_Top5ParticipantsHorizontal: same, but horizontal + - Team_TopParticipantNameAmnt: Top participant and amount + """ # urls self.team_url = f"https://www.extra-life.org/api/teams/{team_ID}" self.team_participant_url = f"https://www.extra-life.org/api/teams/{team_ID}/participants" @@ -20,6 +41,7 @@ def __init__(self, team_ID, folder, currency_symbol): def get_team_json(self): """Get team info from JSON api.""" + # need to debug to keep program from exiting if it can't read the URL self.team_json = extralife_IO.get_JSON(self.team_url) self.team_goal = self.team_json["fundraisingGoal"] self.team_captain = self.team_json["captainDisplayName"] @@ -86,7 +108,17 @@ def participant_run(self): class TeamParticipant(donor.Donor): - """Participant Attributes.""" + """Participant Attributes. + + Inherits from the donor class, but + over-rides the json_to_attributes function. + + API variables: + name: participant's name or Anonymous + amount: the sum of all donations by this participant + number_of_donations: number of all donations by this participant + image_url: the url of the participant's avatar image (not used) + """ def json_to_attributes(self, json): """Convert JSON to Team Participant attributes.""" From 118f4ac553f88b90442f9eb533d7be7b0fbeccf1 Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sat, 25 Jan 2020 11:43:47 -0500 Subject: [PATCH 10/16] Updated doc strings with variable list That said, I realized I still have some relics from before I realized donors and donations were tracked differently (or maybe the API didn't expose things that way before 2018/2019). Also, still have some unpythonic variable names. --- extralifedonations.py | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/extralifedonations.py b/extralifedonations.py index 5d13f49..6199734 100755 --- a/extralifedonations.py +++ b/extralifedonations.py @@ -15,6 +15,43 @@ class Participant: """Owns all the attributes under the participant API. Also owns the results of any calculated data. + + Participant.conf variables: + ExtraLifeID: the participant's extra life ID + textFolder: where the output txt files will be written on disk + CurrencySymbol: for the output txt files + donors_to_display: for txt files that display multiple donors + (or donations), the number of them that should + be written to the text file. + + API Variables: + participantURL: API info for participant + donorURL: donation API info (should be renamed to donationURL) + participant_donor_URL: API info for donors. Useful for calculating + top donor. + participantinfo: a dictionary holding data from participantURL: + - totalRaised: total money raised + - numDonations: number of donations + - averageDonation: this doesn't come from the API, + it's calculated in this class. + - goal: the participant's fundraising goal + myteam: An instantiation of a team class for the participant's team. + donationlist: a list of Donation class ojects made of donations to + this participant + + Helper Variables: + donorcalcs: a dictionary holding values for txt ouput: + - LastDonationNameAmnt: most recent donation, + donor name, amount of donation + - TopDonorNameAmnt: top donor name and sum of donations + - lastNDonationNameAmts: based on value of donors_to_display + above, a list of the last N donor + names and donation amounts + - lastNDonationNameAmtsMessage: same with messages + - lastNDonationNameAmtsMessageHorizontal: same, but horizontal + - lastNDonationNameAmtsHorizontal: same, but no message + loop: set to true on init, it's there so that the GUI can stop the loop. + (if the GUI is being used. Otherwise, no big deal) """ def __init__(self, participant_conf): @@ -85,7 +122,8 @@ def get_donors(self): def _top_donor(self): """Return Top Donor from server. - Uses donor drive's sorting to get the top guy or gal.""" + Uses donor drive's sorting to get the top guy or gal. + """ top_donor_JSON = extralife_IO.get_JSON(self.participant_donor_URL, True) if top_donor_JSON == 0: From 894911e770ad79e4a341c3afbc591d4c212c8530 Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sat, 25 Jan 2020 20:51:18 -0500 Subject: [PATCH 11/16] flake8 compliance changes --- team.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/team.py b/team.py index 8baa5c6..aa37d97 100644 --- a/team.py +++ b/team.py @@ -1,12 +1,13 @@ """Contains classes pertaining to teams.""" -import extralife_IO import donor +import extralife_IO + class Team: """Hold Team Data.""" - def __init__(self, team_ID, folder, currency_symbol): + def __init__(self, team_id, folder, currency_symbol): """Set the team variables. API Variables: @@ -25,13 +26,15 @@ def __init__(self, team_ID, folder, currency_symbol): (comes in via the init function from the participant) currency_symbol: for formatting text (comes in via init function) participant_calculation_dict: dictionary holding output for txt files: - - Team_Top5Participants: top 5 participants by donation amount + - Team_Top5Participants: top 5 participants by + donation amount - Team_Top5ParticipantsHorizontal: same, but horizontal - Team_TopParticipantNameAmnt: Top participant and amount """ # urls - self.team_url = f"https://www.extra-life.org/api/teams/{team_ID}" - self.team_participant_url = f"https://www.extra-life.org/api/teams/{team_ID}/participants" + team_url_base = "https://www.extra-life.org/api/teams/" + self.team_url = f"{team_url_base}{team_id}" + self.team_participant_url = f"{team_url_base}{team_id}/participants" # misc self.output_folder = folder self.currency_symbol = currency_symbol From f296b817a7db5b13b3c41fb2d49bcbcca5ba7b69 Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sun, 26 Jan 2020 12:56:53 -0500 Subject: [PATCH 12/16] changing some variable and function scope Back when I first wrote this, I was very new to class-based programming in Python and gave some variables class-scope when they just needed function scope. --- extralifedonations.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/extralifedonations.py b/extralifedonations.py index 6199734..02aa6cb 100755 --- a/extralifedonations.py +++ b/extralifedonations.py @@ -80,7 +80,7 @@ def __init__(self, participant_conf): self.textFolder, self.CurrencySymbol) - def get_participant_JSON(self): + def _get_participant_JSON(self): """Get JSON data for participant information. Some values that I will want to track as @@ -88,25 +88,25 @@ def get_participant_JSON(self): go into the dictionary participantinfo in the way they'll be written to files. """ - self.participantJSON = extralife_IO.get_JSON(self.participantURL) - if self.participantJSON == 0: + participantJSON = extralife_IO.get_JSON(self.participantURL) + if participantJSON == 0: print("Couldn't access participant JSON.") else: - self.ParticipantTotalRaised = self.participantJSON['sumDonations'] - self.ParticipantNumDonations = self.participantJSON['numDonations'] + self.ParticipantTotalRaised = participantJSON['sumDonations'] + self.ParticipantNumDonations = participantJSON['numDonations'] try: self.averagedonation = self.ParticipantTotalRaised/self.ParticipantNumDonations except ZeroDivisionError: self.averagedonation = 0 - self.participantgoal = self.participantJSON['fundraisingGoal'] + self.participantgoal = participantJSON['fundraisingGoal'] # the dictionary: - self.participantinfo['totalRaised'] = self.CurrencySymbol+'{:.2f}'.format(self.participantJSON['sumDonations']) - self.participantinfo["numDonations"] = str(self.participantJSON['numDonations']) + self.participantinfo['totalRaised'] = self.CurrencySymbol+'{:.2f}'.format(participantJSON['sumDonations']) + self.participantinfo["numDonations"] = str(participantJSON['numDonations']) self.participantinfo["averageDonation"] = self.CurrencySymbol+'{:.2f}'.format(self.averagedonation) self.participantinfo["goal"] = self.CurrencySymbol+'{:.2f}'.format(self.participantgoal) - def get_donors(self): + def _get_donations(self): """Get the donations from the JSON and create the donation objects.""" self.donationlist = [] self.donorJSON = extralife_IO.get_JSON(self.donorURL) @@ -155,24 +155,24 @@ def run(self): the calculations methnods, and the methods to write to text files. """ - self.get_participant_JSON() + self._get_participant_JSON() number_of_dononations = self.ParticipantNumDonations self.write_text_files(self.participantinfo) - self.get_donors() + self._get_donations() if self.donationlist: self._donor_calculations() self.write_text_files(self.donorcalcs) if self.TeamID: self.myteam.team_run() while self.loop: - self.get_participant_JSON() + self._get_participant_JSON() self.write_text_files(self.participantinfo) if self.TeamID: self.myteam.participant_run() if self.ParticipantNumDonations > number_of_dononations: print("A new donor!") number_of_dononations = self.ParticipantNumDonations - self.get_donors() + self._get_donations() self._donor_calculations() self.write_text_files(self.donorcalcs) IPC.writeIPC(self.textFolder, "1") From 22f06149f24ced1417f116fa64ea00305269f739 Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sun, 26 Jan 2020 16:15:47 -0500 Subject: [PATCH 13/16] CI and PEP8 For some reason I'd added a file to the flake8 portion of CI that didn't make sense. So I removed that. The rest of the changes are PEP8 changes to the files. --- .github/workflows/linttest.yml | 4 +-- .github/workflows/linuxbuild.yml | 4 +-- .github/workflows/windowsbuild.yml | 4 +-- CONTRIBUTING.md | 16 ++++------- donation.py | 1 + extralife_IO.py | 3 +- extralifedonations.py | 44 +++++++++++++++--------------- 7 files changed, 37 insertions(+), 39 deletions(-) diff --git a/.github/workflows/linttest.yml b/.github/workflows/linttest.yml index bf94fdf..b32d66c 100644 --- a/.github/workflows/linttest.yml +++ b/.github/workflows/linttest.yml @@ -22,9 +22,9 @@ jobs: run: | pip install flake8 # stop the build if there are Python syntax errors or undefined names - flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py settings.py --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py settings.py --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics shell: bash - name: Test with pytest run: | diff --git a/.github/workflows/linuxbuild.yml b/.github/workflows/linuxbuild.yml index 420a853..756a1ac 100644 --- a/.github/workflows/linuxbuild.yml +++ b/.github/workflows/linuxbuild.yml @@ -21,9 +21,9 @@ jobs: run: | pip install flake8 # stop the build if there are Python syntax errors or undefined names - flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py settings.py --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py settings.py --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | pip install pytest diff --git a/.github/workflows/windowsbuild.yml b/.github/workflows/windowsbuild.yml index 6438a10..ba21934 100644 --- a/.github/workflows/windowsbuild.yml +++ b/.github/workflows/windowsbuild.yml @@ -21,9 +21,9 @@ jobs: run: | pip install flake8 # stop the build if there are Python syntax errors or undefined names - flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py settings.py --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py settings.py --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | pip install pytest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9587451..b94ee48 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,21 +2,17 @@ Master will always contain perfectly working code aligned with the latest releas If you wish to contribute, please do so off of the devel branch and file a pull request with your changes. If I have another branch pushed to Github that may temporarily be the branch to develop from. Check with me. -The code is up to PEP8 standards and I have written unit tests. The following files should be without errors on flake8's test suite (don't worry about >80 char errors): +I strive for the code to be PEP8 compliant. Run the following against your fork: -- donation.py +flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py --count --select=E9,F63,F7,F82 --show-source --statistics -- donor.py +If it fails, the CI will fail and the pull request will not be merged until it is fixed. -- extralifedonations.py +My preference is for the following to be error free (don't worry about line-length errors): -- extralife_IO.py +flake8 extralifedonations.py IPC.py readparticipantconf.py team.py donation.py donor.py extralife_IO.py --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics -- IPC.py - -- readparticipantconf.py - -- team.py +But even my code isn't (as of 26 Jan 2020) perfect against that flake8 run, yet. If that list above becomes out of date, the canonical list of files that need to pass flake8 can be found in the repo under .github/workflows/linttest.yml. diff --git a/donation.py b/donation.py index b85c836..cdea2e3 100644 --- a/donation.py +++ b/donation.py @@ -1,5 +1,6 @@ """A class to hold the Donation attributes and methods.""" + class Donation: """Donation Attributes. diff --git a/extralife_IO.py b/extralife_IO.py index 4a27510..e88c19a 100644 --- a/extralife_IO.py +++ b/extralife_IO.py @@ -1,8 +1,9 @@ """Holds all the file and internet input and output.""" import json -from urllib.request import Request, urlopen, HTTPError, URLError import ssl +from urllib.request import HTTPError, Request, URLError, urlopen + import xdgenvpy diff --git a/extralifedonations.py b/extralifedonations.py index 02aa6cb..7e5aa69 100755 --- a/extralifedonations.py +++ b/extralifedonations.py @@ -25,9 +25,9 @@ class Participant: be written to the text file. API Variables: - participantURL: API info for participant + participant_url: API info for participant donorURL: donation API info (should be renamed to donationURL) - participant_donor_URL: API info for donors. Useful for calculating + participant_donor_URL: API info for donors. Useful for calculating top donor. participantinfo: a dictionary holding data from participantURL: - totalRaised: total money raised @@ -36,7 +36,7 @@ class Participant: it's calculated in this class. - goal: the participant's fundraising goal myteam: An instantiation of a team class for the participant's team. - donationlist: a list of Donation class ojects made of donations to + donationlist: a list of Donation class ojects made of donations to this participant Helper Variables: @@ -50,7 +50,7 @@ class Participant: - lastNDonationNameAmtsMessage: same with messages - lastNDonationNameAmtsMessageHorizontal: same, but horizontal - lastNDonationNameAmtsHorizontal: same, but no message - loop: set to true on init, it's there so that the GUI can stop the loop. + loop: set to true on init, it's there so that the GUI can stop the loop. (if the GUI is being used. Otherwise, no big deal) """ @@ -60,7 +60,7 @@ def __init__(self, participant_conf): self.CurrencySymbol, self.TeamID, self.donors_to_display) = participant_conf.get_CLI_values() # urls - self.participantURL = f"https://www.extra-life.org/api/participants/{self.ExtraLifeID}" + self.participant_url = f"https://www.extra-life.org/api/participants/{self.ExtraLifeID}" self.donorURL = f"https://www.extra-life.org/api/participants/{self.ExtraLifeID}/donations" self.participant_donor_URL = f"https://www.extra-life.org/api/participants/{self.ExtraLifeID}/donors" # donor calculations @@ -88,48 +88,48 @@ def _get_participant_JSON(self): go into the dictionary participantinfo in the way they'll be written to files. """ - participantJSON = extralife_IO.get_JSON(self.participantURL) - if participantJSON == 0: + participant_json = extralife_IO.get_JSON(self.participant_url) + if participant_json == 0: print("Couldn't access participant JSON.") else: - self.ParticipantTotalRaised = participantJSON['sumDonations'] - self.ParticipantNumDonations = participantJSON['numDonations'] + self.ParticipantTotalRaised = participant_json['sumDonations'] + self.ParticipantNumDonations = participant_json['numDonations'] try: self.averagedonation = self.ParticipantTotalRaised/self.ParticipantNumDonations except ZeroDivisionError: self.averagedonation = 0 - self.participantgoal = participantJSON['fundraisingGoal'] + self.participantgoal = participant_json['fundraisingGoal'] # the dictionary: - self.participantinfo['totalRaised'] = self.CurrencySymbol+'{:.2f}'.format(participantJSON['sumDonations']) - self.participantinfo["numDonations"] = str(participantJSON['numDonations']) + self.participantinfo['totalRaised'] = self.CurrencySymbol+'{:.2f}'.format(participant_json['sumDonations']) + self.participantinfo["numDonations"] = str(participant_json['numDonations']) self.participantinfo["averageDonation"] = self.CurrencySymbol+'{:.2f}'.format(self.averagedonation) self.participantinfo["goal"] = self.CurrencySymbol+'{:.2f}'.format(self.participantgoal) def _get_donations(self): """Get the donations from the JSON and create the donation objects.""" self.donationlist = [] - self.donorJSON = extralife_IO.get_JSON(self.donorURL) - if self.donorJSON == 0: - print("couldn't access donor page") - elif len(self.donorJSON) == 0: + donation_json = extralife_IO.get_JSON(self.donorURL) + if donation_json == 0: + print("couldn't access donation page") + elif len(donation_json) == 0: print("No donors!") else: - self.donationlist = [donation.Donation(self.donorJSON[donor].get('displayName'), - self.donorJSON[donor].get('message'), - self.donorJSON[donor].get('amount')) for donor in range(0, len(self.donorJSON))] + self.donationlist = [donation.Donation(donation_json[donor].get('displayName'), + donation_json[donor].get('message'), + donation_json[donor].get('amount')) for donor in range(0, len(donation_json))] def _top_donor(self): """Return Top Donor from server. Uses donor drive's sorting to get the top guy or gal. """ - top_donor_JSON = extralife_IO.get_JSON(self.participant_donor_URL, + top_donor_json = extralife_IO.get_JSON(self.participant_donor_URL, True) - if top_donor_JSON == 0: + if top_donor_json == 0: print("Couldn't access top donor data") else: - top_donor = donor.Donor(top_donor_JSON[0]) + top_donor = donor.Donor(top_donor_json[0]) return extralife_IO.single_format(top_donor, False, self.CurrencySymbol) From 379142f76a9b28d095808cd96d1fca6065b24f5a Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Sun, 26 Jan 2020 20:31:12 -0500 Subject: [PATCH 14/16] Renamed unit test file and updated github CI files --- .github/workflows/linttest.yml | 2 +- .github/workflows/linuxbuild.yml | 2 +- .github/workflows/windowsbuild.yml | 2 +- extralifeunittests.py => test.py | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename extralifeunittests.py => test.py (100%) diff --git a/.github/workflows/linttest.yml b/.github/workflows/linttest.yml index b32d66c..2ecb65b 100644 --- a/.github/workflows/linttest.yml +++ b/.github/workflows/linttest.yml @@ -29,5 +29,5 @@ jobs: - name: Test with pytest run: | pip install pytest - pytest extralifeunittests.py + pytest test.py shell: bash diff --git a/.github/workflows/linuxbuild.yml b/.github/workflows/linuxbuild.yml index 756a1ac..99d21e0 100644 --- a/.github/workflows/linuxbuild.yml +++ b/.github/workflows/linuxbuild.yml @@ -27,7 +27,7 @@ jobs: - name: Test with pytest run: | pip install pytest - pytest extralifeunittests.py + pytest test.py - name: Build with Pyinstaller run: | pip install pyinstaller diff --git a/.github/workflows/windowsbuild.yml b/.github/workflows/windowsbuild.yml index ba21934..2a80691 100644 --- a/.github/workflows/windowsbuild.yml +++ b/.github/workflows/windowsbuild.yml @@ -27,7 +27,7 @@ jobs: - name: Test with pytest run: | pip install pytest - pytest extralifeunittests.py + pytest test.py - name: Build with Pyinstaller run: | pip install pyinstaller diff --git a/extralifeunittests.py b/test.py similarity index 100% rename from extralifeunittests.py rename to test.py From 6b81699bcbae02394a1fa01f02d825522263a8eb Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Tue, 4 Feb 2020 17:38:08 -0500 Subject: [PATCH 15/16] Final step to making team.py resilient I think this is the final area where things would fail if it couldn't get to the URL because of the new DDOS code. Of course, I realized recently I shouldn't be returning JSON OR a 0. Instead I should have it raise a custom exception and deal with that in team.py. For now, however, I think I'm OK with this hack. --- extralife_IO.py | 2 +- team.py | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/extralife_IO.py b/extralife_IO.py index e88c19a..c8b2218 100644 --- a/extralife_IO.py +++ b/extralife_IO.py @@ -35,7 +35,7 @@ def get_JSON(url, order_by_donations=False): and this is not an intermittent issue: please open an issue at: https://github.com/djotaku/ELDonationTracker""") - return 0 + return 0 # to be proper this should return an exemption except URLError: print(f"HTTP code: {payload.getcode()}") print(f""" Timed out while getting JSON. """) diff --git a/team.py b/team.py index aa37d97..68d5085 100644 --- a/team.py +++ b/team.py @@ -46,15 +46,18 @@ def get_team_json(self): """Get team info from JSON api.""" # need to debug to keep program from exiting if it can't read the URL self.team_json = extralife_IO.get_JSON(self.team_url) - self.team_goal = self.team_json["fundraisingGoal"] - self.team_captain = self.team_json["captainDisplayName"] - self.total_raised = self.team_json["sumDonations"] - self.num_donations = self.team_json["numDonations"] - # dictionary - self.team_info["Team_goal"] = f"{self.currency_symbol}{self.team_goal:,.2f}" - self.team_info["Team_captain"] = f"{self.team_captain}" - self.team_info["Team_totalRaised"] = f"{self.currency_symbol}{self.total_raised:,.2f}" - self.team_info["Team_numDonations"] = f"{self.num_donations}" + if self.team_json == 0: + print("Could not get team JSON") + else: + self.team_goal = self.team_json["fundraisingGoal"] + self.team_captain = self.team_json["captainDisplayName"] + self.total_raised = self.team_json["sumDonations"] + self.num_donations = self.team_json["numDonations"] + # dictionary + self.team_info["Team_goal"] = f"{self.currency_symbol}{self.team_goal:,.2f}" + self.team_info["Team_captain"] = f"{self.team_captain}" + self.team_info["Team_totalRaised"] = f"{self.currency_symbol}{self.total_raised:,.2f}" + self.team_info["Team_numDonations"] = f"{self.num_donations}" def get_participants(self): """Get team participants.""" From 0d76260aaca759f5c923ed84cff1baf2612c8203 Mon Sep 17 00:00:00 2001 From: Eric Mesa Date: Tue, 4 Feb 2020 17:41:41 -0500 Subject: [PATCH 16/16] lint test badge For the devel branch. This time made from Github's badge generator. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index afc58c7..c565541 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![Lint_Test](https://github.com/djotaku/ELDonationTracker/workflows/Lint_Test/badge.svg?branch=devel) + Would you like to be able to update your donations in real-time during a Live Stream or while recording a Let's Play as in the following screenshots? ![Updates while in-game](https://github.com/djotaku/ELDonationTracker/blob/devel/screenshots/IngameUpdates.png)