From bb4eff2d78f4ef7c4aa3c85c4f31445c6ff70488 Mon Sep 17 00:00:00 2001 From: Harsha Mandai Date: Sun, 9 Sep 2018 14:45:33 +0530 Subject: [PATCH 1/9] making logger as separate entity instead of getting instance in every class --- scorer/app.py | 16 ++-------------- scorer/fetch_scores.py | 37 ++++++++++++++++++++++++++----------- scorer/logger.py | 15 +++++++++++++++ scorer/notification.py | 6 ++---- scorer/ui.py | 10 +++++----- 5 files changed, 50 insertions(+), 34 deletions(-) create mode 100644 scorer/logger.py diff --git a/scorer/app.py b/scorer/app.py index 77b4fb2..b2cfd2d 100644 --- a/scorer/app.py +++ b/scorer/app.py @@ -1,23 +1,11 @@ -import logging from time import sleep - +import scorer.logger as logger import scorer.fetch_scores as fs import scorer.notification as notify from scorer.system import exitApp from scorer.ui import getUserInput -logger = logging.getLogger("scorer.app") -logger.setLevel(logging.DEBUG) -fh = logging.FileHandler("scorer.log") -fh.setLevel(logging.DEBUG) -ch = logging.StreamHandler() -ch.setLevel(logging.ERROR) -formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -\ - %(message)s') -fh.setFormatter(formatter) -ch.setFormatter(formatter) -logger.addHandler(fh) -logger.addHandler(ch) +logger = logger.get_logger('cricket-scores-api') NO_LIVE_MATCHES = "No Match in progress" SLEEP_INTERVAL = 60 diff --git a/scorer/fetch_scores.py b/scorer/fetch_scores.py index bbb2e4b..a75aead 100644 --- a/scorer/fetch_scores.py +++ b/scorer/fetch_scores.py @@ -1,10 +1,10 @@ -import logging +import scorer.logger as logger import re - import requests from bs4 import BeautifulSoup +from scorer.system import exitApp -logger = logging.getLogger('scorer.fetch_scores') +logger = logger.get_logger('scorer.fetch_scores') WON_STATUS = "won by" @@ -30,30 +30,40 @@ def getPlayingTeamNames(jsonurl): :return: teams playing """ logger.info("Url to get the json from {}".format(jsonurl)) - r = requests.get(jsonurl) + try: + r = requests.get(jsonurl) + except: + logger.error("not able to reach the site to get the match info!!") + exitApp() + jsonData = r.json() playingTeams = {team.get("team_id"): team.get("team_name\ ") for team in jsonData.get("team")} - logging.debug("playingTeams: {}".format(playingTeams)) + logger.debug("playingTeams: {}".format(playingTeams)) return playingTeams def getLastestScore(jsonurl, playingTeams): logger.info("Entry Point for getLastestScore") - logger.debug("Url to get the latest json is: {}" . format(jsonurl)) - r = requests.get(jsonurl) + logger.debug("Url to get the latest json is: {}".format(jsonurl)) + try: + r = requests.get(jsonurl) + except: + logger.error("not able to reach the site to get the match info!!") + exitApp() + jsonData = r.json() matchStatus = jsonData.get("live").get("status") logger.info("matchStatus: {}".format(matchStatus)) titleToDisplay = matchStatus scoreToDisplay = "" # Check if match Started - if(not jsonData.get("live").get("innings")): + if (not jsonData.get("live").get("innings")): logger.info("Match not started") return (titleToDisplay, scoreToDisplay) # Check if match over - if(WON_STATUS in matchStatus): + if (WON_STATUS in matchStatus): logger.info("Match over") return (titleToDisplay, scoreToDisplay) innings = jsonData.get("live").get("innings") @@ -105,9 +115,14 @@ def findMatchesAvailable(url="http://static.cricinfo.com/rss/livescores.xml"): :return: a tuple of xml and matches. """ logger.info("Entry point for findMatchesAvailable") - r = requests.get(url) + try: + r = requests.get(url) + except: + logger.error("not able to reach the site to get the match info!!") + exitApp() + soup = BeautifulSoup(r.text) xml = soup.find_all("item") matches = map(lambda item: re.sub(r'\s+', " ", re.sub('\ - [^A-Za-z ]+', '', item.title.text)), xml) + [^A-Za-z ]+', '', item.title.text)), xml) return (xml, matches) diff --git a/scorer/logger.py b/scorer/logger.py new file mode 100644 index 0000000..2e7953f --- /dev/null +++ b/scorer/logger.py @@ -0,0 +1,15 @@ +import sys +import logging + +def get_logger(name, level=logging.INFO): + logger = logging.getLogger(name) + logger.setLevel(level) + if logger.handlers: + pass + else: + ch = logging.StreamHandler(sys.stderr) + ch.setLevel(level) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + ch.setFormatter(formatter) + logger.addHandler(ch) + return logger \ No newline at end of file diff --git a/scorer/notification.py b/scorer/notification.py index 362d99f..0828896 100644 --- a/scorer/notification.py +++ b/scorer/notification.py @@ -1,10 +1,8 @@ -import logging - +import scorer.logger as logger import pynotify - from scorer.system import exitApp -logger = logging.getLogger('scorer.notification') +logger = logger.getLogger('scorer.notification') def popUpMessage(title, message): diff --git a/scorer/ui.py b/scorer/ui.py index 82f8bdf..b8dcfd0 100644 --- a/scorer/ui.py +++ b/scorer/ui.py @@ -1,8 +1,8 @@ from curses import wrapper import curses -import logging +import scorer.logger as logger -logger = logging.getLogger('scorer.ui') +logger = logger.getLogger('scorer.ui') def printGames(stdscr, matches, selected): @@ -26,15 +26,15 @@ def main(stdscr, matches): printGames(stdscr, matches, selected) event = stdscr.getch() if event == ord("\n"): - logging.info("Enter key pressed") + logger.info("Enter key pressed") return selected elif event == curses.KEY_UP: - logging.info("Up key pressed") + logger.info("Up key pressed") if selected != 0: selected -= 1 printGames(stdscr, matches, selected) elif event == curses.KEY_DOWN: - logging.info("Down key pressed") + logger.info("Down key pressed") if selected != len(matches) - 1: selected += 1 printGames(stdscr, matches, selected) From 8e5bdd4ddf127cbe27751b8d831d78b44f3577f0 Mon Sep 17 00:00:00 2001 From: harsham4026 <42734799+harsham4026@users.noreply.github.com> Date: Sun, 9 Sep 2018 15:37:28 +0530 Subject: [PATCH 2/9] Update notification.py --- scorer/notification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scorer/notification.py b/scorer/notification.py index 0828896..c0a25cd 100644 --- a/scorer/notification.py +++ b/scorer/notification.py @@ -2,7 +2,7 @@ import pynotify from scorer.system import exitApp -logger = logger.getLogger('scorer.notification') +logger = logger.get_logger('scorer.notification') def popUpMessage(title, message): From 03179bafb6dcc0c74fcf585a9bd2789af8503723 Mon Sep 17 00:00:00 2001 From: harsham4026 <42734799+harsham4026@users.noreply.github.com> Date: Sun, 9 Sep 2018 15:38:35 +0530 Subject: [PATCH 3/9] Update notification.py --- scorer/notification.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/scorer/notification.py b/scorer/notification.py index c0a25cd..b2ce552 100644 --- a/scorer/notification.py +++ b/scorer/notification.py @@ -6,13 +6,10 @@ def popUpMessage(title, message): - logger.info("Initializing pynotify") try: - pynotify.init("Scorer") - pynotify.Notification(title, message, "dialog-information").show() + command = ['notify-send', title, message] + pipe = sp.call(command) + logger.info("pop up message sent!!") except Exception as e: - logger.error("Error initializing pynotify") logger.debug(e) - logger.info("Unable to initialize pynotify: Connection Refused") - logger.info("Quitting the app") exitApp() From bd6c2a4b655ae3ef424d3e246ca84634981aa68f Mon Sep 17 00:00:00 2001 From: harsham4026 <42734799+harsham4026@users.noreply.github.com> Date: Sun, 9 Sep 2018 15:39:20 +0530 Subject: [PATCH 4/9] Update notification.py --- scorer/notification.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scorer/notification.py b/scorer/notification.py index b2ce552..9674178 100644 --- a/scorer/notification.py +++ b/scorer/notification.py @@ -1,6 +1,7 @@ import scorer.logger as logger -import pynotify +from sys import version_info from scorer.system import exitApp +import subprocess as sp logger = logger.get_logger('scorer.notification') From e7cd9fdf47c6ce38e6acb0f44a6eef790dbcb495 Mon Sep 17 00:00:00 2001 From: Harsha Mandai Date: Sun, 9 Sep 2018 16:01:38 +0530 Subject: [PATCH 5/9] added config.json to store all the variables to make the code more managable --- scorer/app.py | 5 +++-- scorer/config.json | 5 +++++ scorer/config_reader.py | 21 +++++++++++++++++++++ scorer/fetch_scores.py | 3 ++- scorer/ui.py | 2 +- 5 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 scorer/config.json create mode 100644 scorer/config_reader.py diff --git a/scorer/app.py b/scorer/app.py index b2cfd2d..a8eb8d9 100644 --- a/scorer/app.py +++ b/scorer/app.py @@ -4,11 +4,12 @@ import scorer.notification as notify from scorer.system import exitApp from scorer.ui import getUserInput +from scorer import config_reader logger = logger.get_logger('cricket-scores-api') -NO_LIVE_MATCHES = "No Match in progress" -SLEEP_INTERVAL = 60 +NO_LIVE_MATCHES = config_reader.NO_LIVE_MATCHES +SLEEP_INTERVAL = config_reader.SLEEP_INTERVAL def main(): diff --git a/scorer/config.json b/scorer/config.json new file mode 100644 index 0000000..88a23dd --- /dev/null +++ b/scorer/config.json @@ -0,0 +1,5 @@ +{ + "NO_LIVE_MATCHES": "No Match in progress", + "SLEEP_INTERVAL": 60, + "WON_STATUS": "won by" +} \ No newline at end of file diff --git a/scorer/config_reader.py b/scorer/config_reader.py new file mode 100644 index 0000000..84fc7ff --- /dev/null +++ b/scorer/config_reader.py @@ -0,0 +1,21 @@ +import json +import scorer.logger as logger +from scorer.system import exitApp + +logger = logger.get_logger("config-reader") + + +def read_json(file_path): + try: + with open(file_path.lower()) as json_data: + data = json.load(json_data) + return data + except FileNotFoundError as e: + logger.error("config file not found!!") + exitApp() + + +config_data = read_json("../scorer/config.json") +NO_LIVE_MATCHES = config_data["NO_LIVE_MATCHES"] +SLEEP_INTERVAL = config_data["SLEEP_INTERVAL"] +WON_STATUS = config_data["WON_STATUS"] diff --git a/scorer/fetch_scores.py b/scorer/fetch_scores.py index a75aead..77798fa 100644 --- a/scorer/fetch_scores.py +++ b/scorer/fetch_scores.py @@ -3,10 +3,11 @@ import requests from bs4 import BeautifulSoup from scorer.system import exitApp +from scorer import config_reader logger = logger.get_logger('scorer.fetch_scores') -WON_STATUS = "won by" +WON_STATUS = config_reader.WON_STATUS def getJsonURL(matchId): diff --git a/scorer/ui.py b/scorer/ui.py index b8dcfd0..3390dff 100644 --- a/scorer/ui.py +++ b/scorer/ui.py @@ -2,7 +2,7 @@ import curses import scorer.logger as logger -logger = logger.getLogger('scorer.ui') +logger = logger.get_logger('scorer.ui') def printGames(stdscr, matches, selected): From d8ba16c4585ea0fa3aea70f752f4fb6f424e64ad Mon Sep 17 00:00:00 2001 From: Harsha Mandai Date: Sun, 9 Sep 2018 16:11:51 +0530 Subject: [PATCH 6/9] changes variables naming convention --- scorer/app.py | 8 ++++---- scorer/fetch_scores.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scorer/app.py b/scorer/app.py index a8eb8d9..3d20ede 100644 --- a/scorer/app.py +++ b/scorer/app.py @@ -8,15 +8,15 @@ logger = logger.get_logger('cricket-scores-api') -NO_LIVE_MATCHES = config_reader.NO_LIVE_MATCHES -SLEEP_INTERVAL = config_reader.SLEEP_INTERVAL +no_live_matches = config_reader.NO_LIVE_MATCHES +sleep_interval = config_reader.SLEEP_INTERVAL def main(): while True: logger.debug("Getting the xml and matches list") xml, matches = fs.findMatchesAvailable() - if matches[0] == NO_LIVE_MATCHES: + if matches[0] == no_live_matches: print "No Live matches are available now:" exitApp() matches.append("Quit the scorer app") @@ -39,7 +39,7 @@ def main(): logger.debug("Sending notification for: title:{} score:\ {}".format(title, score)) notify.popUpMessage(title, score) - sleep(SLEEP_INTERVAL) + sleep(sleep_interval) except KeyboardInterrupt: break diff --git a/scorer/fetch_scores.py b/scorer/fetch_scores.py index 77798fa..30233f8 100644 --- a/scorer/fetch_scores.py +++ b/scorer/fetch_scores.py @@ -7,7 +7,7 @@ logger = logger.get_logger('scorer.fetch_scores') -WON_STATUS = config_reader.WON_STATUS +won_status = config_reader.WON_STATUS def getJsonURL(matchId): @@ -64,7 +64,7 @@ def getLastestScore(jsonurl, playingTeams): return (titleToDisplay, scoreToDisplay) # Check if match over - if (WON_STATUS in matchStatus): + if (won_status in matchStatus): logger.info("Match over") return (titleToDisplay, scoreToDisplay) innings = jsonData.get("live").get("innings") From b39a4da64d6a7647e0fdce339d9973061d231405 Mon Sep 17 00:00:00 2001 From: Harsha Mandai Date: Mon, 10 Sep 2018 21:46:02 +0530 Subject: [PATCH 7/9] test cases for config_reader --- test/test_config_reader.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/test_config_reader.py diff --git a/test/test_config_reader.py b/test/test_config_reader.py new file mode 100644 index 0000000..92ef0fb --- /dev/null +++ b/test/test_config_reader.py @@ -0,0 +1,25 @@ +# export the PYTHONPATH till the project folder as I added another folder test for writing the test cases and Python +# wouldn't be able to recognize the other packages. +# export PYTHONPATH=$PYTHONPATH:/Users/hmandadi/Documents/harsh/scorer.py/ +# Run test case from scorer/ folder +# python -m unittest discover ../test/ + +import unittest +from mock import MagicMock +import scorer.config_reader as config_reader + + +class ConfigReaderTestCases(unittest.TestCase): + + def test_wrong_json_path(self): + config_reader.read_json = MagicMock(return_value=str('No such file or directory')) + self.assertEqual(config_reader.read_json('/path/not/found/'), 'No such file or directory') + + def test_json_file_unable_to_open(self): + config_reader.read_json = MagicMock(return_value=str('No JSON object could be decoded')) + self.assertEqual(config_reader.read_json('/filenotabletoberead/sample-template.abc'), + 'No JSON object could be decoded') + + +if __name__ == '__main__': + unittest.main() From 8b4f97a83d7d94300f1880b3081fd437d5a161cb Mon Sep 17 00:00:00 2001 From: harsham4026 <42734799+harsham4026@users.noreply.github.com> Date: Mon, 10 Sep 2018 22:14:39 +0530 Subject: [PATCH 8/9] removed the pynotify dependency and added mock library for test cases --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 39f223f..4a346f3 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ def readme(): install_requires=[ 'requests', 'beautifulsoup4', - 'pynotify' + 'mock' ], entry_points={ 'console_scripts': [ From e5a876f73f93dadc92ca573faf90157085b3c26d Mon Sep 17 00:00:00 2001 From: harsham4026 <42734799+harsham4026@users.noreply.github.com> Date: Mon, 10 Sep 2018 22:15:33 +0530 Subject: [PATCH 9/9] added mock library as requirement --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2d9b3ce..6e69373 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ A simple python script to fetch cricket scores and send notifications. * beautifulSoup4 * requests * python2 only +* mock ## Installation ## ``sudo python setup.py install``