diff --git a/nhlpy/api/games.py b/nhlpy/api/games.py index 26ac96f..af1fa0a 100644 --- a/nhlpy/api/games.py +++ b/nhlpy/api/games.py @@ -2,6 +2,7 @@ from typing import Union, List from nhlpy.api import BaseNHLAPIClient + class Games(BaseNHLAPIClient): """ This class is used to access the NHL API for game data. @@ -58,17 +59,20 @@ def get_game_live_feed(self, game_id: Union[str, int]) -> dict: """ return self._get(resource=f"game/{game_id}/feed/live").json() - def get_game_live_feed_diff_after_timestamp(self, game_id: Union[str, int], timestamp: str) -> dict: + def get_game_live_feed_diff_after_timestamp( + self, game_id: Union[str, int], timestamp: str + ) -> dict: """ Returns the difference in the live feed game data, from since the given timestamp: param. :param game_id: :param timestamp: :return: """ - warnings.warn("This endpoint is still experimental and may not work as expected") + warnings.warn( + "This endpoint is still experimental and may not work as expected" + ) return self._get(resource=f"game/{game_id}/feed/live/diffPath").json() - def get_game_boxscore(self, game_id: Union[str, int]) -> List[dict]: """ Less detail than get_game_live_feed() but still a large response, contains lots of information about @@ -101,4 +105,4 @@ def get_game_content(self, game_id: Union[str, int]) -> dict: :param game_id: :return: """ - return self._get(resource=f"game/{game_id}/content").json() \ No newline at end of file + return self._get(resource=f"game/{game_id}/content").json() diff --git a/nhlpy/api/helpers.py b/nhlpy/api/helpers.py index cc24d38..8cf29fc 100644 --- a/nhlpy/api/helpers.py +++ b/nhlpy/api/helpers.py @@ -1,6 +1,54 @@ -from typing import List +import warnings +from typing import List, Optional from nhlpy.api.standings import Standings +from nhlpy.api.schedule import Schedule +from nhlpy.api.games import Games + + +def _parse_team_specific_game_data( + game_item: dict, team_side: str, game_boxscore_data: dict +) -> None: + """ + Parser helper method + :param team_side: + :param game_boxscore_data: + :return: + """ + game_item[f"{team_side}_pim"] = game_boxscore_data[team_side]["teamStats"][ + "teamSkaterStats" + ]["pim"] + game_item[f"{team_side}_shots"] = game_boxscore_data[team_side]["teamStats"][ + "teamSkaterStats" + ]["shots"] + game_item[f"{team_side}_pp_percent"] = float( + game_boxscore_data[team_side]["teamStats"]["teamSkaterStats"][ + "powerPlayPercentage" + ] + ) + game_item[f"{team_side}_pp_goals"] = game_boxscore_data[team_side]["teamStats"][ + "teamSkaterStats" + ]["powerPlayGoals"] + game_item[f"{team_side}_pp_opps"] = game_boxscore_data[team_side]["teamStats"][ + "teamSkaterStats" + ]["powerPlayOpportunities"] + game_item[f"{team_side}_fo_win_percent"] = float( + game_boxscore_data[team_side]["teamStats"]["teamSkaterStats"][ + "faceOffWinPercentage" + ] + ) + game_item[f"{team_side}_shots_blocked"] = game_boxscore_data[team_side][ + "teamStats" + ]["teamSkaterStats"]["blocked"] + game_item[f"{team_side}_shots_takeaways"] = game_boxscore_data[team_side][ + "teamStats" + ]["teamSkaterStats"]["takeaways"] + game_item[f"{team_side}_shots_giveaways"] = game_boxscore_data[team_side][ + "teamStats" + ]["teamSkaterStats"]["giveaways"] + game_item[f"{team_side}_shots_hits"] = game_boxscore_data[team_side]["teamStats"][ + "teamSkaterStats" + ]["hits"] class Helpers: @@ -61,3 +109,55 @@ def league_standings(self, season: str, py_exp_ex: float = 2.37) -> List[dict]: team["expected_wins"] = team["py_expectation"] * team["games_played"] teams.append(team) return teams + + def get_all_game_results( + self, + season: str, + detailed_game_data: bool = False, + game_type: str = "R", + team_ids: Optional[List[int]] = None, + ) -> List[dict]: + """ + + :param season: + :param detailed_game_data: If True, will return the full game data for each game. If False, will only return simple game data. + :param game_type: + :param team_ids: + :return: + """ + warnings.warn( + "This endpoint will query the schedule API to get the games, and then sequentially query the boxscore API" + " for each game. This is a slow endpoint, do not call this while in a loop, or multiple times in succession" + ) + games = [] + game_dates = Schedule().get_schedule( + season=season, game_type=game_type, team_ids=team_ids + )["dates"] + for d in game_dates: + date = d["date"] + for game in d["games"]: + game_data = { + "date": date, + "home_score": game["teams"]["home"]["score"], + "away_score": game["teams"]["away"]["score"], + "game_id": game["gamePk"], + "game_type": game["gameType"], + "away_id": game["teams"]["away"]["team"]["id"], + "away_name": game["teams"]["away"]["team"]["name"], + "home_id": game["teams"]["home"]["team"]["id"], + "home_name": game["teams"]["home"]["team"]["name"], + } + games.append(game_data) + + if detailed_game_data: + game_client = Games() + for game in games: + data = game_client.get_game_boxscore(game_id=game["game_id"])["teams"] + _parse_team_specific_game_data( + game_item=game, team_side="away", game_boxscore_data=data + ) + _parse_team_specific_game_data( + game_item=game, team_side="home", game_boxscore_data=data + ) + + return games diff --git a/nhlpy/api/players.py b/nhlpy/api/players.py index 863fc25..5337e09 100644 --- a/nhlpy/api/players.py +++ b/nhlpy/api/players.py @@ -8,7 +8,7 @@ def get_player(self, person_id: str) -> dict: :param person_id: :return: """ - return self._get(resource=f"people/{person_id}").json() + return self._get(resource=f"people/{person_id}").json()["people"] def get_player_stats( self, person_id: str, season: str = None, stat_type: str = "statsSingleSeason" @@ -32,7 +32,7 @@ def get_player_stats( query = f"stats={stat_type}" if stat_type else "" return self._get( resource=f"people/{person_id}/stats?season={season}&{query}" - ).json() + ).json()["stats"] def get_player_stat_types(self) -> dict: """ diff --git a/nhlpy/api/schedule.py b/nhlpy/api/schedule.py index 15f2f2d..909703a 100644 --- a/nhlpy/api/schedule.py +++ b/nhlpy/api/schedule.py @@ -1,14 +1,26 @@ -from typing import Optional +from typing import Optional, List from nhlpy.api import BaseNHLAPIClient class Schedule(BaseNHLAPIClient): - def get_schedule(self, season: Optional[str] = None) -> dict: + def get_schedule( + self, season: str, game_type: str = "R", team_ids: Optional[List[int]] = None + ) -> dict: """ - Returns a list of all games for the current season if no season is supplied. Otherwise returns the - schedule for the season defined in the season: param. - :param season: Season in format of 20202021 - :return: + + :param season: str - Season in format of 20202021 + :param game_type: str - Game type, R (default) for regular season, P for playoffs, PR for preseason, A for all-star + :param team_ids: List[int] - List of team ids + + + example: c.schedule.get_schedule(season="20222023", team_ids=[7], game_type='PR') + + :return: dict """ - query = f"?season={season}" if season else "" - return self._get(resource=f"schedule{query}").json() + q: str = f"?season={season}" + team_q: str = ( + f"&teamId={','.join(str(t) for t in team_ids)}" if team_ids else "" + ) + type_q: str = f"&gameType={game_type}" + + return self._get(resource=f"schedule{q}{type_q}{team_q}").json() diff --git a/nhlpy/api/standings.py b/nhlpy/api/standings.py index e9985a7..45f1168 100644 --- a/nhlpy/api/standings.py +++ b/nhlpy/api/standings.py @@ -2,6 +2,13 @@ class Standings(BaseNHLAPIClient): + def get_standing_types(self) -> dict: + """ + Returns a list of standing types that can be used in get_standings_by_standing_type() + :return: dict of standing types + """ + return self._get(resource="standingsTypes").json() + def get_standings(self, season: str = None, detailed_record: bool = False) -> dict: """ Gets the standings for the season supplied via season: param. @@ -11,16 +18,11 @@ def get_standings(self, season: str = None, detailed_record: bool = False) -> di head-to-head records against divisions and conferences. :return: dict """ - modifier = f"season={season}&" if season else "" - detailed = "expand=standings.record&" if detailed_record else "" - return self._get(resource=f"standings?{modifier}{detailed}").json() + modifier: str = f"season={season}&" if season else "" + detailed: str = "expand=standings.record&" if detailed_record else "" - def get_standing_types(self) -> dict: - """ - Returns a list of standing types that can be used in get_standings_by_standing_type() - :return: dict of standing types - """ - return self._get(resource="standingsTypes").json() + response: dict = self._get(resource=f"standings?{modifier}{detailed}").json() + return response["records"] def get_standings_by_standing_type( self, season: str, standing_type: str, detailed_records: bool = False @@ -34,6 +36,9 @@ def get_standings_by_standing_type( postseason, byDivision, byConference, byLeague :return: dict """ - query = f"season={season}&" - detailed = "expand=standings.record&" if detailed_records else "" - return self._get(resource=f"standings/{standing_type}?{query}{detailed}").json() + query: str = f"season={season}&" + detailed: str = "expand=standings.record&" if detailed_records else "" + response: dict = self._get( + resource=f"standings/{standing_type}?{query}{detailed}" + ).json() + return response["records"] diff --git a/nhlpy/api/teams.py b/nhlpy/api/teams.py index f587294..0a8eb55 100644 --- a/nhlpy/api/teams.py +++ b/nhlpy/api/teams.py @@ -7,7 +7,8 @@ def all(self) -> dict: Returns a list of all teams. :return: dict """ - return self._get(resource="/teams").json() + response: dict = self._get(resource="/teams").json() + return response["teams"] def get_by_id( self, @@ -20,10 +21,10 @@ def get_by_id( :param roster: bool, Should include the roster for the team :return: dict """ - query = "" + query: str = "" if roster: query += "?expand=team.roster" - return self._get(resource=f"teams/{id}{query}").json() + return self._get(resource=f"teams/{id}{query}").json()["teams"] def get_team_next_game(self, id: int) -> dict: """ @@ -31,7 +32,9 @@ def get_team_next_game(self, id: int) -> dict: :param id: int, NHL team id :return: dict """ - return self._get(resource=f"teams/{id}?expand=team.schedule.next").json() + return self._get(resource=f"teams/{id}?expand=team.schedule.next").json()[ + "teams" + ] def get_team_previous_game(self, id: int) -> dict: """ @@ -39,7 +42,9 @@ def get_team_previous_game(self, id: int) -> dict: :param id: int, NHL team id :return: dict """ - return self._get(resource=f"teams/{id}?expand=team.schedule.previous").json() + return self._get(resource=f"teams/{id}?expand=team.schedule.previous").json()[ + "teams" + ] def get_team_with_stats(self, id: int) -> dict: """ @@ -47,7 +52,7 @@ def get_team_with_stats(self, id: int) -> dict: :param id: int, NHL team id :return: dict """ - return self._get(resource=f"teams/{id}?expand=team.stats").json() + return self._get(resource=f"teams/{id}?expand=team.stats").json()["teams"] def get_team_roster(self, id: int) -> dict: """ @@ -55,7 +60,7 @@ def get_team_roster(self, id: int) -> dict: :param id: int, NHL team id :return: dict """ - return self._get(resource=f"teams/{id}/roster").json() + return self._get(resource=f"teams/{id}/roster").json()["roster"] def get_team_stats(self, id: int) -> dict: """ @@ -63,4 +68,4 @@ def get_team_stats(self, id: int) -> dict: :param id: :return: dict """ - return self._get(resource=f"teams/{id}/stats").json() + return self._get(resource=f"teams/{id}/stats").json()["stats"] diff --git a/pyproject.toml b/pyproject.toml index 1a6c935..2374eaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "nhl-api-py" -version = "0.3.0" -description = "NHL API Wrapper. For standings, team stats, outcomes and player information." +version = "0.4.1" +description = "NHL API. For standings, team stats, outcomes, player information. Contains each individual API endpoint as well as convience methods for easy data loading in Pandas or any ML applications." authors = ["Corey Schaf "] readme = "README.md" packages = [{include = "nhlpy"}]