Skip to content

Commit

Permalink
Refactor sports.py to use NamedTuple, fix mypy errors
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tianyizheng02 committed Aug 18, 2024
1 parent 6d6b1ef commit 639d70e
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 48 deletions.
70 changes: 40 additions & 30 deletions pittapi/sports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand All @@ -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:
Expand All @@ -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
30 changes: 12 additions & 18 deletions tests/sports_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 639d70e

Please sign in to comment.