From 639d70e12fe1523a7dcf6cecea04ae21edd2edf7 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 17 Aug 2024 23:41:38 -0700 Subject: [PATCH 1/2] Refactor sports.py to use NamedTuple, fix mypy errors Refactor sports.py to store API output using NamedTuples rather than plain Python dicts. This makes the output more user-friendly, less error-prone, and easier to type-hint. --- pittapi/sports.py | 70 +++++++++++++++++++++++++------------------- tests/sports_test.py | 30 ++++++++----------- 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/pittapi/sports.py b/pittapi/sports.py index e70f8a7..feef5a5 100644 --- a/pittapi/sports.py +++ b/pittapi/sports.py @@ -19,24 +19,35 @@ import requests +from typing import Any, NamedTuple + +JSON = dict[str, Any] + FOOTBALL_URL = "http://site.api.espn.com/apis/site/v2/sports/football/college-football/teams/pitt" MENS_BASKETBALL_URL = "http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/teams/pittsburgh" +class GameInfo(NamedTuple): + timestamp: str | None = None + opponent: dict[str, str] | None = None + home_away: str | None = None + location: dict[str, str] | None = None + status: str | None = None + + def get_mens_basketball_record() -> str: """returns the current record of the men's basketball team""" basketball_data = _get_mens_basketball_data() try: - record_summary = basketball_data["team"]["record"]["items"][0]["summary"] - + record_summary: str = basketball_data["team"]["record"]["items"][0]["summary"] except KeyError: record_summary = "There's no record right now." return record_summary -def get_next_mens_basketball_game() -> dict: +def get_next_mens_basketball_game() -> GameInfo: """returns a dict containing details of the next scheduled men's basketball game.""" basketball_data = _get_mens_basketball_data() next_game = None @@ -55,30 +66,29 @@ def get_next_mens_basketball_game() -> dict: else: opponent = next_game["competitions"][0]["competitors"][1] homeaway = next_game["competitions"][0]["competitors"][1]["homeAway"] - return { - "timestamp": next_game["date"], - "opponent": { + return GameInfo( + timestamp=next_game["date"], + opponent={ "id": opponent["team"]["id"], "school": opponent["team"]["nickname"], "name": opponent["team"]["displayName"], }, - "home_away": homeaway, - "location": { + home_away=homeaway, + location={ "full_name": next_game["competitions"][0]["venue"]["fullName"], "address": next_game["competitions"][0]["venue"]["address"], }, - "status": status, - } + status=status, + ) except IndexError: # IndexError occurs when a next game on the schedule is not present - return {"status": "NO_GAME_SCHEDULED"} + return GameInfo(status="NO_GAME_SCHEDULED") def get_mens_basketball_standings() -> str: """returns a string describing the placement of the men's basketball team. eg: '14th in ACC'""" basketball_data = _get_mens_basketball_data() - - return_value = basketball_data["team"]["standingSummary"] + return_value: str = basketball_data["team"]["standingSummary"] return return_value @@ -87,15 +97,14 @@ def get_football_record() -> str: football_data = _get_football_data() try: - record_summary = football_data["team"]["record"]["items"][0]["summary"] - + record_summary: str = football_data["team"]["record"]["items"][0]["summary"] except KeyError: record_summary = "There's no record right now." return record_summary -def get_next_football_game() -> dict: +def get_next_football_game() -> GameInfo: football_data = _get_football_data() next_game = None try: @@ -113,36 +122,37 @@ def get_next_football_game() -> dict: else: opponent = next_game["competitions"][0]["competitors"][0] homeaway = next_game["competitions"][0]["competitors"][1]["homeAway"] - return { - "timestamp": next_game["date"], - "opponent": { + return GameInfo( + timestamp=next_game["date"], + opponent={ "id": opponent["team"]["id"], "school": opponent["team"]["nickname"], "name": opponent["team"]["displayName"], }, - "home_away": homeaway, - "location": { + home_away=homeaway, + location={ "full_name": next_game["competitions"][0]["venue"]["fullName"], "address": next_game["competitions"][0]["venue"]["address"], }, - "status": status, - } + status=status, + ) except IndexError: # IndexError occurs when a next game on the schedule is not present - return {"status": "NO_GAME_SCHEDULED"} + return GameInfo(status="NO_GAME_SCHEDULED") def get_football_standings() -> str: """returns a string describing the placement of the football team. eg: '14th in ACC'""" football_data = _get_football_data() - - return_value = football_data["team"]["standingSummary"] + return_value: str = football_data["team"]["standingSummary"] return return_value -def _get_mens_basketball_data() -> dict: - return requests.get(MENS_BASKETBALL_URL).json() +def _get_mens_basketball_data() -> JSON: + json_data: JSON = requests.get(MENS_BASKETBALL_URL).json() + return json_data -def _get_football_data() -> dict: - return requests.get(FOOTBALL_URL).json() +def _get_football_data() -> JSON: + json_data: JSON = requests.get(FOOTBALL_URL).json() + return json_data diff --git a/tests/sports_test.py b/tests/sports_test.py index 979f822..b88bd22 100644 --- a/tests/sports_test.py +++ b/tests/sports_test.py @@ -170,36 +170,30 @@ def test_get_football_standings(self): def test_get_next_mens_basketball_game(self): next_game_details = sports.get_next_mens_basketball_game() - self.assertIn("timestamp", next_game_details) - self.assertIn("opponent", next_game_details) - self.assertIn("home_away", next_game_details) - self.assertIn("location", next_game_details) - self.assertIn("status", next_game_details) - self.assertNotEqual("NO_GAME_SCHEDULED", next_game_details["status"]) + self.assertNotEqual("NO_GAME_SCHEDULED", next_game_details.status) def test_get_next_football_game(self): next_game_details = sports.get_next_football_game() - self.assertIn("timestamp", next_game_details) - self.assertIn("opponent", next_game_details) - self.assertIn("home_away", next_game_details) - self.assertIn("location", next_game_details) - self.assertIn("status", next_game_details) - self.assertNotEqual("NO_GAME_SCHEDULED", next_game_details["status"]) + self.assertNotEqual("NO_GAME_SCHEDULED", next_game_details.status) def test_get_next_mens_basketball_game_offseason(self): offseason_data = {"team": {"nextEvent": []}} sports._get_mens_basketball_data = MagicMock(return_value=offseason_data) next_game_details = sports.get_next_mens_basketball_game() - self.assertEqual(1, len(next_game_details)) - self.assertIn("status", next_game_details) - self.assertEqual("NO_GAME_SCHEDULED", next_game_details["status"]) + self.assertIsNone(next_game_details.timestamp) + self.assertIsNone(next_game_details.opponent) + self.assertIsNone(next_game_details.home_away) + self.assertIsNone(next_game_details.location) + self.assertEqual("NO_GAME_SCHEDULED", next_game_details.status) def test_get_next_football_game_offseason(self): offseason_data = {"team": {"nextEvent": []}} sports._get_football_data = MagicMock(return_value=offseason_data) next_game_details = sports.get_next_football_game() - self.assertEqual(1, len(next_game_details)) - self.assertIn("status", next_game_details) - self.assertEqual("NO_GAME_SCHEDULED", next_game_details["status"]) + self.assertIsNone(next_game_details.timestamp) + self.assertIsNone(next_game_details.opponent) + self.assertIsNone(next_game_details.home_away) + self.assertIsNone(next_game_details.location) + self.assertEqual("NO_GAME_SCHEDULED", next_game_details.status) From 32c88e96301b16bb0c4f4b46c772001fa1814a03 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 17 Aug 2024 23:51:05 -0700 Subject: [PATCH 2/2] Add from __future__ import annotations for Python 3.9 support --- pittapi/sports.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pittapi/sports.py b/pittapi/sports.py index feef5a5..4431336 100644 --- a/pittapi/sports.py +++ b/pittapi/sports.py @@ -17,6 +17,8 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ +from __future__ import annotations + import requests from typing import Any, NamedTuple