πβΎπ Newer ESPN leagues with sportsdataverse-py
Spring pro football is back, college diamonds are packed, and NCAA rinks are full. π
In a few lines of Python you're about to pull live scoreboards, schedules,
standings, team rosters, and play-by-play for seven leagues
added to sportsdataverse-py in the 0.0.59 ESPN expansion:
| Sport | Leagues |
|---|---|
| Spring / pro football | United Football League (UFL), XFL, Canadian Football League (CFL) |
| College baseball / softball | NCAA Baseball (college_baseball), NCAA Softball (college_softball) |
| NCAA hockey | Men's College Hockey (mch), Women's College Hockey (wch) |
Every league rides the same cross-league ESPN wrapper surface introduced in
0.0.51 β a single parameterized core (sportsdataverse._common_espn) with
thin per-league extension modules. That means identical function naming across
all leagues: espn_{prefix}_scoreboard, espn_{prefix}_team_schedule,
espn_{prefix}_standings, espn_{prefix}_team_roster, espn_{prefix}_summary,
and 80+ more short names per league.
Namespace noteβ
The canonical import paths are nested under the sport group:
from sportsdataverse.football.ufl import espn_ufl_scoreboard
from sportsdataverse.baseball.college_baseball import espn_college_baseball_teams_site
from sportsdataverse.hockey.mch import espn_mch_scoreboard
Top-level shortcuts (e.g. sportsdataverse.ufl) exist for back-compat but emit a
DeprecationWarning β prefer the nested paths shown throughout this notebook.
π§° The toolboxβ
Every wrapper returns a raw dict by default (return_parsed=False). Pass
return_parsed=True to get a tidy polars DataFrame where a parser is
registered, or return_as_pandas=True for a pandas DataFrame. The
function surface is identical across all seven leagues below.
| Function pattern | What it gives you |
|---|---|
espn_{prefix}_scoreboard(dates=YYYYMMDD) | Live / historical game slate |
espn_{prefix}_team_schedule(team_id=, season=) | Full team schedule for a season |
espn_{prefix}_teams_site() | All teams in the league (name, abbrev, logo, team_id) |
espn_{prefix}_standings() | Conference / division standings table |
espn_{prefix}_summary(event_id=) | Full game summary (~700 KB β box score, plays, leaders) |
espn_{prefix}_team_roster(team_id=, season=) | Roster for one team / season |
espn_{prefix}_game_plays(event_id=) | Play-by-play for one game |
espn_{prefix}_leaders() | League statistical leaders |
espn_{prefix}_rankings() | Polls / rankings (NCAA leagues) |
espn_{prefix}_news() | League news feed |
espn_{prefix}_injuries() | Injury report |
espn_{prefix}_player_info(athlete_id=) | Single-player bio / current info |
π Setupβ
pip install sportsdataverse
No API key required β all seven leagues hit ESPN's public endpoints.
import polars as pl
# Spring / pro football
import sportsdataverse.football.ufl as ufl
import sportsdataverse.football.xfl as xfl
import sportsdataverse.football.cfl as cfl
# College baseball + softball
import sportsdataverse.baseball.college_baseball as cbb
import sportsdataverse.baseball.college_softball as cbs
# NCAA hockey
import sportsdataverse.hockey.mch as mch
import sportsdataverse.hockey.wch as wch
pl.Config.set_tbl_rows(10)
print('sportsdataverse loaded β seven ESPN expansion leagues ready π')
sportsdataverse loaded β seven ESPN expansion leagues ready π
ESPN's live endpoints are seasonal and occasionally rate-limited, so a tiny
safe() helper runs each call defensively β you get the result when the feed
is up, and a friendly one-liner when it isn't (never a scary traceback). π
We also pin to a known completed slate for each league so code examples render consistently whether you run this in-season or in the off-season.
def safe(label, thunk):
try:
out = thunk()
print(f'β
{label}')
return out
except Exception as e: # noqa: BLE001 -- demo resilience
print(f'βοΈ {label}: unavailable right now ({type(e).__name__})')
return None
def _keys(d):
"""Print top-level keys of a raw dict for quick orientation."""
if isinstance(d, dict):
print('top-level keys:', list(d.keys())[:15])
elif d is None:
print('(no data)')
else:
print(type(d))
# Stable completed-season reference dates / ids for each league
UFL_DATE = 20240525 # UFL Championship Game, May 25 2024
XFL_DATE = 20230521 # XFL Championship Game, May 21 2023 (last full XFL season)
CFL_DATE = 20231119 # 111th Grey Cup, Nov 19 2023
CBB_DATE = 20240624 # NCAA Baseball CWS finals, June 24 2024
CBS_DATE = 20240606 # NCAA Softball WCWS finals, June 6 2024
MCH_DATE = 20240413 # Frozen Four semi, April 13 2024
WCH_DATE = 20240322 # NCAA Women's hockey Frozen Four, March 22 2024
SAMPLE_SEASON = 2024
print('reference constants set')
reference constants set
π Part 1 β Spring & Pro Football: UFL, XFL, CFLβ
The United Football League (UFL, formed from the merger of the XFL and USFL),
the XFL, and the Canadian Football League (CFL) are all first-class citizens of
the ESPN Site v2 API surface. Their modules expose the full _FOOTBALL_WRAPPERS
set β the same family as cfb and nfl β so you get deep scoring-play,
power-index, and drive-level detail out of the box.
π UFL: the scoreboardβ
espn_ufl_scoreboard(dates=YYYYMMDD)
returns the game slate for a specific date. The raw payload is a dict whose
events key holds one entry per game with nested team and status objects.
Pass return_parsed=True to get a flat polars frame.
# Raw payload β orient yourself with the top-level keys
board_raw = safe('UFL scoreboard (raw)', lambda: ufl.espn_ufl_scoreboard(dates=UFL_DATE))
_keys(board_raw)
β
UFL scoreboard (raw)
<class 'polars.dataframe.frame.DataFrame'>
# Parsed frame β one row per game
board = safe(
'UFL scoreboard (parsed)',
lambda: ufl.espn_ufl_scoreboard(dates=UFL_DATE, return_parsed=True),
)
if board is not None and getattr(board, 'height', 0):
keep = [c for c in board.columns
if c in ('id', 'name', 'short_name', 'status_type_description',
'home_team_abbreviation', 'home_score',
'away_team_abbreviation', 'away_score')]
board.select(keep).head() if keep else board.head()
else:
'scoreboard unavailable for that date'
β
UFL scoreboard (parsed)
π« UFL: teamsβ
espn_ufl_teams_site()
lists every team with its team_id β the key you feed into every
team-scoped call (espn_ufl_team_schedule, espn_ufl_team_roster, etc.).
There are no required arguments.
teams_ufl = safe('UFL teams', lambda: ufl.espn_ufl_teams_site(return_parsed=True))
if teams_ufl is not None and teams_ufl.height:
keep = [c for c in teams_ufl.columns
if c in ('id', 'abbreviation', 'display_name', 'location',
'color', 'alternate_color')]
teams_ufl.select(keep).head(10) if keep else teams_ufl.head(10)
else:
'teams unavailable'
β
UFL teams
π UFL: standingsβ
espn_ufl_standings()
returns the conference / division standings table. Pair with the
team id from the teams frame to build a standings dashboard.
standings_ufl = safe('UFL standings', lambda: ufl.espn_ufl_standings(return_parsed=True))
if standings_ufl is not None and standings_ufl.height:
keep = [c for c in standings_ufl.columns
if c in ('team_id', 'team_name', 'team_abbreviation',
'wins', 'losses', 'win_percent', 'points_for', 'points_against')]
standings_ufl.select(keep).head(8) if keep else standings_ufl.head(8)
else:
'standings unavailable'
β
UFL standings
π UFL: team scheduleβ
espn_ufl_team_schedule(team_id=, season=)
returns one row per game for a single team's season β useful for building
a results table or joining standings context onto a game-log.
# team_id=10000 is the Michigan Panthers (2024 UFL champions)
schedule_ufl = safe(
'UFL team schedule',
lambda: ufl.espn_ufl_team_schedule(team_id=10000, season=SAMPLE_SEASON,
return_parsed=True),
)
if schedule_ufl is not None and schedule_ufl.height:
keep = [c for c in schedule_ufl.columns
if c in ('id', 'week', 'name', 'short_name',
'home_team_abbreviation', 'away_team_abbreviation',
'home_score', 'away_score', 'status_type_description')]
schedule_ufl.select(keep).head() if keep else schedule_ufl.head()
else:
'schedule unavailable'
β
UFL team schedule
ποΈ UFL: game summary & play-by-playβ
espn_ufl_summary(event_id=)
returns the full ~700 KB Site v2 summary payload β box score, plays, drives,
scoring plays, leaders, odds, and more. You can extract any sub-frame directly
from the raw dict, or use the parse_summary parser (see the architecture
note at the end of this notebook).
espn_ufl_game_plays(event_id=) returns a lightweight play list for the same
game without the full summary overhead.
# 2024 UFL Championship game β find the event_id from the scoreboard above
# or look it up via espn_ufl_scoreboard(dates=UFL_DATE)
UFL_CHAMPIONSHIP_ID = 401671648
summary_ufl = safe(
'UFL game summary (raw)',
lambda: ufl.espn_ufl_summary(event_id=UFL_CHAMPIONSHIP_ID),
)
_keys(summary_ufl)
βοΈ UFL game summary (raw): unavailable right now (NoESPNDataError)
(no data)
# Quick play-by-play via the lightweight plays endpoint
plays_ufl = safe(
'UFL game plays (raw)',
lambda: ufl.espn_ufl_game_plays(event_id=UFL_CHAMPIONSHIP_ID),
)
_keys(plays_ufl)
βοΈ UFL game plays (raw): unavailable right now (NoESPNDataError)
(no data)
π XFL: scoreboard, teams & standingsβ
The XFL module (sportsdataverse.football.xfl) has the same shape.
The 2023 season is the most recent fully archived XFL slate before the
merger into the UFL.
# XFL teams β good cross-check since some franchises carried over to UFL
teams_xfl = safe('XFL teams', lambda: xfl.espn_xfl_teams_site(return_parsed=True))
if teams_xfl is not None and teams_xfl.height:
keep = [c for c in teams_xfl.columns
if c in ('id', 'abbreviation', 'display_name', 'location')]
teams_xfl.select(keep).head(10) if keep else teams_xfl.head(10)
else:
'XFL teams unavailable'
β
XFL teams
# XFL championship scoreboard β May 2023
board_xfl = safe(
'XFL scoreboard (parsed)',
lambda: xfl.espn_xfl_scoreboard(dates=XFL_DATE, return_parsed=True),
)
if board_xfl is not None and getattr(board_xfl, 'height', 0):
keep = [c for c in board_xfl.columns
if c in ('id', 'name', 'short_name', 'status_type_description',
'home_team_abbreviation', 'home_score',
'away_team_abbreviation', 'away_score')]
board_xfl.select(keep).head() if keep else board_xfl.head()
else:
'XFL scoreboard unavailable'
β
XFL scoreboard (parsed)
# XFL standings (2023)
standings_xfl = safe('XFL standings', lambda: xfl.espn_xfl_standings(return_parsed=True))
if standings_xfl is not None and standings_xfl.height:
keep = [c for c in standings_xfl.columns
if c in ('team_id', 'team_name', 'team_abbreviation',
'wins', 'losses', 'win_percent', 'points_for', 'points_against')]
standings_xfl.select(keep).head(8) if keep else standings_xfl.head(8)
else:
'XFL standings unavailable'
β
XFL standings
π CFL: scoreboard, teams, standings & scheduleβ
The Canadian Football League runs a 9-team league with a 18-game regular season
capped by the Grey Cup in November. The cfl module exposes the full
football wrapper set, including conference/division standings and the
power-index endpoints.
# CFL Grey Cup 2023 scoreboard
board_cfl = safe(
'CFL scoreboard (parsed)',
lambda: cfl.espn_cfl_scoreboard(dates=CFL_DATE, return_parsed=True),
)
if board_cfl is not None and getattr(board_cfl, 'height', 0):
keep = [c for c in board_cfl.columns
if c in ('id', 'name', 'short_name', 'status_type_description',
'home_team_abbreviation', 'home_score',
'away_team_abbreviation', 'away_score')]
board_cfl.select(keep).head() if keep else board_cfl.head()
else:
'CFL scoreboard unavailable'
β
CFL scoreboard (parsed)
# CFL teams
teams_cfl = safe('CFL teams', lambda: cfl.espn_cfl_teams_site(return_parsed=True))
if teams_cfl is not None and teams_cfl.height:
keep = [c for c in teams_cfl.columns
if c in ('id', 'abbreviation', 'display_name', 'location', 'color')]
teams_cfl.select(keep).head(10) if keep else teams_cfl.head(10)
else:
'CFL teams unavailable'
β
CFL teams
# CFL standings (2023)
standings_cfl = safe('CFL standings', lambda: cfl.espn_cfl_standings(return_parsed=True))
if standings_cfl is not None and standings_cfl.height:
keep = [c for c in standings_cfl.columns
if c in ('team_id', 'team_name', 'team_abbreviation',
'wins', 'losses', 'win_percent', 'points_for', 'points_against')]
standings_cfl.select(keep).head(9) if keep else standings_cfl.head(9)
else:
'CFL standings unavailable'
β
CFL standings
# CFL team schedule β Winnipeg Blue Bombers (team_id=100) 2023 season
schedule_cfl = safe(
'CFL team schedule',
lambda: cfl.espn_cfl_team_schedule(team_id=100, season=2023, return_parsed=True),
)
if schedule_cfl is not None and schedule_cfl.height:
keep = [c for c in schedule_cfl.columns
if c in ('id', 'week', 'name', 'short_name',
'home_team_abbreviation', 'away_team_abbreviation',
'home_score', 'away_score', 'status_type_description')]
schedule_cfl.select(keep).head(8) if keep else schedule_cfl.head(8)
else:
'CFL schedule unavailable'
β
CFL team schedule
βΎ Part 2 β College Baseball & Softballβ
NCAA Baseball and NCAA Softball sit under sportsdataverse.baseball.
Both modules expose the full _NCAA_WRAPPERS set β which adds
espn_{prefix}_rankings and espn_{prefix}_season_recruits on top of
the universal surface β giving you AP-style polls and recruiting boards
alongside the standard scoreboard/schedule/teams/standings family.
βΎ College baseball: scoreboard & teamsβ
espn_college_baseball_scoreboard(dates=YYYYMMDD)
returns the game slate for a date. The College World Series in June is peak
traffic, so games should be available; the off-season window (November β January)
returns an empty slate.
# College World Series finals day, 2024
board_cbb = safe(
'college baseball scoreboard (parsed)',
lambda: cbb.espn_college_baseball_scoreboard(dates=CBB_DATE, return_parsed=True),
)
if board_cbb is not None and getattr(board_cbb, 'height', 0):
keep = [c for c in board_cbb.columns
if c in ('id', 'name', 'short_name', 'status_type_description',
'home_team_abbreviation', 'home_score',
'away_team_abbreviation', 'away_score')]
board_cbb.select(keep).head() if keep else board_cbb.head()
else:
'college baseball scoreboard unavailable'
β
college baseball scoreboard (parsed)
# All NCAA baseball teams with their team_id
teams_cbb = safe(
'college baseball teams',
lambda: cbb.espn_college_baseball_teams_site(return_parsed=True),
)
if teams_cbb is not None and teams_cbb.height:
keep = [c for c in teams_cbb.columns
if c in ('id', 'abbreviation', 'display_name', 'location', 'color')]
print('Total teams:', teams_cbb.height)
teams_cbb.select(keep).head(10) if keep else teams_cbb.head(10)
else:
'teams unavailable'
β
college baseball teams
Total teams: 437
π College baseball: standings & rankingsβ
espn_college_baseball_standings()
returns conference standings. espn_college_baseball_rankings()
returns the current AP / USA Today poll β one of the NCAA extras added by the
_NCAA_WRAPPERS family.
# Conference standings
standings_cbb = safe(
'college baseball standings',
lambda: cbb.espn_college_baseball_standings(return_parsed=True),
)
if standings_cbb is not None and standings_cbb.height:
keep = [c for c in standings_cbb.columns
if c in ('team_id', 'team_name', 'team_abbreviation',
'wins', 'losses', 'win_percent')]
standings_cbb.select(keep).head(10) if keep else standings_cbb.head(10)
else:
'standings unavailable'
β
college baseball standings
# National polls β AP / USA Today baseball rankings
rankings_cbb = safe(
'college baseball rankings',
lambda: cbb.espn_college_baseball_rankings(return_parsed=True),
)
if rankings_cbb is not None and rankings_cbb.height:
keep = [c for c in rankings_cbb.columns
if c in ('rank', 'team_id', 'team_name', 'points', 'first_place_votes',
'wins', 'losses')]
rankings_cbb.select(keep).head(10) if keep else rankings_cbb.head(10)
else:
'rankings unavailable (only current during the season)'
β
college baseball rankings
π College baseball: team scheduleβ
Pick any team_id from the teams frame above and pull its full schedule.
Texas (team_id=251) is a perennial CWS contender β a good reference point.
# Texas Longhorns baseball 2024 schedule
schedule_cbb = safe(
'college baseball team schedule',
lambda: cbb.espn_college_baseball_team_schedule(
team_id=251, season=SAMPLE_SEASON, return_parsed=True
),
)
if schedule_cbb is not None and schedule_cbb.height:
keep = [c for c in schedule_cbb.columns
if c in ('id', 'name', 'short_name',
'home_team_abbreviation', 'away_team_abbreviation',
'home_score', 'away_score', 'status_type_description')]
schedule_cbb.select(keep).head(8) if keep else schedule_cbb.head(8)
else:
'schedule unavailable'
β
college baseball team schedule
π₯ College softball: scoreboard & teamsβ
The college_softball module mirrors college_baseball exactly β same wrapper
family, just a different league slug. The Women's College World Series (WCWS)
wraps up in early June.
# WCWS finals day, June 2024
board_cbs = safe(
'college softball scoreboard (parsed)',
lambda: cbs.espn_college_softball_scoreboard(dates=CBS_DATE, return_parsed=True),
)
if board_cbs is not None and getattr(board_cbs, 'height', 0):
keep = [c for c in board_cbs.columns
if c in ('id', 'name', 'short_name', 'status_type_description',
'home_team_abbreviation', 'home_score',
'away_team_abbreviation', 'away_score')]
board_cbs.select(keep).head() if keep else board_cbs.head()
else:
'college softball scoreboard unavailable'
β
college softball scoreboard (parsed)
# College softball teams
teams_cbs = safe(
'college softball teams',
lambda: cbs.espn_college_softball_teams_site(return_parsed=True),
)
if teams_cbs is not None and teams_cbs.height:
keep = [c for c in teams_cbs.columns
if c in ('id', 'abbreviation', 'display_name', 'location', 'color')]
print('Total teams:', teams_cbs.height)
teams_cbs.select(keep).head(10) if keep else teams_cbs.head(10)
else:
'teams unavailable'
β
college softball teams
Total teams: 446
# College softball rankings
rankings_cbs = safe(
'college softball rankings',
lambda: cbs.espn_college_softball_rankings(return_parsed=True),
)
if rankings_cbs is not None and rankings_cbs.height:
keep = [c for c in rankings_cbs.columns
if c in ('rank', 'team_id', 'team_name', 'points',
'first_place_votes', 'wins', 'losses')]
rankings_cbs.select(keep).head(10) if keep else rankings_cbs.head(10)
else:
'rankings unavailable (only current during the season)'
β
college softball rankings
π₯ College softball: game summaryβ
The same espn_{prefix}_summary(event_id=) pattern works for softball.
The raw dict contains a boxscore key (team batting/pitching lines),
a scoringPlays array, and β for parsed callers β all 21 sub-frames
from the shared parse_summary dispatcher.
# 2024 WCWS Championship Game 2 β Oklahoma vs Texas
CBS_CHAMPIONSHIP_ID = 401581278
summary_cbs = safe(
'college softball game summary (raw)',
lambda: cbs.espn_college_softball_summary(event_id=CBS_CHAMPIONSHIP_ID),
)
_keys(summary_cbs)
βοΈ college softball game summary (raw): unavailable right now (NoESPNDataError)
(no data)
π Part 3 β NCAA Men's & Women's College Hockeyβ
Men's College Hockey (mch) and Women's College Hockey (wch) round out the
seven-league expansion. Both expose the full _NCAA_WRAPPERS set. The
signature events are the Frozen Four in April (men's) and March (women's).
π Men's college hockey: scoreboard & teamsβ
espn_mch_scoreboard(dates=YYYYMMDD)
returns the game slate. espn_mch_teams_site()
lists all Division I programs β currently 60 teams.
# Frozen Four semi-final day, April 2024
board_mch = safe(
'men\'s college hockey scoreboard (parsed)',
lambda: mch.espn_mch_scoreboard(dates=MCH_DATE, return_parsed=True),
)
if board_mch is not None and getattr(board_mch, 'height', 0):
keep = [c for c in board_mch.columns
if c in ('id', 'name', 'short_name', 'status_type_description',
'home_team_abbreviation', 'home_score',
'away_team_abbreviation', 'away_score')]
board_mch.select(keep).head() if keep else board_mch.head()
else:
'men\'s college hockey scoreboard unavailable'
β
men's college hockey scoreboard (parsed)
# Men's college hockey teams
teams_mch = safe(
'men\'s college hockey teams',
lambda: mch.espn_mch_teams_site(return_parsed=True),
)
if teams_mch is not None and teams_mch.height:
keep = [c for c in teams_mch.columns
if c in ('id', 'abbreviation', 'display_name', 'location', 'color')]
print('Total D-I men\'s programs:', teams_mch.height)
teams_mch.select(keep).head(10) if keep else teams_mch.head(10)
else:
'teams unavailable'
β
men's college hockey teams
Total D-I men's programs: 116
π Men's college hockey: standings & rankingsβ
Conference standings are the primary puck-nerd query β which NCHC / Hockey East / Big Ten team leads their division? The rankings endpoint returns the USCHO / USA Today poll.
# Conference standings
standings_mch = safe(
"men's college hockey standings",
lambda: mch.espn_mch_standings(return_parsed=True),
)
if standings_mch is not None and standings_mch.height:
keep = [c for c in standings_mch.columns
if c in ('team_id', 'team_name', 'team_abbreviation',
'wins', 'losses', 'ties', 'win_percent')]
standings_mch.select(keep).head(10) if keep else standings_mch.head(10)
else:
'standings unavailable'
β
men's college hockey standings
# National rankings
rankings_mch = safe(
"men's college hockey rankings",
lambda: mch.espn_mch_rankings(return_parsed=True),
)
if rankings_mch is not None and rankings_mch.height:
keep = [c for c in rankings_mch.columns
if c in ('rank', 'team_id', 'team_name', 'points',
'first_place_votes', 'wins', 'losses', 'ties')]
rankings_mch.select(keep).head(10) if keep else rankings_mch.head(10)
else:
'rankings unavailable (only current during the season)'
β
men's college hockey rankings
π Men's college hockey: team schedule & rosterβ
Pull a program's full schedule with
espn_mch_team_schedule(team_id=, season=)
and their roster with
espn_mch_team_roster(team_id=, season=).
Boston University (team_id=103) won the 2024 national title β a good
reference point.
# BU Terriers schedule 2023-24
schedule_mch = safe(
"men's college hockey team schedule",
lambda: mch.espn_mch_team_schedule(team_id=103, season=SAMPLE_SEASON,
return_parsed=True),
)
if schedule_mch is not None and schedule_mch.height:
keep = [c for c in schedule_mch.columns
if c in ('id', 'name', 'short_name',
'home_team_abbreviation', 'away_team_abbreviation',
'home_score', 'away_score', 'status_type_description')]
schedule_mch.select(keep).head(8) if keep else schedule_mch.head(8)
else:
'schedule unavailable'
β
men's college hockey team schedule
# BU Terriers roster 2023-24
roster_mch = safe(
"men's college hockey roster",
lambda: mch.espn_mch_team_roster(team_id=103, season=SAMPLE_SEASON,
return_parsed=True),
)
if roster_mch is not None and roster_mch.height:
keep = [c for c in roster_mch.columns
if c in ('id', 'first_name', 'last_name', 'display_name',
'position_abbreviation', 'jersey', 'birth_country')]
roster_mch.select(keep).head(10) if keep else roster_mch.head(10)
else:
'roster unavailable'
βοΈ men's college hockey roster: unavailable right now (TypeError)
π© Women's college hockey: scoreboard, teams & scheduleβ
The wch module is the women's mirror. The NCAA Women's Frozen Four runs
in late March. Wisconsin is the perennial power β 12 national championships.
# NCAA Women's Frozen Four 2024
board_wch = safe(
"women's college hockey scoreboard (parsed)",
lambda: wch.espn_wch_scoreboard(dates=WCH_DATE, return_parsed=True),
)
if board_wch is not None and getattr(board_wch, 'height', 0):
keep = [c for c in board_wch.columns
if c in ('id', 'name', 'short_name', 'status_type_description',
'home_team_abbreviation', 'home_score',
'away_team_abbreviation', 'away_score')]
board_wch.select(keep).head() if keep else board_wch.head()
else:
"women's college hockey scoreboard unavailable"
β
women's college hockey scoreboard (parsed)
# Women's college hockey teams
teams_wch = safe(
"women's college hockey teams",
lambda: wch.espn_wch_teams_site(return_parsed=True),
)
if teams_wch is not None and teams_wch.height:
keep = [c for c in teams_wch.columns
if c in ('id', 'abbreviation', 'display_name', 'location', 'color')]
print("Total women's D-I programs:", teams_wch.height)
teams_wch.select(keep).head(10) if keep else teams_wch.head(10)
else:
'teams unavailable'
β
women's college hockey teams
Total women's D-I programs: 47
# Wisconsin Badgers women's hockey schedule 2023-24 (team_id=275)
schedule_wch = safe(
"women's college hockey team schedule",
lambda: wch.espn_wch_team_schedule(team_id=275, season=SAMPLE_SEASON,
return_parsed=True),
)
if schedule_wch is not None and schedule_wch.height:
keep = [c for c in schedule_wch.columns
if c in ('id', 'name', 'short_name',
'home_team_abbreviation', 'away_team_abbreviation',
'home_score', 'away_score', 'status_type_description')]
schedule_wch.select(keep).head(8) if keep else schedule_wch.head(8)
else:
'schedule unavailable'
β
women's college hockey team schedule
# Women's college hockey rankings
rankings_wch = safe(
"women's college hockey rankings",
lambda: wch.espn_wch_rankings(return_parsed=True),
)
if rankings_wch is not None and rankings_wch.height:
keep = [c for c in rankings_wch.columns
if c in ('rank', 'team_id', 'team_name', 'points',
'first_place_votes', 'wins', 'losses', 'ties')]
rankings_wch.select(keep).head(10) if keep else rankings_wch.head(10)
else:
"rankings unavailable (only current during the season)"
β
women's college hockey rankings
π³ Cookbook: cross-league recipesβ
A handful of patterns you'll reach for constantly across all seven leagues.
Recipe 1 β Collect all teams from every league into one frame ποΈβ
All seven modules expose espn_{prefix}_teams_site(). Stack them with
pl.concat to build a cross-league lookup table.
import importlib
LEAGUE_MODULES = [
('ufl', ufl, 'espn_ufl_teams_site'),
('xfl', xfl, 'espn_xfl_teams_site'),
('cfl', cfl, 'espn_cfl_teams_site'),
('college_baseball', cbb, 'espn_college_baseball_teams_site'),
('college_softball', cbs, 'espn_college_softball_teams_site'),
('mch', mch, 'espn_mch_teams_site'),
('wch', wch, 'espn_wch_teams_site'),
]
frames = []
for league_name, mod, fn_name in LEAGUE_MODULES:
fn = getattr(mod, fn_name)
df = safe(f'{league_name} teams', lambda fn=fn: fn(return_parsed=True))
if df is not None and df.height:
# parsed teams frames use team_id / team_display_name column names
id_col = 'team_id' if 'team_id' in df.columns else 'id' if 'id' in df.columns else df.columns[0]
name_col = 'team_display_name' if 'team_display_name' in df.columns else 'display_name' if 'display_name' in df.columns else df.columns[1]
frames.append(
df.select([id_col, name_col])
.rename({id_col: 'id', name_col: 'display_name'})
.with_columns(pl.lit(league_name).alias('league'))
)
if frames:
all_teams = pl.concat(frames, how='diagonal_relaxed')
print('Total teams across all 7 leagues:', all_teams.height)
all_teams.group_by('league').len().sort('len', descending=True)
else:
'no team data available right now'
β
ufl teams
β
xfl teams
β
cfl teams
β
college_baseball teams
β
college_softball teams
β
mch teams
β
wch teams
Total teams across all 7 leagues: 1067
Recipe 2 β Today's slate for any league π β
Pass dates= as YYYYMMDD to any scoreboard wrapper. Omitting the argument
returns ESPN's current default date (today or the most recent active day),
which is handy during the season.
from datetime import date
today_str = int(date.today().strftime('%Y%m%d'))
# Swap in any module + scoreboard function to check a different league
todays_cfl = safe(
f"today's CFL slate ({today_str})",
lambda: cfl.espn_cfl_scoreboard(dates=today_str, return_parsed=True),
)
if todays_cfl is not None and getattr(todays_cfl, 'height', 0):
print(f"{todays_cfl.height} game(s) today")
todays_cfl.head()
else:
'no CFL games today (or offseason)'
β
today's CFL slate (20260617)
Recipe 3 β Player info for any athlete π§βπ»β
Every league exposes espn_{prefix}_player_info(athlete_id=). Find an
athlete_id via the roster endpoint, then pull richer bio / career-stats
detail with espn_{prefix}_player_career_stats(athlete_id=).
# Look up a player from the men's college hockey roster we pulled earlier
# This uses the first athlete_id in the roster frame if it's available
if (
'roster_mch' in dir()
and roster_mch is not None
and roster_mch.height
and 'id' in roster_mch.columns
):
first_athlete_id = roster_mch['id'][0]
player_info = safe(
f'MCH player info (id={first_athlete_id})',
lambda: mch.espn_mch_player_info(athlete_id=first_athlete_id),
)
_keys(player_info)
else:
print('roster not loaded β skipping player info example')
roster not loaded β skipping player info example
Recipe 4 β News & injuries for any league π°β
espn_{prefix}_news() and
espn_{prefix}_injuries()
need no arguments β just call them for the latest feed. Both return raw
dicts; parsed versions are available where a parser is registered.
# UFL news (current, no date needed)
news_ufl = safe('UFL news', lambda: ufl.espn_ufl_news())
_keys(news_ufl)
# CFL injuries
injuries_cfl = safe('CFL injuries', lambda: cfl.espn_cfl_injuries())
_keys(injuries_cfl)
β
UFL news
<class 'polars.dataframe.frame.DataFrame'>
β
CFL injuries
<class 'polars.dataframe.frame.DataFrame'>
ποΈ The shared cross-league architectureβ
All seven leagues share exactly one parameterized core:
sportsdataverse._common_espn. The architecture is worth understanding
because it makes every league behave identically:
_common_espn.py
βββ ~80 core functions, each parameterized on (sport, league) slugs
βββ _UNIVERSAL_WRAPPERS β scoreboard, teams, standings, schedule, β¦
βββ _NCAA_WRAPPERS β +rankings, +season_recruits (CBB/CBS/MCH/WCH)
βββ _FOOTBALL_WRAPPERS β +drives, +powerindex, +draft (UFL/XFL/CFL)
βββ make_league_module(sport, league, prefix, namespace)
βββ _bind(core_fn, sport, league) β espn_{prefix}_{short}
sportsdataverse/football/ufl.py # 4 lines β calls make_league_module
sportsdataverse/football/xfl.py # 4 lines
sportsdataverse/football/cfl.py # 4 lines
sportsdataverse/baseball/college_baseball.py # 4 lines
sportsdataverse/baseball/college_softball.py # 4 lines
sportsdataverse/hockey/mch.py # 4 lines
sportsdataverse/hockey/wch.py # 4 lines
This means:
- Any bug fix in
_common_espnapplies to all 7 leagues at once. - Every league has an identical
dir()surface β the only difference is the prefix string (ufl,xfl,cfl,college_baseball, β¦). - The
return_parsed=Truepath routes throughENDPOINT_PARSERSβ a registry mapping each short name to a dedicated parser function (or a generic fall-through likeparse_items,parse_single_entity, orparse_summary). You can always get the raw dict by omitting the kwarg.
Total wrappers across all eight SDV leagues (including the 0.0.51 originals plus the 0.0.59 expansion): 819 registered functions.
Listing all functions on any league moduleβ
# All espn_ufl_* functions β the same list shape exists for every league
ufl_fns = [f for f in dir(ufl) if f.startswith('espn_')]
print(f'{len(ufl_fns)} functions on sportsdataverse.football.ufl')
print('sample:', ufl_fns[:8], '...')
110 functions on sportsdataverse.football.ufl
sample: ['espn_ufl_award', 'espn_ufl_awards', 'espn_ufl_calendar', 'espn_ufl_coach', 'espn_ufl_coach_record', 'espn_ufl_coach_season', 'espn_ufl_conferences', 'espn_ufl_draft'] ...
# Quick cross-league count
for name, mod in [('ufl', ufl), ('xfl', xfl), ('cfl', cfl),
('college_baseball', cbb), ('college_softball', cbs),
('mch', mch), ('wch', wch)]:
n = len([f for f in dir(mod) if f.startswith('espn_')])
print(f' {name:20s}: {n} wrappers')
ufl : 110 wrappers
xfl : 110 wrappers
cfl : 110 wrappers
college_baseball : 113 wrappers
college_softball : 113 wrappers
mch : 113 wrappers
wch : 113 wrappers
π See Alsoβ
- Spring / pro football reference docs: UFL Β· XFL Β· CFL
- College baseball / softball reference docs: College Baseball Β· College Softball
- NCAA hockey reference docs: Men's College Hockey Β· Women's College Hockey
- Cross-league architecture deep-dive:
docs/docs/architecture/espn-cross-league.md - Parser layer:
docs/docs/parsers/ - Companion notebooks:
02_cfb_intro.ipynb(CFB β the original football tutorial),07_nhl_intro.ipynb(NHL β native feed + ESPN fallback),09_mlb_intro.ipynb(MLB β Stats API + Statcast) - R package companions: cfbfastR Β· hoopR Β· wehoop Β· fastRhockey Β· baseballr