diff --git a/src/nba_api/stats/endpoints/_expected_data/dunkscoreleaders.py b/src/nba_api/stats/endpoints/_expected_data/dunkscoreleaders.py index ebc73e79..66036817 100644 --- a/src/nba_api/stats/endpoints/_expected_data/dunkscoreleaders.py +++ b/src/nba_api/stats/endpoints/_expected_data/dunkscoreleaders.py @@ -1,61 +1,61 @@ """Expected data structure for DunkScoreLeaders endpoint.""" _EXPECTED_DATA = { - "Dunks": [ - "GAME_ID", - "GAME_DATE", - "MATCHUP", - "PERIOD", - "GAME_CLOCK_TIME", - "EVENT_NUM", - "PLAYER_ID", - "PLAYER_NAME", - "FIRST_NAME", - "LAST_NAME", - "TEAM_ID", - "TEAM_NAME", - "TEAM_CITY", - "TEAM_ABBREVIATION", - "DUNK_SCORE", - "JUMP_SUBSCORE", - "POWER_SUBSCORE", - "STYLE_SUBSCORE", - "DEFENSIVE_CONTEST_SUBSCORE", - "MAX_BALL_HEIGHT", - "BALL_SPEED_THROUGH_RIM", - "PLAYER_VERTICAL", - "HANG_TIME", - "TAKEOFF_DISTANCE", - "REVERSE_DUNK", - "DUNK_360", - "THROUGH_THE_LEGS", - "ALLEY_OOP", - "TIP_IN", - "SELF_OOP", - "PLAYER_ROTATION", - "PLAYER_LATERAL_SPEED", - "BALL_DISTANCE_TRAVELED", - "BALL_REACH_BACK", - "TOTAL_BALL_ACCELERATION", - "DUNKING_HAND", - "JUMPING_FOOT", - "PASS_LENGTH", - "CATCHING_HAND", - "CATCH_DISTANCE", - "LATERAL_CATCH_DISTANCE", - "PASSER_ID", - "PASSER_NAME", - "PASSER_FIRST_NAME", - "PASSER_LAST_NAME", - "PASS_RELEASE_POINT", - "SHOOTER_ID", - "SHOOTER_NAME", - "SHOOTER_FIRST_NAME", - "SHOOTER_LAST_NAME", - "SHOT_RELEASE_POINT", - "SHOT_LENGTH", - "DEFENSIVE_CONTEST_LEVEL", - "POSSIBLE_ATTEMPTED_CHARGE", - "VIDEO_AVAILABLE", + "dunks": [ + "gameId", + "gameDate", + "matchup", + "period", + "gameClockTime", + "eventNum", + "playerId", + "playerName", + "firstName", + "lastName", + "teamId", + "teamName", + "teamCity", + "teamAbbreviation", + "dunkScore", + "jumpSubscore", + "powerSubscore", + "styleSubscore", + "defensiveContestSubscore", + "maxBallHeight", + "ballSpeedThroughRim", + "playerVertical", + "hangTime", + "takeoffDistance", + "reverseDunk", + "dunk360", + "throughTheLegs", + "alleyOop", + "tipIn", + "selfOop", + "playerRotation", + "playerLateralSpeed", + "ballDistanceTraveled", + "ballReachBack", + "totalBallAcceleration", + "dunkingHand", + "jumpingFoot", + "passLength", + "catchingHand", + "catchDistance", + "lateralCatchDistance", + "passerId", + "passerName", + "passerFirstName", + "passerLastName", + "passReleasePoint", + "shooterId", + "shooterName", + "shooterFirstName", + "shooterLastName", + "shotReleasePoint", + "shotLength", + "defensiveContestLevel", + "possibleAttemptedCharge", + "videoAvailable", ] } diff --git a/src/nba_api/stats/endpoints/_parsers/__init__.py b/src/nba_api/stats/endpoints/_parsers/__init__.py index 92561845..100a13c8 100644 --- a/src/nba_api/stats/endpoints/_parsers/__init__.py +++ b/src/nba_api/stats/endpoints/_parsers/__init__.py @@ -21,6 +21,7 @@ from .boxscoresummaryv3 import NBAStatsBoxscoreSummaryParserV3 from .boxscoretraditionalv3 import NBAStatsBoxscoreTraditionalParserV3 from .boxscoreusagev3 import NBAStatsBoxscoreUsageV3Parser +from .dunkscoreleaders import NBAStatsDunkScoreLeadersParser from .gravityleaders import NBAStatsGravityLeadersParser from .iststandings import NBAStatsISTStandingsParser from .playbyplayv3 import NBAStatsPlayByPlayParserV3 @@ -48,6 +49,7 @@ "NBAStatsScheduleLeagueV2Parser", "NBAStatsScheduleLeagueV2IntParser", "NBAStatsScoreboardV3Parser", + "NBAStatsDunkScoreLeadersParser", "get_parser_for_endpoint", ] @@ -71,6 +73,7 @@ "scheduleleaguev2": NBAStatsScheduleLeagueV2Parser, "scheduleleaguev2int": NBAStatsScheduleLeagueV2IntParser, "scoreboardv3": NBAStatsScoreboardV3Parser, + "dunkscoreleaders": NBAStatsDunkScoreLeadersParser, } diff --git a/src/nba_api/stats/endpoints/_parsers/dunkscoreleaders.py b/src/nba_api/stats/endpoints/_parsers/dunkscoreleaders.py new file mode 100644 index 00000000..76970dd1 --- /dev/null +++ b/src/nba_api/stats/endpoints/_parsers/dunkscoreleaders.py @@ -0,0 +1,109 @@ +"""Parser for dunkscoreleaders endpoint.""" + + +class NBAStatsDunkScoreLeadersParser: + """Parser for dunkscoreleaders endpoint data. + + Parses the nested JSON response from the dunkscoreleaders endpoint into + tabular datasets for use with Pandas DataFrames. + """ + + def __init__(self, nba_dict): + """Initialize parser with NBA stats dictionary. + + Args: + nba_dict: Dictionary containing 'dunks' key with nested data + """ + self.nba_dict = nba_dict + self.dunks = nba_dict.get("dunks", []) + + def get_dunkscoreleaders_headers(self): + """Get headers for dunk score leaders data. + + Returns: + List of header names for the DunkScoreLeaders dataset + + """ + return [ + "gameId", + "gameDate", + "matchup", + "period", + "gameClockTime", + "eventNum", + "playerId", + "playerName", + "firstName", + "lastName", + "teamId", + "teamName", + "teamCity", + "teamAbbreviation", + "dunkScore", + "jumpSubscore", + "powerSubscore", + "styleSubscore", + "defensiveContestSubscore", + "maxBallHeight", + "ballSpeedThroughRim", + "playerVertical", + "hangTime", + "takeoffDistance", + "reverseDunk", + "dunk360", + "throughTheLegs", + "alleyOop", + "tipIn", + "selfOop", + "playerRotation", + "playerLateralSpeed", + "ballDistanceTraveled", + "ballReachBack", + "totalBallAcceleration", + "dunkingHand", + "jumpingFoot", + "passLength", + "catchingHand", + "catchDistance", + "lateralCatchDistance", + "passerId", + "passerName", + "passerFirstName", + "passerLastName", + "passReleasePoint", + "shooterId", + "shooterName", + "shooterFirstName", + "shooterLastName", + "shotReleasePoint", + "shotLength", + "defensiveContestLevel", + "possibleAttemptedCharge", + "videoAvailable", + ] + + def get_dunkscoreleaders_data(self): + """Extract dunkscoreleaders data + + Returns: + list: A list of rows, where each row represents a dunk event. + """ + rows = [] + headers = self.get_dunkscoreleaders_headers() + for dunk in self.dunks: + row = [dunk.get(header) for header in headers] + rows.append(row) + return rows + + def get_data_sets(self): + """Return all datasets for this endpoint. + + Returns: + Dictionary mapping dataset name to its headers and data. + """ + return { + "DunkScoreLeaders": { + "headers": self.get_dunkscoreleaders_headers(), + "data": self.get_dunkscoreleaders_data(), + }, + } diff --git a/src/nba_api/stats/endpoints/dunkscoreleaders.py b/src/nba_api/stats/endpoints/dunkscoreleaders.py index 2d6cfbfd..67b61062 100644 --- a/src/nba_api/stats/endpoints/dunkscoreleaders.py +++ b/src/nba_api/stats/endpoints/dunkscoreleaders.py @@ -9,7 +9,7 @@ >>> from nba_api.stats.endpoints import DunkScoreLeaders >>> dunks = DunkScoreLeaders(season='2024-25') >>> df = dunks.dunks.get_data_frame() - >>> print(df[['PLAYER_NAME', 'DUNK_SCORE', 'PLAYER_VERTICAL']].head()) + >>> print(df[['playerName', 'dunkScore', 'playerVertical']].head()) """ from nba_api.stats.endpoints._base import Endpoint @@ -57,7 +57,7 @@ class DunkScoreLeaders(Endpoint): ... player_id_nullable='1630168' ... ) >>> df = player_dunks.dunks.get_data_frame() - >>> print(df[['PLAYER_NAME', 'DUNK_SCORE', 'PLAYER_VERTICAL', 'HANG_TIME']]) + >>> print(df[['playerName', 'dunkScore', 'playerVertical', 'hangTime']].head()) """ endpoint = "dunkscoreleaders" @@ -140,9 +140,9 @@ def load_response(self): accessible DataSet object containing all dunk records with detailed biomechanics and scoring information. """ - data_sets = self.nba_response.get_data_sets() + data_sets = self.nba_response.get_data_sets(self.endpoint) self.data_sets = [ Endpoint.DataSet(data=data_set) for data_set_name, data_set in data_sets.items() ] - self.dunks = Endpoint.DataSet(data=data_sets["Dunks"]) + self.dunks = Endpoint.DataSet(data=data_sets["DunkScoreLeaders"]) diff --git a/tests/unit/stats/endpoints/data/dunkscoreleaders.py b/tests/unit/stats/endpoints/data/dunkscoreleaders.py index 3364ad7d..cf27ac47 100644 --- a/tests/unit/stats/endpoints/data/dunkscoreleaders.py +++ b/tests/unit/stats/endpoints/data/dunkscoreleaders.py @@ -10,15 +10,15 @@ "params": { "leagueId": "00", "seasonType": "Regular Season", - "seasonYear": "2024-25", - "playerId": None, - "teamId": None, - "gameId": None + "seasonYear": "2025-26", + "playerId": 1630168, + "teamId": 1610612737, + "gameId": None, }, "dunks": [ { "gameId": "0022500101", - "gameDate": "10/25/2024 12:00:00 AM", + "gameDate": "10/25/2025 12:00:00 AM", "matchup": "OKC @ ATL", "period": 1, "gameClockTime": "11:06", @@ -56,57 +56,592 @@ "jumpingFoot": "both", "passLength": 0.0, "catchingHand": "", - "passer": "", - "passerId": None, - "shotDistance": 0.0, - "shotZone": "", - "videoAvailable": True + "catchDistance": 0.0, + "lateralCatchDistance": 0.0, + "passerId": 0, + "passerName": "", + "passerFirstName": "", + "passerLastName": "", + "passReleasePoint": "", + "shooterId": 0, + "shooterName": "", + "shooterFirstName": "", + "shooterLastName": "", + "shotReleasePoint": "", + "shotLength": 0.0, + "defensiveContestLevel": 73.816, + "possibleAttemptedCharge": False, + "videoAvailable": True, }, { - "gameId": "0022500095", - "gameDate": "10/24/2024 12:00:00 AM", - "matchup": "TOR @ CLE", - "period": 2, - "gameClockTime": "08:15", - "eventNum": 45, - "playerId": 1629627, - "playerName": "Zion Williamson", - "firstName": "Zion", - "lastName": "Williamson", - "teamId": 1610612740, - "teamName": "Pelicans", - "teamCity": "New Orleans", - "teamAbbreviation": "NOP", - "dunkScore": 117.5, - "jumpSubscore": 85.2, - "powerSubscore": 94.5, - "styleSubscore": 71.3, - "defensiveContestSubscore": 85.0, - "maxBallHeight": 11.2, - "ballSpeedThroughRim": 29.123, - "playerVertical": 33.582, - "hangTime": 0.495, - "takeoffDistance": 6.321, + "gameId": "0022500090", + "gameDate": "10/24/2025 12:00:00 AM", + "matchup": "ATL @ ORL", + "period": 3, + "gameClockTime": "10:42", + "eventNum": 408, + "playerId": 1630168, + "playerName": "Onyeka Okongwu", + "firstName": "Onyeka", + "lastName": "Okongwu", + "teamId": 1610612737, + "teamName": "Hawks", + "teamCity": "Atlanta", + "teamAbbreviation": "ATL", + "dunkScore": 63.1, + "jumpSubscore": 53.4, + "powerSubscore": 42.9, + "styleSubscore": 27.5, + "defensiveContestSubscore": 49.8, + "maxBallHeight": 10.659, + "ballSpeedThroughRim": 22.009, + "playerVertical": 26.741, + "hangTime": 0.416, + "takeoffDistance": 4.94, + "reverseDunk": False, + "dunk360": False, + "throughTheLegs": False, + "alleyOop": False, + "tipIn": False, + "selfOop": False, + "playerRotation": 33.881, + "playerLateralSpeed": 4.536, + "ballDistanceTraveled": 1.814, + "ballReachBack": 0.0, + "totalBallAcceleration": 0.556, + "dunkingHand": "both", + "jumpingFoot": "both", + "passLength": 0.0, + "catchingHand": "", + "catchDistance": 0.0, + "lateralCatchDistance": 0.0, + "passerId": 0, + "passerName": "", + "passerFirstName": "", + "passerLastName": "", + "passReleasePoint": "", + "shooterId": 0, + "shooterName": "", + "shooterFirstName": "", + "shooterLastName": "", + "shotReleasePoint": "", + "shotLength": 0.0, + "defensiveContestLevel": 53.863, + "possibleAttemptedCharge": False, + "videoAvailable": True, + }, + { + "gameId": "0022500101", + "gameDate": "10/25/2025 12:00:00 AM", + "matchup": "OKC @ ATL", + "period": 3, + "gameClockTime": "7:37", + "eventNum": 398, + "playerId": 1630168, + "playerName": "Onyeka Okongwu", + "firstName": "Onyeka", + "lastName": "Okongwu", + "teamId": 1610612737, + "teamName": "Hawks", + "teamCity": "Atlanta", + "teamAbbreviation": "ATL", + "dunkScore": 46.7, + "jumpSubscore": 41.1, + "powerSubscore": 53.7, + "styleSubscore": 79.3, + "defensiveContestSubscore": 0.0, + "maxBallHeight": 10.601, + "ballSpeedThroughRim": 10.865, + "playerVertical": 25.434, + "hangTime": 0.483, + "takeoffDistance": 1.771, "reverseDunk": False, "dunk360": False, "throughTheLegs": False, "alleyOop": True, "tipIn": False, "selfOop": False, - "playerRotation": 35.214, - "playerLateralSpeed": 4.567, - "ballDistanceTraveled": 2.45, - "ballReachBack": 1.123, - "totalBallAcceleration": 1.412, - "dunkingHand": "right", + "playerRotation": 14.612, + "playerLateralSpeed": 1.172, + "ballDistanceTraveled": 1.977, + "ballReachBack": 1.977, + "totalBallAcceleration": 0.071, + "dunkingHand": "both", + "jumpingFoot": "both", + "passLength": 15.805, + "catchingHand": "both", + "catchDistance": 1.863, + "lateralCatchDistance": 1.863, + "passerId": 1629027, + "passerName": "Trae Young", + "passerFirstName": "Trae", + "passerLastName": "Young", + "passReleasePoint": "-350.838,115.86,85.957", + "shooterId": 0, + "shooterName": "", + "shooterFirstName": "", + "shooterLastName": "", + "shotReleasePoint": "", + "shotLength": 0.0, + "defensiveContestLevel": 0.0, + "possibleAttemptedCharge": False, + "videoAvailable": True, + }, + { + "gameId": "0022500239", + "gameDate": "11/16/2025 12:00:00 AM", + "matchup": "ATL @ PHX", + "period": 4, + "gameClockTime": "1:16", + "eventNum": 684, + "playerId": 1630168, + "playerName": "Onyeka Okongwu", + "firstName": "Onyeka", + "lastName": "Okongwu", + "teamId": 1610612737, + "teamName": "Hawks", + "teamCity": "Atlanta", + "teamAbbreviation": "ATL", + "dunkScore": 46.0, + "jumpSubscore": 68.5, + "powerSubscore": 17.3, + "styleSubscore": 32.5, + "defensiveContestSubscore": 4.6, + "maxBallHeight": 11.128, + "ballSpeedThroughRim": 12.386, + "playerVertical": 30.59, + "hangTime": 0.516, + "takeoffDistance": 3.216, + "reverseDunk": False, + "dunk360": False, + "throughTheLegs": False, + "alleyOop": True, + "tipIn": False, + "selfOop": False, + "playerRotation": 7.763, + "playerLateralSpeed": 3.288, + "ballDistanceTraveled": 0.0, + "ballReachBack": 0.0, + "totalBallAcceleration": 0.191, + "dunkingHand": "both", + "jumpingFoot": "both", + "passLength": 13.578, + "catchingHand": "both", + "catchDistance": 2.193, + "lateralCatchDistance": 1.891, + "passerId": 1630700, + "passerName": "Dyson Daniels", + "passerFirstName": "Dyson", + "passerLastName": "Daniels", + "passReleasePoint": "-340.271,26.746,82.963", + "shooterId": 0, + "shooterName": "", + "shooterFirstName": "", + "shooterLastName": "", + "shotReleasePoint": "", + "shotLength": 0.0, + "defensiveContestLevel": 5.045, + "possibleAttemptedCharge": False, + "videoAvailable": True, + }, + { + "gameId": "0022500227", + "gameDate": "11/13/2025 12:00:00 AM", + "matchup": "ATL @ UTA", + "period": 3, + "gameClockTime": "11:48", + "eventNum": 373, + "playerId": 1630168, + "playerName": "Onyeka Okongwu", + "firstName": "Onyeka", + "lastName": "Okongwu", + "teamId": 1610612737, + "teamName": "Hawks", + "teamCity": "Atlanta", + "teamAbbreviation": "ATL", + "dunkScore": 44.0, + "jumpSubscore": 49.7, + "powerSubscore": 47.9, + "styleSubscore": 58.9, + "defensiveContestSubscore": 0.5, + "maxBallHeight": 10.561, + "ballSpeedThroughRim": 18.068, + "playerVertical": 27.454, + "hangTime": 0.35, + "takeoffDistance": 4.714, + "reverseDunk": False, + "dunk360": False, + "throughTheLegs": False, + "alleyOop": False, + "tipIn": False, + "selfOop": False, + "playerRotation": 15.539, + "playerLateralSpeed": 5.87, + "ballDistanceTraveled": 1.709, + "ballReachBack": 0.84, + "totalBallAcceleration": 0.0, + "dunkingHand": "both", + "jumpingFoot": "both", + "passLength": 0.0, + "catchingHand": "", + "catchDistance": 0.0, + "lateralCatchDistance": 0.0, + "passerId": 0, + "passerName": "", + "passerFirstName": "", + "passerLastName": "", + "passReleasePoint": "", + "shooterId": 0, + "shooterName": "", + "shooterFirstName": "", + "shooterLastName": "", + "shotReleasePoint": "", + "shotLength": 0.0, + "defensiveContestLevel": 0.54, + "possibleAttemptedCharge": False, + "videoAvailable": True, + }, + { + "gameId": "0022500166", + "gameDate": "11/4/2025 12:00:00 AM", + "matchup": "ORL @ ATL", + "period": 3, + "gameClockTime": "4:33", + "eventNum": 485, + "playerId": 1630168, + "playerName": "Onyeka Okongwu", + "firstName": "Onyeka", + "lastName": "Okongwu", + "teamId": 1610612737, + "teamName": "Hawks", + "teamCity": "Atlanta", + "teamAbbreviation": "ATL", + "dunkScore": 37.4, + "jumpSubscore": 61.0, + "powerSubscore": 14.3, + "styleSubscore": 23.9, + "defensiveContestSubscore": 0.0, + "maxBallHeight": 10.622, + "ballSpeedThroughRim": 13.105, + "playerVertical": 29.253, + "hangTime": 0.417, + "takeoffDistance": 5.185, + "reverseDunk": False, + "dunk360": False, + "throughTheLegs": False, + "alleyOop": False, + "tipIn": False, + "selfOop": False, + "playerRotation": 24.354, + "playerLateralSpeed": 3.666, + "ballDistanceTraveled": 1.225, + "ballReachBack": 0.0, + "totalBallAcceleration": 0.095, + "dunkingHand": "both", + "jumpingFoot": "both", + "passLength": 0.0, + "catchingHand": "", + "catchDistance": 0.0, + "lateralCatchDistance": 0.0, + "passerId": 0, + "passerName": "", + "passerFirstName": "", + "passerLastName": "", + "passReleasePoint": "", + "shooterId": 0, + "shooterName": "", + "shooterFirstName": "", + "shooterLastName": "", + "shotReleasePoint": "", + "shotLength": 0.0, + "defensiveContestLevel": 0.0, + "possibleAttemptedCharge": False, + "videoAvailable": True, + }, + { + "gameId": "0022500227", + "gameDate": "11/13/2025 12:00:00 AM", + "matchup": "ATL @ UTA", + "period": 4, + "gameClockTime": "5:08", + "eventNum": 664, + "playerId": 1630168, + "playerName": "Onyeka Okongwu", + "firstName": "Onyeka", + "lastName": "Okongwu", + "teamId": 1610612737, + "teamName": "Hawks", + "teamCity": "Atlanta", + "teamAbbreviation": "ATL", + "dunkScore": 37.4, + "jumpSubscore": 53.0, + "powerSubscore": 14.0, + "styleSubscore": 33.3, + "defensiveContestSubscore": 0.0, + "maxBallHeight": 10.826, + "ballSpeedThroughRim": 13.513, + "playerVertical": 27.428, + "hangTime": 0.467, + "takeoffDistance": 2.283, + "reverseDunk": False, + "dunk360": False, + "throughTheLegs": False, + "alleyOop": True, + "tipIn": False, + "selfOop": False, + "playerRotation": 6.535, + "playerLateralSpeed": 2.066, + "ballDistanceTraveled": 0.0, + "ballReachBack": 0.0, + "totalBallAcceleration": 0.076, + "dunkingHand": "both", + "jumpingFoot": "both", + "passLength": 12.932, + "catchingHand": "both", + "catchDistance": 2.81, + "lateralCatchDistance": 2.804, + "passerId": 1630552, + "passerName": "Jalen Johnson", + "passerFirstName": "Jalen", + "passerLastName": "Johnson", + "passReleasePoint": "-358.792,62.131,99.339", + "shooterId": 0, + "shooterName": "", + "shooterFirstName": "", + "shooterLastName": "", + "shotReleasePoint": "", + "shotLength": 0.0, + "defensiveContestLevel": 0.0, + "possibleAttemptedCharge": False, + "videoAvailable": True, + }, + { + "gameId": "0022500090", + "gameDate": "10/24/2025 12:00:00 AM", + "matchup": "ATL @ ORL", + "period": 3, + "gameClockTime": "8:29", + "eventNum": 428, + "playerId": 1630168, + "playerName": "Onyeka Okongwu", + "firstName": "Onyeka", + "lastName": "Okongwu", + "teamId": 1610612737, + "teamName": "Hawks", + "teamCity": "Atlanta", + "teamAbbreviation": "ATL", + "dunkScore": 32.1, + "jumpSubscore": 38.1, + "powerSubscore": 13.2, + "styleSubscore": 34.2, + "defensiveContestSubscore": 4.5, + "maxBallHeight": 10.616, + "ballSpeedThroughRim": 12.234, + "playerVertical": 23.0, + "hangTime": 0.45, + "takeoffDistance": 4.231, + "reverseDunk": False, + "dunk360": False, + "throughTheLegs": False, + "alleyOop": True, + "tipIn": False, + "selfOop": False, + "playerRotation": 58.421, + "playerLateralSpeed": 6.043, + "ballDistanceTraveled": 0.0, + "ballReachBack": 0.0, + "totalBallAcceleration": 0.077, + "dunkingHand": "both", "jumpingFoot": "left", - "passLength": 25.5, - "catchingHand": "right", - "passer": "Brandon Ingram", - "passerId": 1627742, - "shotDistance": 0.5, - "shotZone": "Restricted Area", - "videoAvailable": True - } - ] + "passLength": 16.285, + "catchingHand": "both", + "catchDistance": 2.278, + "lateralCatchDistance": 2.268, + "passerId": 1629027, + "passerName": "Trae Young", + "passerFirstName": "Trae", + "passerLastName": "Young", + "passReleasePoint": "306.349,17.356,92.668", + "shooterId": 0, + "shooterName": "", + "shooterFirstName": "", + "shooterLastName": "", + "shotReleasePoint": "", + "shotLength": 0.0, + "defensiveContestLevel": 4.916, + "possibleAttemptedCharge": False, + "videoAvailable": True, + }, + { + "gameId": "0022500130", + "gameDate": "10/29/2025 12:00:00 AM", + "matchup": "ATL @ BKN", + "period": 1, + "gameClockTime": "5:41", + "eventNum": 94, + "playerId": 1630168, + "playerName": "Onyeka Okongwu", + "firstName": "Onyeka", + "lastName": "Okongwu", + "teamId": 1610612737, + "teamName": "Hawks", + "teamCity": "Atlanta", + "teamAbbreviation": "ATL", + "dunkScore": 25.6, + "jumpSubscore": 35.0, + "powerSubscore": 13.0, + "styleSubscore": 24.0, + "defensiveContestSubscore": 0.0, + "maxBallHeight": 10.476, + "ballSpeedThroughRim": 12.939, + "playerVertical": 25.949, + "hangTime": 0.317, + "takeoffDistance": 1.974, + "reverseDunk": False, + "dunk360": False, + "throughTheLegs": False, + "alleyOop": False, + "tipIn": False, + "selfOop": False, + "playerRotation": 8.362, + "playerLateralSpeed": 2.901, + "ballDistanceTraveled": 1.252, + "ballReachBack": 0.0, + "totalBallAcceleration": 0.056, + "dunkingHand": "both", + "jumpingFoot": "both", + "passLength": 0.0, + "catchingHand": "", + "catchDistance": 0.0, + "lateralCatchDistance": 0.0, + "passerId": 0, + "passerName": "", + "passerFirstName": "", + "passerLastName": "", + "passReleasePoint": "", + "shooterId": 0, + "shooterName": "", + "shooterFirstName": "", + "shooterLastName": "", + "shotReleasePoint": "", + "shotLength": 0.0, + "defensiveContestLevel": 0.0, + "possibleAttemptedCharge": False, + "videoAvailable": True, + }, + { + "gameId": "0022500130", + "gameDate": "10/29/2025 12:00:00 AM", + "matchup": "ATL @ BKN", + "period": 4, + "gameClockTime": "9:35", + "eventNum": 575, + "playerId": 1630168, + "playerName": "Onyeka Okongwu", + "firstName": "Onyeka", + "lastName": "Okongwu", + "teamId": 1610612737, + "teamName": "Hawks", + "teamCity": "Atlanta", + "teamAbbreviation": "ATL", + "dunkScore": 25.5, + "jumpSubscore": 34.2, + "powerSubscore": 14.4, + "styleSubscore": 23.9, + "defensiveContestSubscore": 0.0, + "maxBallHeight": 10.513, + "ballSpeedThroughRim": 14.016, + "playerVertical": 24.828, + "hangTime": 0.333, + "takeoffDistance": 3.256, + "reverseDunk": False, + "dunk360": False, + "throughTheLegs": False, + "alleyOop": False, + "tipIn": False, + "selfOop": False, + "playerRotation": 46.284, + "playerLateralSpeed": 4.401, + "ballDistanceTraveled": 1.228, + "ballReachBack": 0.0, + "totalBallAcceleration": 0.075, + "dunkingHand": "both", + "jumpingFoot": "both", + "passLength": 0.0, + "catchingHand": "", + "catchDistance": 0.0, + "lateralCatchDistance": 0.0, + "passerId": 0, + "passerName": "", + "passerFirstName": "", + "passerLastName": "", + "passReleasePoint": "", + "shooterId": 0, + "shooterName": "", + "shooterFirstName": "", + "shooterLastName": "", + "shotReleasePoint": "", + "shotLength": 0.0, + "defensiveContestLevel": 0.0, + "possibleAttemptedCharge": False, + "videoAvailable": True, + }, + { + "gameId": "0022500115", + "gameDate": "10/27/2025 12:00:00 AM", + "matchup": "ATL @ CHI", + "period": 4, + "gameClockTime": "10:22", + "eventNum": 561, + "playerId": 1630168, + "playerName": "Onyeka Okongwu", + "firstName": "Onyeka", + "lastName": "Okongwu", + "teamId": 1610612737, + "teamName": "Hawks", + "teamCity": "Atlanta", + "teamAbbreviation": "ATL", + "dunkScore": 24.2, + "jumpSubscore": 33.1, + "powerSubscore": 10.3, + "styleSubscore": 23.8, + "defensiveContestSubscore": 0.4, + "maxBallHeight": 10.445, + "ballSpeedThroughRim": 9.668, + "playerVertical": 25.043, + "hangTime": 0.317, + "takeoffDistance": 3.005, + "reverseDunk": False, + "dunk360": False, + "throughTheLegs": False, + "alleyOop": False, + "tipIn": False, + "selfOop": False, + "playerRotation": 38.098, + "playerLateralSpeed": 2.969, + "ballDistanceTraveled": 1.218, + "ballReachBack": 0.0, + "totalBallAcceleration": 0.012, + "dunkingHand": "both", + "jumpingFoot": "both", + "passLength": 0.0, + "catchingHand": "", + "catchDistance": 0.0, + "lateralCatchDistance": 0.0, + "passerId": 0, + "passerName": "", + "passerFirstName": "", + "passerLastName": "", + "passReleasePoint": "", + "shooterId": 0, + "shooterName": "", + "shooterFirstName": "", + "shooterLastName": "", + "shotReleasePoint": "", + "shotLength": 0.0, + "defensiveContestLevel": 0.39, + "possibleAttemptedCharge": False, + "videoAvailable": True, + }, + ], } diff --git a/tests/unit/stats/endpoints/test_dunkscoreleaders.py b/tests/unit/stats/endpoints/test_dunkscoreleaders.py index de4d548f..a1d59a43 100644 --- a/tests/unit/stats/endpoints/test_dunkscoreleaders.py +++ b/tests/unit/stats/endpoints/test_dunkscoreleaders.py @@ -6,6 +6,7 @@ import pytest from nba_api.stats.endpoints import DunkScoreLeaders + from .data.dunkscoreleaders import DUNKSCORELEADERS_SAMPLE @@ -46,9 +47,9 @@ def test_expected_data_structure(self): _EXPECTED_DATA, ) - assert "Dunks" in _EXPECTED_DATA - assert isinstance(_EXPECTED_DATA["Dunks"], list) - assert len(_EXPECTED_DATA["Dunks"]) == 55 + assert "dunks" in _EXPECTED_DATA + assert isinstance(_EXPECTED_DATA["dunks"], list) + assert len(_EXPECTED_DATA["dunks"]) == 55 def test_expected_data_fields(self): """Test that expected_data contains key dunk tracking fields.""" @@ -56,17 +57,64 @@ def test_expected_data_fields(self): _EXPECTED_DATA, ) - dunks_fields = _EXPECTED_DATA["Dunks"] + dunks_fields = _EXPECTED_DATA["dunks"] # Verify key fields are present - assert "GAME_ID" in dunks_fields - assert "PLAYER_NAME" in dunks_fields - assert "DUNK_SCORE" in dunks_fields - assert "PLAYER_VERTICAL" in dunks_fields - assert "HANG_TIME" in dunks_fields - assert "TAKEOFF_DISTANCE" in dunks_fields - assert "ALLEY_OOP" in dunks_fields - assert "VIDEO_AVAILABLE" in dunks_fields + assert "gameId" in dunks_fields + assert "gameDate" in dunks_fields + assert "matchup" in dunks_fields + assert "period" in dunks_fields + assert "gameClockTime" in dunks_fields + assert "eventNum" in dunks_fields + assert "playerId" in dunks_fields + assert "playerName" in dunks_fields + assert "firstName" in dunks_fields + assert "lastName" in dunks_fields + assert "teamId" in dunks_fields + assert "teamName" in dunks_fields + assert "teamCity" in dunks_fields + assert "teamAbbreviation" in dunks_fields + assert "dunkScore" in dunks_fields + assert "jumpSubscore" in dunks_fields + assert "powerSubscore" in dunks_fields + assert "styleSubscore" in dunks_fields + assert "defensiveContestSubscore" in dunks_fields + assert "maxBallHeight" in dunks_fields + assert "ballSpeedThroughRim" in dunks_fields + assert "playerVertical" in dunks_fields + assert "hangTime" in dunks_fields + assert "takeoffDistance" in dunks_fields + assert "reverseDunk" in dunks_fields + assert "dunk360" in dunks_fields + assert "throughTheLegs" in dunks_fields + assert "alleyOop" in dunks_fields + assert "tipIn" in dunks_fields + assert "selfOop" in dunks_fields + assert "playerRotation" in dunks_fields + assert "playerLateralSpeed" in dunks_fields + assert "ballDistanceTraveled" in dunks_fields + assert "ballReachBack" in dunks_fields + assert "totalBallAcceleration" in dunks_fields + assert "dunkingHand" in dunks_fields + assert "jumpingFoot" in dunks_fields + assert "passLength" in dunks_fields + assert "catchingHand" in dunks_fields + assert "catchDistance" in dunks_fields + assert "lateralCatchDistance" in dunks_fields + assert "passerId" in dunks_fields + assert "passerName" in dunks_fields + assert "passerFirstName" in dunks_fields + assert "passerLastName" in dunks_fields + assert "passReleasePoint" in dunks_fields + assert "shooterId" in dunks_fields + assert "shooterName" in dunks_fields + assert "shooterFirstName" in dunks_fields + assert "shooterLastName" in dunks_fields + assert "shotReleasePoint" in dunks_fields + assert "shotLength" in dunks_fields + assert "defensiveContestLevel" in dunks_fields + assert "possibleAttemptedCharge" in dunks_fields + assert "videoAvailable" in dunks_fields @patch("nba_api.stats.library.http.NBAStatsHTTP.send_api_request") def test_endpoint_with_mocked_request(self, mock_request, json_fixture): @@ -75,16 +123,179 @@ def test_endpoint_with_mocked_request(self, mock_request, json_fixture): mock_response = Mock() mock_response.get_dict.return_value = json_fixture mock_response.get_data_sets.return_value = { - "Dunks": { + "DunkScoreLeaders": { "headers": [ - "GAME_ID", - "PLAYER_NAME", - "DUNK_SCORE", - "PLAYER_VERTICAL", + "gameId", + "gameDate", + "matchup", + "period", + "gameClockTime", + "eventNum", + "playerId", + "playerName", + "firstName", + "lastName", + "teamId", + "teamName", + "teamCity", + "teamAbbreviation", + "dunkScore", + "jumpSubscore", + "powerSubscore", + "styleSubscore", + "defensiveContestSubscore", + "maxBallHeight", + "ballSpeedThroughRim", + "playerVertical", + "hangTime", + "takeoffDistance", + "reverseDunk", + "dunk360", + "throughTheLegs", + "alleyOop", + "tipIn", + "selfOop", + "playerRotation", + "playerLateralSpeed", + "ballDistanceTraveled", + "ballReachBack", + "totalBallAcceleration", + "dunkingHand", + "jumpingFoot", + "passLength", + "catchingHand", + "catchDistance", + "lateralCatchDistance", + "passerId", + "passerName", + "passerFirstName", + "passerLastName", + "passReleasePoint", + "shooterId", + "shooterName", + "shooterFirstName", + "shooterLastName", + "shotReleasePoint", + "shotLength", + "defensiveContestLevel", + "possibleAttemptedCharge", + "videoAvailable", ], "data": [ - ["0022500101", "Onyeka Okongwu", 117.8, 32.338], - ["0022500095", "Zion Williamson", 117.5, 33.582], + [ + "0022500101", + "10/25/2025 12:00:00 AM", + "OKC @ ATL", + 1, + "11:06", + 13, + 1630168, + "Onyeka Okongwu", + "Onyeka", + "Okongwu", + 1610612737, + "Hawks", + "Atlanta", + "ATL", + 117.8, + 82.6, + 96.8, + 69.1, + 87.3, + 10.9, + 28.772, + 32.338, + 0.483, + 6.494, + False, + False, + False, + False, + False, + False, + 33.819, + 4.321, + 2.13, + 1.072, + 1.335, + "right", + "both", + 0.0, + "", + 0.0, + 0.0, + 0, + "", + "", + "", + "", + 0, + "", + "", + "", + "", + 0.0, + 73.816, + False, + True, + ], + [ + "0022500090", + "10/24/2025 12:00:00 AM", + "ATL @ ORL", + 3, + "10:42", + 408, + 1630168, + "Onyeka Okongwu", + "Onyeka", + "Okongwu", + 1610612737, + "Hawks", + "Atlanta", + "ATL", + 63.1, + 53.4, + 42.9, + 27.5, + 49.8, + 10.659, + 22.009, + 26.741, + 0.416, + 4.94, + False, + False, + False, + False, + False, + False, + 33.881, + 4.536, + 1.814, + 0.0, + 0.556, + "both", + "both", + 0.0, + "", + 0.0, + 0.0, + 0, + "", + "", + "", + "", + 0, + "", + "", + "", + "", + 0.0, + 53.863, + False, + True, + ], ], }, } diff --git a/tests/unit/stats/endpoints/test_dunkscoreleaders_parser.py b/tests/unit/stats/endpoints/test_dunkscoreleaders_parser.py new file mode 100644 index 00000000..c529a9fc --- /dev/null +++ b/tests/unit/stats/endpoints/test_dunkscoreleaders_parser.py @@ -0,0 +1,124 @@ +"""Unit tests for DunkScoreLeaders parser.""" + +import pytest + +from nba_api.stats.endpoints._parsers.dunkscoreleaders import ( + NBAStatsDunkScoreLeadersParser, +) + +from .data.dunkscoreleaders import DUNKSCORELEADERS_SAMPLE + + +@pytest.fixture +def json_fixture(): + """Load the DunkScoreLeaders fixture.""" + return DUNKSCORELEADERS_SAMPLE + + +@pytest.fixture +def parser(json_fixture): + """Create a parser instance with the JSON fixture.""" + return NBAStatsDunkScoreLeadersParser(json_fixture) + + +class TestNBAStatsDunkScoreLeadersParser: + """Test the DunkScoreLeaders parser.""" + + def test_parser_initialization(self, parser, json_fixture): + """Test that parser initializes correctly.""" + assert parser.nba_dict == json_fixture + assert parser.dunks == json_fixture["dunks"] + + def test_get_dunkscoreleaders_headers_returns_list(self, parser): + """Test get_dunkscoreleaders_headers returns a list.""" + headers = parser.get_dunkscoreleaders_headers() + + assert isinstance(headers, list) + assert len(headers) > 0 + + def test_get_dunkscoreleaders_headers_structure(self, parser, json_fixture): + """Test headers include expected fields.""" + headers = parser.get_dunkscoreleaders_headers() + first_dunk_keys = list(json_fixture["dunks"][0].keys()) + + assert "gameId" in headers + assert "gameDate" in headers + assert "matchup" in headers + assert "period" in headers + assert "gameClockTime" in headers + assert "eventNum" in headers + assert "playerId" in headers + assert "playerName" in headers + assert "firstName" in headers + assert "lastName" in headers + assert "teamId" in headers + assert "teamName" in headers + assert "teamCity" in headers + assert "teamAbbreviation" in headers + assert "dunkScore" in headers + assert "jumpSubscore" in headers + assert "powerSubscore" in headers + assert "styleSubscore" in headers + assert "defensiveContestSubscore" in headers + assert "maxBallHeight" in headers + assert "ballSpeedThroughRim" in headers + assert "playerVertical" in headers + assert "hangTime" in headers + assert "takeoffDistance" in headers + assert "reverseDunk" in headers + assert "dunk360" in headers + assert "throughTheLegs" in headers + assert "alleyOop" in headers + assert "tipIn" in headers + assert "selfOop" in headers + assert "playerRotation" in headers + assert "playerLateralSpeed" in headers + assert "ballDistanceTraveled" in headers + assert "ballReachBack" in headers + assert "totalBallAcceleration" in headers + assert "dunkingHand" in headers + assert "jumpingFoot" in headers + assert "passLength" in headers + assert "catchingHand" in headers + assert "catchDistance" in headers + assert "lateralCatchDistance" in headers + assert "passerId" in headers + assert "passerName" in headers + assert "passerFirstName" in headers + assert "passerLastName" in headers + assert "passReleasePoint" in headers + assert "shooterId" in headers + assert "shooterName" in headers + assert "shooterFirstName" in headers + assert "shooterLastName" in headers + assert "shotReleasePoint" in headers + assert "shotLength" in headers + assert "defensiveContestLevel" in headers + assert "possibleAttemptedCharge" in headers + assert "videoAvailable" in headers + + # The parser headers should contain all keys from the data + for key in first_dunk_keys: + assert key in headers + + def test_get_dunkscoreleaders_data(self, parser, json_fixture): + """Test dunk score leaders data extraction.""" + data = parser.get_dunkscoreleaders_data() + + assert isinstance(data, list) + assert len(data) == len(json_fixture["dunks"]), "Should have row for each dunk" + + # Each row should be a list + assert all(isinstance(row, list) for row in data) + + def test_get_data_sets(self, parser): + """Test get_data_sets returns correct structure.""" + result = parser.get_data_sets() + + assert "DunkScoreLeaders" in result + + # Check DunkScoreLeaders structure + assert "headers" in result["DunkScoreLeaders"] + assert "data" in result["DunkScoreLeaders"] + assert isinstance(result["DunkScoreLeaders"]["headers"], list) + assert isinstance(result["DunkScoreLeaders"]["data"], list) diff --git a/tests/unit/stats/endpoints/test_iststandings_parser.py b/tests/unit/stats/endpoints/test_iststandings_parser.py index 20424dbc..9eef0593 100644 --- a/tests/unit/stats/endpoints/test_iststandings_parser.py +++ b/tests/unit/stats/endpoints/test_iststandings_parser.py @@ -1,9 +1,10 @@ """Unit tests for ISTStandings parser.""" import json -import pytest from pathlib import Path +import pytest + from nba_api.stats.endpoints._parsers.iststandings import ( NBAStatsISTStandingsParser, )