From 646c1e530ecb71959fb6cd4c4821a6277217047e Mon Sep 17 00:00:00 2001 From: madhavcodez Date: Thu, 21 May 2026 23:56:41 -0500 Subject: [PATCH] feat: add team conference and division static data Add team_conferences and team_divisions dicts to stats/library/data.py mapping the 30 NBA team_ids to their conference and division. WNBA teams are excluded since the existing teams.get_wnba_teams() set is already separated and the league has its own alignment. Add four helpers in stats/static/teams.py: - get_team_conference(team_id) -> "East" | "West" | None - get_team_division(team_id) -> "Atlantic" | "Central" | ... | None - find_teams_by_conference(conference) (case-insensitive) - find_teams_by_division(division) (case-insensitive) String values match the existing Conference and DivisionSimple parameter enums in stats/library/parameters.py so a lookup can be fed straight back into an endpoint call without remapping. Adds 14 unit tests (5 structural + 6 parametrized lookup + disjoint-membership + case-insensitivity + unknown-input) for the new data and helpers. --- src/nba_api/stats/library/data.py | 80 +++++++++++++++ src/nba_api/stats/static/teams.py | 42 ++++++++ tests/unit/stats/static/test_static_data.py | 103 ++++++++++++++++++++ 3 files changed, 225 insertions(+) diff --git a/src/nba_api/stats/library/data.py b/src/nba_api/stats/library/data.py index 91f674cf..50c56384 100644 --- a/src/nba_api/stats/library/data.py +++ b/src/nba_api/stats/library/data.py @@ -6661,3 +6661,83 @@ [], ], ] + + +# Conference and division mapping for NBA teams, keyed by team_id from `teams`. +# Alignment is stable since the 2004-05 season. WNBA teams are intentionally +# excluded because the WNBA uses a different alignment that changes with +# expansion. + +team_conferences = { + # Eastern Conference + 1610612737: "East", # Atlanta Hawks + 1610612738: "East", # Boston Celtics + 1610612739: "East", # Cleveland Cavaliers + 1610612741: "East", # Chicago Bulls + 1610612748: "East", # Miami Heat + 1610612749: "East", # Milwaukee Bucks + 1610612751: "East", # Brooklyn Nets + 1610612752: "East", # New York Knicks + 1610612753: "East", # Orlando Magic + 1610612754: "East", # Indiana Pacers + 1610612755: "East", # Philadelphia 76ers + 1610612761: "East", # Toronto Raptors + 1610612764: "East", # Washington Wizards + 1610612765: "East", # Detroit Pistons + 1610612766: "East", # Charlotte Hornets + # Western Conference + 1610612740: "West", # New Orleans Pelicans + 1610612742: "West", # Dallas Mavericks + 1610612743: "West", # Denver Nuggets + 1610612744: "West", # Golden State Warriors + 1610612745: "West", # Houston Rockets + 1610612746: "West", # Los Angeles Clippers + 1610612747: "West", # Los Angeles Lakers + 1610612750: "West", # Minnesota Timberwolves + 1610612756: "West", # Phoenix Suns + 1610612757: "West", # Portland Trail Blazers + 1610612758: "West", # Sacramento Kings + 1610612759: "West", # San Antonio Spurs + 1610612760: "West", # Oklahoma City Thunder + 1610612762: "West", # Utah Jazz + 1610612763: "West", # Memphis Grizzlies +} + +team_divisions = { + # Atlantic (Eastern Conference) + 1610612738: "Atlantic", # Boston Celtics + 1610612751: "Atlantic", # Brooklyn Nets + 1610612752: "Atlantic", # New York Knicks + 1610612755: "Atlantic", # Philadelphia 76ers + 1610612761: "Atlantic", # Toronto Raptors + # Central (Eastern Conference) + 1610612739: "Central", # Cleveland Cavaliers + 1610612741: "Central", # Chicago Bulls + 1610612749: "Central", # Milwaukee Bucks + 1610612754: "Central", # Indiana Pacers + 1610612765: "Central", # Detroit Pistons + # Southeast (Eastern Conference) + 1610612737: "Southeast", # Atlanta Hawks + 1610612748: "Southeast", # Miami Heat + 1610612753: "Southeast", # Orlando Magic + 1610612764: "Southeast", # Washington Wizards + 1610612766: "Southeast", # Charlotte Hornets + # Northwest (Western Conference) + 1610612743: "Northwest", # Denver Nuggets + 1610612750: "Northwest", # Minnesota Timberwolves + 1610612757: "Northwest", # Portland Trail Blazers + 1610612760: "Northwest", # Oklahoma City Thunder + 1610612762: "Northwest", # Utah Jazz + # Pacific (Western Conference) + 1610612744: "Pacific", # Golden State Warriors + 1610612746: "Pacific", # Los Angeles Clippers + 1610612747: "Pacific", # Los Angeles Lakers + 1610612756: "Pacific", # Phoenix Suns + 1610612758: "Pacific", # Sacramento Kings + # Southwest (Western Conference) + 1610612740: "Southwest", # New Orleans Pelicans + 1610612742: "Southwest", # Dallas Mavericks + 1610612745: "Southwest", # Houston Rockets + 1610612759: "Southwest", # San Antonio Spurs + 1610612763: "Southwest", # Memphis Grizzlies +} diff --git a/src/nba_api/stats/static/teams.py b/src/nba_api/stats/static/teams.py index e494f8fe..5420f0e6 100644 --- a/src/nba_api/stats/static/teams.py +++ b/src/nba_api/stats/static/teams.py @@ -1,6 +1,8 @@ import re from nba_api.stats.library.data import ( + team_conferences, + team_divisions, team_index_abbreviation, team_index_championship_year, team_index_city, @@ -148,3 +150,43 @@ def find_wnba_team_name_by_id(team_id): def get_wnba_teams(): return _get_teams(teams=wnba_teams) + + +def get_team_conference(team_id): + """Return the conference name ("East" or "West") for an NBA team id, + or None if the team id is not an NBA team. WNBA teams are not covered.""" + return team_conferences.get(team_id) + + +def get_team_division(team_id): + """Return the division name for an NBA team id (one of "Atlantic", + "Central", "Southeast", "Northwest", "Pacific", "Southwest"), or None + if the team id is not an NBA team.""" + return team_divisions.get(team_id) + + +def find_teams_by_conference(conference): + """Return all NBA teams in the given conference. Conference matching + is case-insensitive ("East" and "east" both work). Returns an empty + list for falsy input.""" + if not conference: + return [] + target = conference.lower() + return [ + _get_team_dict(team) + for team in teams + if team_conferences.get(team[team_index_id], "").lower() == target + ] + + +def find_teams_by_division(division): + """Return all NBA teams in the given division. Division matching is + case-insensitive. Returns an empty list for falsy input.""" + if not division: + return [] + target = division.lower() + return [ + _get_team_dict(team) + for team in teams + if team_divisions.get(team[team_index_id], "").lower() == target + ] diff --git a/tests/unit/stats/static/test_static_data.py b/tests/unit/stats/static/test_static_data.py index 91689474..b7e0d34e 100644 --- a/tests/unit/stats/static/test_static_data.py +++ b/tests/unit/stats/static/test_static_data.py @@ -1,3 +1,5 @@ +import pytest + from nba_api.stats.static import teams @@ -7,3 +9,104 @@ def test_nba_teams(): def test_wnba_teams(): assert len(teams.wnba_teams) == 13 + + +# --- Conference and division static data ----------------------------------- + + +CONFERENCES = ("East", "West") +DIVISIONS = ("Atlantic", "Central", "Southeast", "Northwest", "Pacific", "Southwest") + + +def test_every_nba_team_has_conference_and_division(): + nba_ids = {team["id"] for team in teams.get_teams()} + assert set(teams.team_conferences) == nba_ids + assert set(teams.team_divisions) == nba_ids + + +def test_conferences_are_evenly_split(): + by_conference = dict.fromkeys(CONFERENCES, 0) + for conference in teams.team_conferences.values(): + assert conference in CONFERENCES + by_conference[conference] += 1 + assert by_conference == {"East": 15, "West": 15} + + +def test_divisions_have_five_teams_each(): + by_division = dict.fromkeys(DIVISIONS, 0) + for division in teams.team_divisions.values(): + assert division in DIVISIONS + by_division[division] += 1 + assert all(count == 5 for count in by_division.values()) + + +@pytest.mark.parametrize( + "team_id, expected_conference, expected_division", + [ + (1610612747, "West", "Pacific"), # Los Angeles Lakers + (1610612738, "East", "Atlantic"), # Boston Celtics + (1610612749, "East", "Central"), # Milwaukee Bucks + (1610612759, "West", "Southwest"), # San Antonio Spurs + (1610612737, "East", "Southeast"), # Atlanta Hawks + (1610612760, "West", "Northwest"), # Oklahoma City Thunder + ], +) +def test_known_team_conference_and_division_lookups( + team_id, expected_conference, expected_division +): + assert teams.get_team_conference(team_id) == expected_conference + assert teams.get_team_division(team_id) == expected_division + + +def test_unknown_team_id_returns_none_for_both_helpers(): + assert teams.get_team_conference(1) is None + assert teams.get_team_division(1) is None + # WNBA team ids are intentionally not covered. + assert teams.get_team_conference(1611661313) is None # New York Liberty + assert teams.get_team_division(1611661313) is None + + +def test_find_teams_by_conference(): + east = teams.find_teams_by_conference("East") + west = teams.find_teams_by_conference("West") + assert len(east) == 15 + assert len(west) == 15 + # Case-insensitive. + assert {t["id"] for t in east} == { + t["id"] for t in teams.find_teams_by_conference("east") + } + + +def test_find_teams_by_division(): + pacific = teams.find_teams_by_division("Pacific") + assert len(pacific) == 5 + assert {t["abbreviation"] for t in pacific} == {"GSW", "LAC", "LAL", "PHX", "SAC"} + # Case-insensitive. + assert {t["id"] for t in pacific} == { + t["id"] for t in teams.find_teams_by_division("pacific") + } + + +def test_find_teams_by_conference_exact_membership_is_disjoint(): + """Guard against a team being swapped between conferences. Count-only + assertions would still pass if two teams traded conferences; exact + membership + disjointness will not.""" + east = {t["id"] for t in teams.find_teams_by_conference("East")} + west = {t["id"] for t in teams.find_teams_by_conference("West")} + nba_ids = {t["id"] for t in teams.get_teams()} + assert len(east) == 15 + assert len(west) == 15 + assert east.isdisjoint(west) + assert east | west == nba_ids + + +def test_find_teams_by_unknown_value_returns_empty(): + # Division names passed to the conference helper, and vice versa, must + # return empty rather than silently matching. + assert teams.find_teams_by_conference("Central") == [] + assert teams.find_teams_by_conference("Pacific") == [] + assert teams.find_teams_by_division("East") == [] + assert teams.find_teams_by_division("Midwest") == [] + # Falsy input is tolerated, not crashing. + assert teams.find_teams_by_conference(None) == [] + assert teams.find_teams_by_division("") == []