Table of Contents generated with DocToc
- Unreleased
- 0.0.67 Release: June 17, 2026
- 0.0.66 Release: June 17, 2026
- 0.0.65 Release: June 17, 2026
- 0.0.64 Release: June 17, 2026
- 0.0.63 Release: June 16, 2026
- 0.0.62 Release: June 16, 2026
- 0.0.61 Release: June 16, 2026
- 0.0.60 Release: June 15, 2026
- 0.0.59 Release: June 13, 2026
- CFB — cross-source crosswalk loaders (
load_cfb_*_crosswalk) - ESPN — NCAA men's & women's college hockey (
espn_mch_*,espn_wch_*) - ESPN — NCAA college baseball + softball (
espn_college_baseball_*,espn_college_softball_*) - ESPN — UFL, XFL, and CFL (
espn_ufl_*,espn_xfl_*,espn_cfl_*) - ESPN — soccer/cricket param families + soccer headline aliases (
espn_soccer_*(league=),espn_cricket_*(league=),espn_epl_*,espn_ucl_*,espn_mls_*, ...)
- CFB — cross-source crosswalk loaders (
- 0.0.58 Release: June 12, 2026
- Loaders — NHL core + new NBA/MBB datasets aligned to
sportsdataverse-datareleases - Robustness & infrastructure — typing, CI gates, HTTP, deprecation policy
- The Odds API wrappers (
sportsdataverse.odds,toa_*) - Yahoo Sports college football wrappers (
yahoo_cfb_*) - NFL —
api.nfl.comwrappers cut over to generated; "NFL.com API" docs grouping - NFL — automatic
api.nfl.comtoken caching +NFL_ACCESS_TOKENoverride - Documentation —
api.nfl.comOpenAPI spec - Bug fixes
- Internal — Fox data key single-sourced
- Loaders — NHL core + new NBA/MBB datasets aligned to
- 0.0.57 Release: June 10, 2026
- 0.0.56 Release: June 9, 2026
- HockeyTech — live multi-league scraper (PWHL + AHL/OHL/WHL/QMJHL) + on-ice/Corsi/TOI analytics
- NFL — Next Gen Stats (
nfl_ngs_*) + api.nfl.com football/v2 (nfl_*) modules - NFL — restored the api.nfl.com game schedule + play-by-play wrappers
- ESPN — remove always-erroring endpoint variants + NFL R-parity
- Documentation — per-league Python ↔ R parity tables
- Documentation — example notebooks repaired, expanded, and rendered on-site
- NHL / PWHL — loader naming-parity aliases + games-manifest loaders (fastRhockey parity)
- Documentation — NFL return-table descriptions mined from nflverse
- Documentation — class methods rendered on autodoc pages (CFB / NFL)
- Documentation — accuracy-audit fixes
- 0.0.55 Release: June 8, 2026
- 0.0.54 Release: June 8, 2026
- 0.0.53 Release: June 8, 2026
- ESPN — declarative codegen + factory retirement
- NHL native — codegen cutover + clean names (api-web; in progress)
- Dataset loaders — release manifest + drift audit
- Generated documentation — reference pages + drift gate
- CFB — advanced box score expansion (
create_box_score) - CFB — box-score attribution correctness + ESPN-sourced totals (
create_box_score) - CFB — play-type reclassification: interception-return-fumble guard (
__add_new_play_types) - CFB — blocked-kick turnover flags + ESPN native-flag tripwires
- CFB — pre-2014 era support (
CFBPlayProcess) - Removed — NCAA bracketology
- 0.0.52 Release: June 3, 2026
- 0.0.51 Release: May 30, 2026
- User-facing quality-of-life additions
- New: MLB module (greenfield)
- New: NHL —
api-web.nhle.commigration + EDGE / Stats REST / Records - New: ESPN cross-league port
- New: NCAA bracketology
- New:
_common_espn_parsers.py(polars / pandas parser layer) - New:
return_parsed=Truedispatch shim - New:
nhl_edge_parsers.py - New: Site v2 summary dispatcher (20 sub-parsers)
- New: 100% ENDPOINT_PARSERS coverage (121/121)
- New: weekly cron live-test drift detector
- New: MLB Stats API parser layer
- New: NHL Stats REST + Records parser layers
- New: NHL api-web parser layer
- Bug fixes
- New: NFL drive-plays parser (true PBP parity)
- Test infrastructure
- Documentation
- 0.0.50 Release: May 7, 2026
- Packaging modernization
- Conda installability
- Linting & pre-commit modernization
- Documentation toolchain
- Runnable docstring examples (~190 functions)
- Example notebooks
- Contributor docs and templates
- NFL — nflreadpy parity
- NFL — caching and configuration
- NFL — static datasets
- NFL — pickcenter / odds modern path
- NFL —
load_nfl_scheduleparquet port - WBB / WNBA — new ESPN scrape modules
- CFB —
cfb_play_participantsand__add_player_colscollapse - CFB — pandas → polars 1.x bug-fix reconciliation (
0.36-live→main) - Infrastructure and tooling
- Bug fixes
- Deprecations
- 0.0.40 Release: December 6, 2025
- 0.0.38-39 Release: August 28, 2023
- 0.0.36-37 Release: July 9, 2023
- 0.0.34-35 Release: May 7-9, 2023
- 0.0.18 Release: July 25, 2022
- 0.0.17 Release: July 9, 2022
- 0.0.15 Release: May 8, 2022
- 0.0.14 Release: March 16, 2022
- 0.0.12 Release: February 24, 2022
- 0.0.5 Release: October 20, 2021
Unreleased
Documentation — Great Docs site wired into the toolchain (executable guides + auto-introspected reference)
An alternative Great Docs site is now a first-class, reproducible part of the repo. It is additive and complementary to the production Docusaurus codegen docs — not a replacement: Docusaurus still owns the full ~800-wrapper breadth and the per-column returns tables, while Great Docs renders a curated public surface introspected live from the real Python objects (signature / params / returns / example) plus executable user-guide pages whose cells run against the live ESPN API at build time.
great-docsadded to thedocsoptional-dependencies extra (flows intoall); Quarto remains a separate system install (https://quarto.org). Build/preview withPYTHONUTF8=1 great-docs build(~95s →great-docs/_site/) /great-docs preview.- Curated
reference:allow-list ingreat-docs.ymlis load-bearing:import sportsdataverseregisters ~2,940 dynamic wrappers, and auto-discovery validates every name via a ~2s griffe call (~90-min walk → timeout), so the explicit allow-list skips that loop and keeps the build at ~95s / 85 pages / 95 symbols. - Informational, non-blocking CI (
.github/workflows/great-docs.yml) builds the site and uploadsgreat-docs/_siteas an artifact on changes togreat-docs.yml/user_guide/**/assets/**; the live-cell render iscontinue-on-errorso a flaky ESPN render never blocks a merge. The ephemeralgreat-docs/output stays gitignored. Background notes live inGREAT-DOCS-NOTES.md. Public deployment is left as a maintainer decision (the Docusaurus site already auto-deploys at the root).
0.0.67 Release: June 17, 2026
Documentation — return-table column descriptions filled (~3,061 columns)
Every generated reference page renders a col_name | type | description returns table; ~3,061 of those cells previously rendered blank because the column name had no entry in the R-package-mined dictionary that backfills descriptions at render time (sdv-py-/provider-specific columns: ESPN Site v2, MLB Stats API, NHL api-web / EDGE, nflverse Shield, HockeyTech, etc.). Those cells are now filled.
- New hand-curated source
tools/codegen/manual_column_descriptions.yaml, keyed by the schema'sschema:field (with a_globaltable-agnostic fallback), consumed at render time bygenerate.py:_table_cell_desc. Resolution order: captured-stored value →manual[schema][col]→manual._global[col]→ R-dict mined fill → empty. Descriptions live only here (theschemas/**.yamlare clobbered blank on every capture), so they survive re-capture. - Coverage: NFL (1,158 — nflverse / Next Gen Stats / Pro Football Reference / ESPN), MLB (599 — Stats API + ESPN), NHL (588 — api-web / EDGE / ESPN), CFB (177 — ESPN + cfbfastR), plus the ESPN cross-league game summary (sport-agnostic), NBA/WNBA/MBB/WBB, PWHL + CHL junior hockey (OHL/QMJHL/WHL/AHL), and the shared
standings/leaders/team_roster/news/team_scheduleschemas. - Regression guard:
tools/codegen/extract_residual_columns.pycomputes the render-blank residual;tests/codegen/test_manual_descriptions.pyasserts it stays at 0 (a newly-captured undocumented column fails CI until authored), plus an orphan guard (no stale dict keys) and a filler-lint (rejects terse/generic descriptions). Every bucket was adversarially accuracy-reviewed; corrections included PFRrec_br, MLBbase_on_balls, NHL EDGE goalie goal-differential / pbp assist totals, and the long-formatload_cfb_betting_linescolumns.
Documentation — doctest-prompt cleanup, native returns-tables, new tutorials
- No more raw
>>>doctest prompts. The generated ESPN-wrapper + loader docstring templates emitted>>> callunderExample:(whichsphinx.ext.doctestwould try to verify); both emission sites now produce the napoleonQuick start::literal block, clearing ~3,559 generated hazards. The remaining ~55 hand-written prompts (NFL NGS / parsers, The Odds API,find/discover, etc.) were converted in source. - 78 new native returns-tables. Wired
returns_schemafor NHL api-web (9), stats-rest (10), records (37), EDGE (15), and MLB Stats API (8) endpoints that previously rendered no return table — captured from live fixtures; the 676 new columns are fully described. (24 endpoints were skipped: off-season EDGE top-10 leaderboards, retired record paths, and auth-gated MLB endpoints.) refresh_return_schemasno longer writes 0-column per-league schemas — an emptycolumns: []file shadowed and suppressed the genericschemas/{name}.yamlfallback, leaving some leagues with no table; it now skips them so the generic table renders.- Three new intro tutorials under
examples/notebooks/(rendered todocs/docs/tutorials/): Soccer (espn_soccer_*(league=)+ headline aliases), Cricket (espn_cricket_*+ the 8-section matchcard summary), and Other ESPN leagues (UFL/XFL/CFL, college baseball/softball, NCAA M/W hockey).
NFL — PBP ETL ↔ nflfastR alignment + faithful model artifacts
enrich_nfl_pbp()lead-diff orchestrator computes nflverse-native EP/EPA/WP/WPA/CP/xYAC on a real nflverse PBP frame, aligned to nflfastR; runs on live nflverse data.- Shared derivations
calculate_epa()/calculate_wpa()lifted intosportsdataverse/nfl/ep_wp.py; the NFL EP/WP constants + shared column contract centralized insportsdataverse/nfl/model_vars.py. - Faithful NFL model artifacts replace the byte-identical CFB 8-feature placeholders that previously shipped under
nfl/models/:ep_model.ubj(18 features),wp_spread.ubj(12),wp_naive.ubj(11),cp_model.ubj(18) — resolving the long-standingxgboost num_feature >= num_col (8 vs 18)mismatch that left the NFL model path red. - New test coverage:
tests/nfl/gains enrich, enrich-derive, EPA, WPA, and column-contract suites.
CFB — EP + WP models retrained on the full 2004–2025 history
- Canonical
cfb/models/ep_model.ubjandcfb/models/wp_spread.ubjretrained on the complete cfbfastR-cfb-raw finals — 2,219,607 cleaned/labeled/weighted plays, seasons 2004–2025 — now that the raw backfill is complete. Shipped XGBoost recipes unchanged (EPmulti:softprob7-class/525 rounds; WP-spreadbinary:logistic/760 rounds). - Leave-one-season-out validated (22 folds, out-of-fold): EP mlogloss 1.233 / accuracy 0.500 / EP-value calibration MAE 0.014 pts; WP logloss 0.362 / Brier 0.118 / AUC 0.916 / weighted-cal-error 0.0147. Drop-in safe (feature names/order match
cfb_pbp.ep_final_names/wp_final_names). QBR is intentionally unchanged (LOSO R² 0.585 — remains the Dec-2020 canonical model).
0.0.66 Release: June 17, 2026
CFB — cfb_pbp sparse-game ColumnNotFoundError guard (end.team.id et al.)
Sparse pre-2010 games (e.g. 2005 game 252440154) crashed CFBPlayProcess.run_processing_pipeline() with polars.exceptions.ColumnNotFoundError: unable to find column "end.team.id". The per-play start.*/end.*/period.*/clock.*/type.* columns are produced only by pd.json_normalize flattening the plays array, so when no play in a game carries a given nested object the column is never created — and the downstream with_columns chain dereferences it via pl.col(...) unconditionally, which raises at plan time before the existing fill_null / when-otherwise logic can substitute a value.
Added a column-materialization guard in __helper_cfb_pbp_features (after the early-return length checks, before the main play chain) that diffs the 15 unconditionally-referenced json_normalize-origin columns against the live frame and creates any missing one as a Null literal:
- String-typed source columns (
clock.displayValue,type.text,text,start.downDistanceText) are created aspl.lit(None, dtype=pl.String)because the chain runs.str.*ops on them (an untyped Null column raisesSchemaError). - Numeric/bool columns stay untyped
pl.lit(None)so their explicit downstream.cast(...)owns the final dtype. - The guard is a no-op for healthy games —
with_columnsis skipped when nothing is missing, so output is byte-identical (verified: 5 control games reprocessed to identical 406-column frames and exact play counts). Resolves all 7 known-failing 2005 games.
0.0.65 Release: June 17, 2026
Namespace — minor/alias leagues nested under sport-group packages
- refactor(namespace): nest minor/alias leagues under sport-group packages (
sportsdataverse.soccer.epl,.hockey.ahl,.football.ufl,.baseball.college_baseball); the 8 majors + pwhl/soccer/cricket stay top-level. Legacy names (sportsdataverse.epl,import sportsdataverse.ufl) still resolve with aDeprecationWarning. NOTE:discover.function_count()/list_functions()keep flat-leaf keys (function_count(league="ufl")works);import sportsdataversenow eagerly loads the 12 soccer-alias submodules to support attribute access.
All sports — espn_*_game_rosters vectorized logo extraction
Pre-2010 ESPN team payloads omit the logos key entirely, causing helper_{sport}_team_items to raise polars.exceptions.ColumnNotFoundError: "logos" not found. The row-by-row item-assignment fallback (teams_df[row, "logo_href"] = ...) also triggers TypeError: the truth value of a Series is ambiguous in polars 1.x because the row-index selector internally evaluates Series.__bool__.
Replaced the logos block in all seven espn_*_game_rosters modules (cfb, mbb, nba, nfl, nhl, wbb, wnba) with vectorized with_columns:
if "logos" in teams_df.columns:guard handles pre-2010 payloads where the key is absent.pl.col("logos").list.get(i).struct.field("href").fill_null("")— expression-engine extraction, null-safe, no Python-level row iteration.except Exception:fallback to empty-string literals if the logos payload doesn't match the expectedList(Struct)shape.
MLB — mlb_api_* renamed to mlb_*
The 64 Stats API wrapper functions in mlb_api.py (generated via tools/codegen/endpoints/mlb_api.yaml) and the 15 hand-written functions in mlb_api_extra.py were renamed from mlb_api_{short} to mlb_{short} — parallel to the statcast_* → mlb_statcast_* rename in 0.0.64. The _api_ infix was a disambiguation artifact from when multiple backends shared the module; it is now redundant. No aliases — update call sites accordingly.
0.0.64 Release: June 17, 2026
MLB — comprehensive Baseball Savant / Statcast surface (mlb_statcast_*, 43 endpoints)
Expanded the Baseball Savant integration from a 12-endpoint representative slice to the full ~43-endpoint catalog under the mlb_statcast_<family>_<name> naming (search / leaderboard / gamefeed / player), with every endpoint parsed to a tidy frame by default (return_parsed=False / raw=True for the raw payload).
- 39 codegen-generated wrappers — 37 leaderboards (expected stats, sprint speed, bat tracking, pitch arsenals/movement/tempo, OAA, arm strength, catcher framing/blocking/throwing, baserunning, park factors, …) plus
mlb_statcast_gamefeed(one row per pitch) andmlb_statcast_schedule(one row per game). Savant mixes CSV / JSON / HTML, so the family uses a content-type-aware getter (dictfor JSON,strfor CSV/HTML); the two HTML-embedded leaderboards (fielding-run-value,statcast-park-factors) are parsed from their embeddeddata[]blob. - Hand-written search —
mlb_statcast_search(+_minors,_wbc) auto-chunks the 25,000-row Savant cap and translates friendly filters (season,pitch_type,at_bat_result,batters_lookup, …) to Savant'shf*params.mlb_statcast_playerparses a player page'sserverValssection (defaultstatcast, ~260 metrics) to a tidy frame (section=for others,raw=Truefor HTML). - Returns-schemas (
col_name | type | description) for every frame-returning function, andexamples/notebooks/09_mlb_intro.ipynbmodernized to the new surface. - The pre-0.0.64
statcast_*names were renamed (no aliases) to themlb_statcast_*convention.
Documentation — nfl_api (NFL.com Shield) returns-schema tables
Added live-captured col_name | type | description returns-schemas for all 11 api.nfl.com endpoints (standings, rosters, teams_history, team, weeks, weeks_by_date, combine_profiles, draft_picks, injuries, game_summaries, weekly_game_details), wired via returns_schema: into nfl_api.yaml and rendered into the reference docs — bringing nfl_api to parity with the other six native API families (mlb_api, nhl_*). Docs/codegen-metadata only; no runtime change.
0.0.63 Release: June 16, 2026
All sports — espn_*_game_rosters diagonal per-team concat (fixes silent roster loss)
The per-team roster concat in espn_wbb/wnba/nba/mbb/nfl/cfb_game_rosters used pl.concat(..., how="vertical"), which hard-fails with polars.exceptions.ShapeError when a game's two teams ship different roster columns (e.g. one entry list has jersey, the other didNotPlay). The whole game then errored and was discarded as empty despite having roster data. Switched to how="diagonal" (union + null-fill), matching nhl_game_rosters and the teams/athletes concats in the same modules.
HTTP — download() no longer retries a definitive 404
sportsdataverse.dl_utils.download retried a NoESPNDataError (ESPN 404 / code:404 body) for the full num_retries budget — wasting ~51s of backoff and N requests per genuinely-absent resource, amplifying load against a rate-limited host. A 404 is definitive "no data", so it now fails fast (one attempt) instead of retrying. Connection/timeout/5xx errors still retry as before.
0.0.62 Release: June 16, 2026
All sports — espn_*_game_rosters robust to long-tail ESPN payloads
Applies the two espn_cfb_game_rosters robustness fixes from 0.0.61 to every sibling rosters builder — espn_wbb_game_rosters, espn_wnba_game_rosters, espn_nba_game_rosters, espn_mbb_game_rosters, espn_nhl_game_rosters, and espn_nfl_game_rosters — which were templated from the same source and shared both bugs verbatim:
statistics_hrefstrict-rename of the competitors payload now renames only keys actually present, so older games that omit the team-levelstatistics$refno longer raisepolars.exceptions.ColumnNotFoundError.- Per-team roster 404 is now tolerated: a single team's missing
/roster(NoESPNDataError) no longer fails the whole game; the other team's roster is recovered, andNoESPNDataErroris raised only when every team is empty.
Adds parametrized offline regression tests across all six modules (tests/test_sibling_game_rosters.py).
0.0.61 Release: June 16, 2026
CFB — espn_cfb_game_rosters robust to long-tail ESPN payloads
Surfaced by the 2004–2023 cfbfastR-cfb-raw backfill, two deterministic failures used to empty a game's rosters entirely (then get caught upstream and banked as empty "hollow" extras):
statistics_hrefstrict-rename. Older games (e.g. pre-2021) omit the team-levelstatistics$refin the competitors payload, sostatistics_hrefnever exists and the unconditionalitems.rename({..., "statistics_href": "team_statistics_href"})raisedpolars.exceptions.ColumnNotFoundErrorfor the whole game. The renamed column is unused downstream, so the rename now applies only to keys actually present.- Per-team roster 404. A single team's
/rostersub-endpoint can 404 (NoESPNDataError) — common for older games and FCS opponents — while the other team's roster exists. The per-team loop now skips a 404 team and recovers the other, raisingNoESPNDataErroronly when every team is empty (genuinely no roster data).
Adds offline helper unit tests (tests/cfb/test_cfb_game_rosters.py, no network).
0.0.60 Release: June 15, 2026
NFL — expected points, win probability, completion probability (CP/CPOE), and expected YAC (XYAC) models
sportsdataverse.nfl.ep_wp gains nflfastR-parity modeling functions — calculate_expected_points, calculate_win_probability, calculate_completion_probability (CP + CPOE), and calculate_xyac (four XYAC sub-models: mean/median/SD yardage + completion probability) — fed by ESPN-adapter feature builders and wired into NFLPlayProcess. Ships the bundled XGBoost .ubj model files.
CFB — espn_cfb_schedule guards null-competitor placeholder events
ESPN's 2010 and 2014 college-football scoreboards include placeholder events with null competitions/competitors. espn_cfb_schedule now skips those events instead of raising TypeError: 'NoneType' object is not subscriptable and failing the entire season.
0.0.59 Release: June 13, 2026
CFB — cross-source crosswalk loaders (load_cfb_*_crosswalk)
New 404-safe dataset loaders read pre-built CFB identity crosswalks from the cfb_crosswalk release tag on sportsdataverse-data, so callers can translate ids across providers without re-scraping every source. They cache the output of the live cfb_teams_crosswalk / cfb_schedule_crosswalk / cfb_rosters_crosswalk builders (ESPN × Fox × Yahoo, keyed on an aggressively-normalized team name; see sportsdataverse.cfb.cfb_crosswalk) — a full-season schedule build otherwise fans out hundreds of requests across three providers.
ESPN — NCAA men's & women's college hockey (espn_mch_*, espn_wch_*)
- feat(espn): add NCAA men's & women's college hockey (espn_mch_, espn_wch_)
ESPN — NCAA college baseball + softball (espn_college_baseball_*, espn_college_softball_*)
- feat(espn): add NCAA college baseball + softball (espn_college_baseball_, espn_college_softball_)
ESPN — UFL, XFL, and CFL (espn_ufl_*, espn_xfl_*, espn_cfl_*)
- feat(espn): add UFL, XFL, and CFL (espn_ufl_, espn_xfl_, espn_cfl_*)
ESPN — soccer/cricket param families + soccer headline aliases (espn_soccer_*(league=), espn_cricket_*(league=), espn_epl_*, espn_ucl_*, espn_mls_*, ...)
-
feat(espn): add league-parameterized soccer + cricket families (espn_soccer_(league=), espn_cricket_(league=)) + soccer headline aliases (espn_epl_, espn_ucl_, espn_mls_*, ...)
-
feat(soccer): full-parity soccer parsers — scoreboard→matches, standings→league table (group column), summary→11-section dispatcher (header/lineups/key_events/team_stats/commentary/leaders/standings/head_to_head/last_five/game_info/shootout), teams, roster — routed via per-sport codegen overrides; feat(cricket): cricket parsers — scoreboard, standings, summary→8-section matchcard dispatcher (batting/bowling/partnerships)
-
load_cfb_teams_crosswalk(seasons=)andload_cfb_schedule_crosswalk(seasons=)are per-season (min_season2014) — teams and schedules are genuinely historical, published per year for 2014–2025. -
load_cfb_rosters_crosswalk()is season-less: ESPN's and Fox's team-roster endpoints expose only the current roster, so the artifact is a single snapshot (noseasons=argument) rather than a misleading per-season series.
All accept return_as_pandas=. Artifacts are produced by cfbfastR-cfb-data/scripts/build_cfb_crosswalk.py (the rosters table fans the per-team cfb_rosters_crosswalk out over the current season's ESPN↔Fox team-id pairs and concatenates them with espn_team_id / fox_team_id provenance). The companion on-demand builder cfb_odds_events_crosswalk (The Odds API event-id ↔ ESPN game-id) remains live-only — near-term events aren't worth caching.
0.0.58 Release: June 12, 2026
Loaders — NHL core + new NBA/MBB datasets aligned to sportsdataverse-data releases
The four core NHL loaders (load_nhl_pbp, load_nhl_player_boxscore, load_nhl_team_boxscore, load_nhl_schedule) now read the SDV-native sportsdataverse-data releases (nhl_pbp_full, nhl_player_boxscores, nhl_team_boxscores, nhl_schedules) instead of the legacy R fastRhockey-data branch — gaining the 2010 season (min_season 2011 → 2010). Added loaders for NBA/MBB datasets that were already published but had no loader, bringing them to parity with the WBB/WNBA surface: load_nba_player_season_stats, load_nba_team_season_stats, load_nba_draft, load_nba_rosters, and load_mbb_standings, load_mbb_player_season_stats, load_mbb_team_season_stats, load_mbb_rosters, load_mbb_officials, load_mbb_game_rosters — each with a generated return-schema table. Also fixed the --audit-releases drift check to key on the release tag (it parsed the human-readable title), which had been falsely flagging valid releases as missing.
Robustness & infrastructure — typing, CI gates, HTTP, deprecation policy
A package-wide hardening pass with no change to public data outputs:
- Typing + CI: ships a PEP 561
py.typedmarker; a newquality.ymlCI gate runsruff+ruff format --check+mypyon every PR, with a[tool.mypy] filesratchet (modules join the strict gate as they reach clean typing), and the test workflow now emits coverage. - Errors + logging: a
SportsDataverseErrorbase class (withSeasonNotFoundError/NoESPNDataErrorre-parented under it) and a package logger with aNullHandler; previously-silentexceptpaths now log. - HTTP layer:
dl_utils.download()reuses a module-level pooledrequests.Sessionand backs off honoringRetry-After(numeric and RFC 7231 HTTP-date, clamped non-negative, 120s ceiling) instead of a fixed sleep. - Deprecation policy: a centralized
sportsdataverse._deprecation(warn_deprecated+@deprecated) with a documented removal window; the 11 per-type NFL loader aliases migrated to it. - Codegen determinism: generator output is LF-only on every platform and the ruff format pass is pinned to the project's ruff (no CRLF phantom diffs); idempotency tests lock it in.
- Tests: a VCR-style record/replay harness (committed cassettes, secret-scrubbing) exercises the real
download()→ parser call path offline.
The Odds API wrappers (sportsdataverse.odds, toa_*)
New sportsdataverse.odds module wrapping The Odds API v4 — live + historical sports betting odds, scores, events, markets and participants across a wide range of bookmakers. Mirrors the sister R package oddsapiR's toa_* surface: toa_sports, toa_sports_odds, toa_sports_scores, toa_sports_events, toa_event_odds, toa_event_markets, toa_sports_participants, the three *_history snapshot variants, and toa_usage (cached quota, no network). The odds endpoints return tidy long-format frames (one row per event × bookmaker × market × outcome). Auth resolves from the ODDS_API_KEY env var (same variable as oddsapiR) or an api_key= argument; the call routes through the shared dl_utils.download() gateway. Same return_parsed / return_as_pandas contract (polars by default). Built from the the_odds_api OpenAPI spec.
Yahoo Sports college football wrappers (yahoo_cfb_*)
Read-only Yahoo Sports wrappers for college football over Yahoo's shangrila stats graph (graphite-secure.sports.yahoo.com/v1/query/shangrila) and editorial feed (api-secure.sports.yahoo.com/v1/editorial/s): yahoo_cfb_player_season_stats, yahoo_cfb_team_season_stats, the legacy per-category *_season_stats_legacy variants, yahoo_cfb_scoreboard, and a yahoo_cfb_boxscore scaffold. Same return_parsed / return_as_pandas contract (polars by default).
NFL — api.nfl.com wrappers cut over to generated; "NFL.com API" docs grouping
The hand-written sportsdataverse.nfl.nfl_api wrappers (nfl_standings, nfl_rosters, nfl_injuries, …) are now generated from tools/codegen/endpoints/nfl_api.yaml, like the NHL/MLB native families. The flat-API codegen gained getter_module + auth support so an authenticated family (the NFL.com WEB_DESKTOP bearer token) can be generated; the auth getter lives in nfl_api_runtime.py and the per-endpoint record extraction in nfl_api_parsers.py. As a result the NFL docs index now lists a dedicated "NFL.com API" reference grouping (11 functions) instead of burying those wrappers in "Additional functions". Wrapper signatures gain return_parsed / **kwargs.
NFL — automatic api.nfl.com token caching + NFL_ACCESS_TOKEN override
The api.nfl.com bearer token is now minted once and cached in-process, then auto-renewed just before its JWT exp — so back-to-back nfl_* / nfl_api_* calls reuse a single token instead of POSTing to /identity/v3/token on every call, with no setup and no manual refresh. A new optional NFL_ACCESS_TOKEN env var injects a pre-minted bearer token verbatim (skipping the mint + cache); the existing NFL_CLIENT_KEY / NFL_CLIENT_SECRET credential overrides still apply. nfl_clear_token_cache() forces a fresh mint, and nfl_token_gen(force_refresh=True) re-mints on demand.
Documentation — api.nfl.com OpenAPI spec
Added an OpenAPI 3.1 description of the modern NFL.com "Shield" data API (api.nfl.com: /identity/v3/token device-token auth + /football/v2/* + /experience/*) to the reference repos (sdv-internal-refs/nfl/, sdv-swagger/nfl_api_openapi.yaml).
Bug fixes
load_nfl_players()now reads the nflverse players release (players/players.parquet) on both the polars and pandas paths; the default polars path previously returned the officials dataset by mistake.- The generated
api.nfl.comwrappers route their HTTP call through the sharedsportsdataverse.dl_utils.download()gateway (retries + cache + ESPN-aware error handling) like every other wrapper, instead of callingrequests.get()directly. Boolean query flags and thenfl_weeksseason/season_typepath params are hardened soNonecan no longer leak onto the wire.
Internal — Fox data key single-sourced
sportsdataverse.cfb.cfb_fox_ext.FOX_DATA_KEY is now imported from sportsdataverse._fox_layout.DATA_KEY so the bundled public Fox key and its SDV_PY_FOX_DATA_KEY env override live in exactly one place instead of being duplicated.
0.0.57 Release: June 10, 2026
Fox Sports Bifrost wrappers (CFB, NBA, MBB, NHL, MLB)
Read-only Fox Sports "Bifrost" wrappers (fox_<sport>_*) over api.foxsports.com/bifrost/v1/<sport>/*, complementing the espn_<sport>_* families. The Bifrost API is a layout API (sections → tables → rows → cells) that is uniform across sports; a shared parsing layer (sportsdataverse/_fox_layout.py) backs every league module. Same return_parsed / return_as_pandas contract (polars by default).
CFB (cfb module): fox_cfb_pbp (quarters → drives → plays), fox_cfb_boxscore, fox_cfb_odds, fox_cfb_team_roster, fox_cfb_team_stats, fox_cfb_team_gamelog, fox_cfb_standings, fox_cfb_league_leaders.
NBA / MBB / NHL (nba / mbb / nhl modules): the same eight wrappers per sport (fox_<sport>_pbp, _boxscore, _odds, _team_roster, _team_stats, _team_gamelog, _standings, _league_leaders). Play-by-play is period-based (QUARTER / HALF / PERIOD → plays); boxscore is tidy long per player-stat.
MLB (mlb module): fox_mlb_team_roster, fox_mlb_team_stats, fox_mlb_team_gamelog, fox_mlb_standings, fox_mlb_league_leaders, fox_mlb_odds. Fox does not expose MLB play-by-play or boxscore via event/{id}/data, so those two are intentionally omitted.
Live-tested (gated behind SDV_PY_LIVE_TESTS=1). Reverse-engineering notes + an OpenAPI 3.1 spec live in the sdv-internal-refs repo. Parallels the cfbfastR / hoopR / fastRhockey / baseballr fox_* families.
CFB — Fox as a backup source for the EPA/WPA play processor (fox_cfb_play_process)
Where fox_cfb_pbp returns the raw Fox play rows, fox_cfb_play_process runs Fox data through the same CFBPlayProcess pipeline ESPN games use — producing EPA / WPA / advanced box score — as a backup/alternative when ESPN is unavailable. The new module sportsdataverse.cfb.cfb_pbp_fox adapts a Fox cfb/event/{id}/data payload into the ESPN-summary shape the processor consumes (fox_to_espn_summary), so the 6,000-line pipeline runs unmodified.
fox_cfb_play_process(event_id)— fetch + adapt +run_processing_pipeline(orprocess=Falsefor cleaning-only,raw=Truefor the adapted summary). Returns the processed payload taggedsource="fox".fox_to_espn_summary(fox_data)— the adapter (modalPlay.events[].yardStart→ yards-to-goal, play title → down/distance,events[].text→ ESPNtype.textvocab, team logo → possession).- Validated offline (5 tests) on a captured blowout (FSU 66-10 → FSU +0.50 vs Kent −0.94 EPA/play — game-consistent). High fidelity on the structured/numeric path (down/distance/yards-to-goal/EPA/WPA); text-grammar features (detailed player attribution, penalty yards) degrade vs ESPN. Archive-format Fox games (no
modalPlaygeometry) are detected and rejected. A Fox event id differs from an ESPN game id; backing up a specific ESPN game needs matching by teams + date.
0.0.56 Release: June 9, 2026
HockeyTech — live multi-league scraper (PWHL + AHL/OHL/WHL/QMJHL) + on-ice/Corsi/TOI analytics
A new sportsdataverse.hockeytech core powers live wrappers over the HockeyTech
feeds, alongside the existing offline load_pwhl_* loaders:
- PWHL (
sportsdataverse.pwhl): 20 livepwhl_*()functions at fastRhockey output parity —pwhl_schedule,pwhl_scorebar,pwhl_game_info,pwhl_game_summary,pwhl_pbp,pwhl_player_box,pwhl_teams,pwhl_team_roster,pwhl_standings,pwhl_player_info,pwhl_player_stats,pwhl_player_game_log,pwhl_player_search,pwhl_stats,pwhl_leaders,pwhl_streaks,pwhl_transactions,pwhl_playoff_bracket,pwhl_season_id, andmost_recent_pwhl_season. - AHL / OHL / WHL / QMJHL (
sportsdataverse.{ahl,ohl,whl,qmjhl}): per-league families (schedule, pbp, standings, teams, team_roster, player_stats, leaders, game_summary, season_id,most_recent_<lg>_season) over one shared core. - Analytics across all five leagues:
<lg>_game_shifts,<lg>_player_toi, and<lg>_game_corsi(player-level on-ice Corsi/Fenwick), reconstructed from the shift tables via countdown-clock interval matching.<lg>_pbpis enriched to a superset (coordinate transforms, clock columns, shot distance/angle, scoring chances, on-ice players, game-meta join,blocked_shot/hitevents). - Corsi/Fenwick caveat: the HockeyTech feed has no missed-shot event, so both
metrics are computed from shots-on-goal + blocked + goals and every analytics
output carries
corsi_includes_missed = False. - All returned columns are snake_case; PWHL columns match fastRhockey exactly. A companion fastRhockey (R) release mirrors this surface, verified by a cross-language parity test pinning identical Corsi/TOI numbers.
NFL — Next Gen Stats (nfl_ngs_*) + api.nfl.com football/v2 (nfl_*) modules
- New
sportsdataverse/nfl/nfl_ngs.py— token-free Next Gen Stats wrappers overnextgenstats.nfl.com/api(browser session, no auth). 10 functions / 21 endpoints:nfl_ngs_statboard(passing/receiving/rushing),nfl_ngs_statboard_leaders,nfl_ngs_leaders(speed/distance/time-to-sack + completion/ery/yac expectation, season & week),nfl_ngs_league_schedule[_current],nfl_ngs_league_teams,nfl_ngs_gamecenter_overview,nfl_ngs_microsite_chart[_players],nfl_ngs_play_is_highlight. The/live/*NGS endpoints are anonymous-403 (need elevated auth) and are documented as omitted. - New
sportsdataverse/nfl/nfl_api.py—api.nfl.com/football/v2+/experiencewrappers on the bearer token (reusesnfl_headers_gen). 11 functions:nfl_standings,nfl_rosters,nfl_teams_history,nfl_team,nfl_weeks,nfl_weeks_by_date,nfl_combine_profiles,nfl_draft_picks,nfl_injuries,nfl_game_summaries,nfl_weekly_game_details. - Both return tidy polars DataFrames by default (
return_as_pandassupported) and are documented on the NFL reference pages. Catalogued from a full crawl of the NFL API surface (api.nfl.com + NGS). - Captured autodoc return-column tables for all 23 new NFL functions (live
introspection ->
schemas/autodoc/nfl/*.yaml+autodoc_example_args.yaml), so each renders acol_name | type | descriptiontable on its reference page.
NFL — restored the api.nfl.com game schedule + play-by-play wrappers
nfl_game_schedule/nfl_game_detailswere broken because NFL.com retired the old/v1/rerouteclient-credentials token endpoint (404 ->JSONDecodeError). Rebuiltsportsdataverse/nfl/nfl_games.pyon the modern flow the NFL.com web app (and nflverse'snflapi) now use:nfl_token_gen()mints a bearer token from/identity/v3/token(form-encoded device grant,X-Domain-Id: 100);nfl_game_schedule()reads/football/v2/games/season/{s}/seasonType/{t}/week/{w};nfl_game_details()reads/experience/v1/gamedetails/{id}and unwraps the shielddata.viewer.gameDetailobject (plays, drives, scoring summaries, line scores).- Auth uses the NFL.com public
WEB_DESKTOPweb-client credentials as defaults, overridable viaNFL_CLIENT_KEY/NFL_CLIENT_SECRETenv vars or function args (no personal account; the token carries the anonymousfreeplan). Verified live: 16 games for 2024 REG wk1, 194 plays / 20 drives for the opener. - Added a parsed surface over the raw dicts:
nfl_game_pbp(game_id)returns a tidy polars/pandas DataFrame (one row per play, withgame_id/home_team/visitor_teamcontext), andnfl_week_games(season, season_type, week)returns one row per game. (Named to avoid colliding with thenfl_pbp/nfl_schedulesubmodules.)
ESPN — remove always-erroring endpoint variants + NFL R-parity
- Removed dead ESPN endpoint variants (all leagues). A live health sweep found
these generated wrappers 404 /
NoESPNDataErrorat ESPN for every league and season: the season-lessespn_*_coacheslist (/leagues/{league}/coaches) and the fourespn_*_calendar_{offseason,regular_season,postseason,ondays}sub-paths. They are dropped from the codegen so the package no longer ships endpoints that always raise. The working counterparts remain:espn_*_season_coaches(/seasons/{season}/coaches), the coach-detail endpoints (espn_*_coach, ...), and the baseespn_*_calendar. (~40 dead functions removed across 8 leagues.) - NFL Python ↔ R parity. Added curated
r_parity_aliases.yamlentries mapping the canonicalload_nfl_*loaders to their nflreadr equivalents (e.g.load_nfl_pbp→load_pbp,load_nfl_schedule→load_schedules), so the NFL parity table links both naming styles (nfl rows 26 → 49). Theload_nfl_*/ bareload_*dual-naming itself was verified already consistent (intentional nflreadpy parity; the only unaliasedload_nfl_*are deprecated or sdv-specific).
Documentation — per-league Python ↔ R parity tables
- Each league's
index.mdnow carries a Python ↔ R parity table mapping everysportsdataversefunction to its equivalent in the sister R package (cfbfastR / hoopR / wehoop / baseballr / fastRhockey), linking the Python doc page and the R pkgdown reference. Driven by a newtools/codegen/build_r_exports.pyminer (NAMESPACE → committedr_exports.yaml, so links never 404 and the offline--checkstays deterministic) plus a curatedr_parity_aliases.yamlfor divergent names (e.g.mlb_api_*→ baseballrmlb_*, +36 verified). Coverage: nhl 202, mlb 107, wnba 83, nba/wbb 74, mbb 69, cfb 55, nfl 26, pwhl 15. - Fixed a self-referential codegen bug the parity table exposed:
render_autodoc_pagecomputed "already documented" against a corpus that included the index, so the index's parity table (which names autodoc functions) caused those functions to be dropped fromadditional.mdand their parity links to 404. It now uses the reference-pages corpus only, matching the autodoc-name count used for the index.
Documentation — example notebooks repaired, expanded, and rendered on-site
- Repaired the example notebooks. Live execution (
nbclient) surfaced runtime schema/usage drift that import/compile checks miss: ESPN schedule team columns renamed tohome_display_name/away_display_name;espn_*_pbp()['plays']is a raw list using dot-notation keys (period.number,clock.displayValue,scoringPlay,shootingPlay,coordinate.x/.y) built viapl.DataFrame(...); ESPN scores are strings (cast before arithmetic);espn_cfb_scheduletakesdates=notseason=; ESPN team rosters usefull_name;espn_*_team_statsreturns a dict{Averages, Totals, Misc}; some hardcoded dates had no games. All notebooks now execute clean end-to-end. - Split + expanded the suite to ten notebooks. The combined
wbb_wnbanotebook was split into separate05_wbb_introand08_wnba_intro, both expanded; the NHL notebook gained an ESPN-NHL section alongside the native api-web surface; and two new notebooks were added:09_mlb_intro(MLB Stats API + Statcast + ESPN MLB) and10_pwhl_intro(PWHL loaders). - On-site rendered Tutorials. New
tools/codegen/render_notebooks.pyexecutes each notebook and renders it (with real outputs, as clean monospace tables) to a themed page underdocs/docs/tutorials/, surfaced in a new Tutorials sidebar section. Execution is quarantined to the weeklylive-tests-cronworkflow, which now re-executes + renders and opens a refresh PR (main is branch-protected); the normal offline docs build just consumes the committed pages. Each league index's Examples section now links the on-site tutorial pages instead of GitHub.
NHL / PWHL — loader naming-parity aliases + games-manifest loaders (fastRhockey parity)
- Added 4 NHL short-name aliases in
sportsdataverse/nhl/nhl_loaders.py:load_nhl_team_box→load_nhl_team_boxscore,load_nhl_player_box→load_nhl_player_boxscore,load_nhl_skater_box→load_nhl_skater_boxscores,load_nhl_goalie_box→load_nhl_goalie_boxscores. - Added 5 PWHL short-name aliases in
sportsdataverse/pwhl/pwhl_loaders.py:load_pwhl_team_box→load_pwhl_team_boxscores,load_pwhl_player_box→load_pwhl_player_boxscores,load_pwhl_skater_box→load_pwhl_skater_boxscores,load_pwhl_goalie_box→load_pwhl_goalie_boxscores,load_pwhl_schedule→load_pwhl_schedules. - Added
load_nhl_games()(noseasonsarg) reading the NHL games-in-data-repo manifest parquet from thenhl_schedulesrelease asset (primary URL verified working:sportsdataverse-data/releases/download/nhl_schedules/nhl_games_in_data_repo.parquet). - Added
load_pwhl_games()(noseasonsarg) reading the PWHL games-in-data-repo manifest parquet from thepwhl_schedulesrelease asset (primary URL verified working:sportsdataverse-data/releases/download/pwhl_schedules/pwhl_games_in_data_repo.parquet). - Added
tests/test_loader_parity.pycovering importability,__all__membership, docstring-based forwarding assertions, and live alias shape-parity + manifest tests (gated behindSDV_PY_LIVE_TESTS=1).
Documentation — NFL return-table descriptions mined from nflverse
- Extended
tools/codegen/build_r_col_descriptions.pywith two nflverse source parsers:mine_csv_dictionaries()reads nflreadr's canonicaldata-raw/dictionary_*.csvfield docs (delimiter-sniffing for the semicolon-delimited NGS file, BOM-stripping forroster_status, and case-insensitive Field/Description column resolution across 6 header variants), andmine_item_list()reads nflfastR'sdata-raw/variable_list.txt(\item{Field}{Description}form). Yieldsnflreadr(941 columns) andnflfastR(372 columns) dictionaries inr_column_descriptions.yaml. - Mapped
nfl → nflreadringenerate.py's_LEAGUE_R_PACKAGE; nflfastR's fields still contribute via the_mergedcross-package fallback. - NFL generated reference-page description fill rose from ~36% to ~85%. The
enlarged
_mergedunion (7.3k → 8.1k columns) also backfilled previously-blank shared football/stat columns on the CFB and MLB reference pages (e.g.passing_yards,receptions,kicker_player_name,name_short).
Documentation — class methods rendered on autodoc pages (CFB / NFL)
- Hand-written classes (
CFBPlayProcess,NFLPlayProcess) previously rendered on theadditionalreference pages as a bare constructor signature with no description and an empty parameter table — their public methods, returns, and examples were omitted entirely. The autodoc renderer now treats a class specially:_doc_view()attaches a per-method doc-view list (via_augment_class_view()), and theautodoc_page.md.jinjatemplate renders each public method as a nested#### Class.method(...)entry with its description, parameters, returns, and example. Both classes now document all 7 of their public methods (espn_*_pbp,*_pbp_disk,*_pbp_json,corrupt_pbp_check,create_box_score,run_cleaning_pipeline,run_processing_pipeline). - Constructor parameter descriptions are backfilled from the class's
__init__docstring when the class object itself carries none (CFBPlayProcessdocuments its ctor args on__init__), so the constructor parameter table now renders with descriptions instead of blank cells. - Added a class-level docstring to
CFBPlayProcess(it had none) mirroringNFLPlayProcess, so the class entry leads with an overview + runnable example instead of aNo description available.placeholder.
Documentation — accuracy-audit fixes
- Stable autodoc anchors. Every autodoc function/class heading now carries an
explicit
{#name}id, so it is reliably deep-linkable instead of relying on a signature-derived slug. This fixes a broken cross-link inecosystem.md(espn_nhl_teamsnow resolves to itsadditionalpage entry) and future-proofs any reference to a hand-written wrapper. - Invalid example code.
_clean_example()mis-handled reST literal-block intros that wrap across multiple prose lines (only the line ending in::was recognized), leaking a prose sentence into the rendered```pythonblock as a broken statement. It now absorbs the preceding contiguous intro lines into the step comment. Fixes theNflConfigandespn_wbb_team_statsexamples; all non-REPL doc examples now compile. - Notebook reachability.
ecosystem.mdnow links all seven example notebooks individually (previously only01_quickstartwas linked; the per-sport intros 02–07 were an un-linked "for your league" mention). Each league's generatedindex.mdlanding page also gained an Examples section linking the quickstart plus that sport's intro notebook (render_league_index+ a league→notebook map ingenerate.py); mlb/pwhl show the quickstart until they get a dedicated intro.
0.0.55 Release: June 8, 2026
Documentation — richer per-function reference
- Autodoc "Additional functions" pages now render full Parameters tables (name/type/default/description), Returns, and runnable Example blocks parsed from each function's docstring (previously just a signature + one line).
- Endpoint reference pages gained a Description column on the parameter table; shared query params carry authored descriptions.
- Function Returns are now
col_name | type | descriptiontables: endpoint pages from introspected per-sport schemas, and autodoc DataFrame functions from a newgenerate.py --autodoc-schemaslive-introspection pass (best-effort, with prose fallback where a function can't be introspected offline). - Return-table column descriptions are filled by column name from the sibling
SDV R packages'
@returndocs (cfbfastR / hoopR / wehoop / baseballr), mined totools/codegen/r_column_descriptions.yamland applied at render time (hand-curated descriptions take precedence; unmatched columns stay blank).
Bug fixes
espn_mbb_game_rosters/espn_wbb_game_rosters/espn_nfl_game_rosters: fixed aShapeError(positional column rename broke when ESPN ships extra*_$reffields); columns are now renamed by source key.espn_nhl_schedule: fixed'NoneType' object has no attribute 'get'with default args (a helper was missing itsreturn event).- The
espn_*_game_rostersrename-by-source-key fix is additive: NBA/WNBA roster frames now include ateam_alternate_ids_sdrcolumn when ESPN ships it (the old positional rename would have raised once that field appeared).
0.0.54 Release: June 8, 2026
Per-sport return schemas (correctness)
@return tables are now derived per league by running the real parsers against
captured per-sport fixtures (generate.py --schemas), replacing the previous
sport-agnostic schemas that showed (e.g.) basketball boxscore columns on MLB/NHL
pages. Native API pages (nhl_api_web/nhl_edge/nhl_records/nhl_stats_rest/
mlb_api) gained accurate return schemas. Schemas are now introspected truth,
gated by generate.py --check.
BREAKING — parser-backed wrappers return a DataFrame by default
return_parsed now defaults to True for the parser-backed wrappers; they
return a tidy polars DataFrame instead of the raw Dict. Pass
return_parsed=False to recover the raw Dict; return_as_pandas=True switches
polars→pandas. Wrappers without a registered parser are unchanged (still Dict).
The sportsdataverse.parsed.{league} mirror modules are unaffected.
Docs coverage gate + autodoc
Every user-facing function now reaches the docs. A new generate.py --coverage
audit enumerates in-scope exported functions per league and fails --check if
any is undocumented (allowlist for cross-cutting internals in
tools/codegen/coverage_allowlist.yaml). ~180 hand-written wrappers/loaders/
statcast/utility functions that the endpoint-YAML codegen never documented are
now rendered into per-league "Additional functions" reference pages
(autodoc from live signatures + docstrings).
MLB - full MLB Stats API coverage
The codegen now wraps the full statsapi.mlb.com surface: 38 previously
unwrapped endpoints were added (home run derby, all-star ballots, conferences,
free agents, game pace, jobs/datacasters/official-scorers, team coaches/
personnel, schedule variants, seasons/all, sport, teams history/stats, etc.).
28 are publicly serviceable and ship with captured fixtures + introspected
return schemas; the remaining handful are auth-gated/internal MLBAM feeds
(analytics/guids/color), wrapped with valid example args for if/when access
exists.
Deprecations
sportsdataverse.parsed.{league}is deprecated (since the default modules now return parsed DataFrames by default). Importing aparsed.*module emits aDeprecationWarning; it still works and will be removed in a future release. Migrate tofrom sportsdataverse.{league} import <fn>directly.
0.0.53 Release: June 8, 2026
ESPN — declarative codegen + factory retirement
The runtime "magic" that mass-registered each league's espn_<league>_* ESPN
wrappers at import time (_common_espn.make_league_module / _bind + the
_UNIVERSAL_WRAPPERS / _NCAA_WRAPPERS / _FOOTBALL_WRAPPERS / _MLB_WRAPPERS
tables + ~127 private _site_v2_* / _espn_* / _core_v2_* core functions) has
been replaced by a declarative codegen pipeline (tools/codegen/). Endpoint
metadata lives in tools/codegen/endpoints/*.yaml; generate.py renders concrete,
fully-documented wrapper modules into sportsdataverse/<league>/<league>_espn_ext.py.
- New
espn_nhl_*surface (115 functions). NHL previously had no ESPN cross-league wrappers; it now gets the full Site v2 / Web v3 / Core v2 surface, andfind()works for NHL for free. - Identical behavior, real signatures. Every generated function builds a
byte-identical URL + query string to the function it replaced (verified by a
URL+params parity gate across all scopes), but now exposes concrete parameter
names, type hints, and docstrings instead of an opaque
*args, **kwargsshim. - Names aligned to the R sister packages (universal, token-level convention).
Across all eight leagues the generated
espn_*names follow the cfbfastR/hoopR/wehoop taxonomy (behavior unchanged). The rename is applied at the underscore-token level (not just prefixes), soathlete/eventconvert in every position incl. plurals:athlete -> player(athlete_vs_athlete -> player_vs_player,athletes_index -> players_index,season_athletes -> season_players),event -> game(bareevent -> game,events -> games,event_* -> game_*,season_week_events -> season_week_games). Two combined mappings run first:event_competitor* -> game_team*(a competitor is the game's team) andevent_competition -> game_competition/event_competition_* -> game_*. Compound tokens likeeventlogare preserved (athlete_eventlog -> player_eventlog). cfb additionally getsseason_*cleanups vs cfbfastR (futures/groups/recruits/week_rankings;powerindex -> team_powerindex). Rule engine:generate._convention_rename; cfb-specific exceptions:tools/codegen/espn_rename_map.yaml. - Collision-guarded. Renames that would clash with a hand-written sibling or
another generated name are skipped automatically:
teams_site(raw endpoint, != parsedespn_*_teams) andespn_cfb_season_{team,awards,coaches}(vs the catalog). SAME-endpoint duplicates are dropped: the generated rawespn_{wbb,wnba}_game_officialsis suppressed (viaespn_rename_map.yamldrop:) because the hand-written parsedespn_{wbb,wnba}_game_officials(renamed fromevent_officials, core-api officials with ids) exposes the same endpoint. One->many splits (e.g.summary) remain for curation (seedocs/superpowers/specs/espn-r-naming-worksheet.md). - Versioned collision rule (dynamic, "one stays bare"). When a generated name
would collide with an existing function but they hit different endpoints, both are
kept: ONE keeps the bare name and the larger/newer one is version-qualified. This is
now decided dynamically by the generator (
_league_module_sourcepass 2 +_versioned_on_collision), not hard-coded. The web-common-v3/athletes/{id}/statsendpoint wants the bareplayer_stats; it is version-qualified toespn_*_player_stats_v3only when a hand-written bareplayer_statsalready claims the name — a league without that sibling would get the bare name automatically (no orphaned*_v3). - Cross-league
player_statsparity (core-v2 season) for ALL eight ESPN leagues. Every league now exposes a bareespn_<league>_player_stats(core-v2/seasons/{season}/types/{type}/athletes/{id}/statisticsseason line) returning one wide, self-describing row (athlete identity + season line as{category}_{stat}columns +team_*identity), plus the generatedespn_<league>_player_stats_v3(web-v3 comprehensive) — matching the hoopR/wehoop/cfbfastR convention exactly. nba, mbb, nfl, nhl, mlb, and cfb gain new hand-written wrappers; wnba/wbb were already converted. A single sport-aware core (sportsdataverse._common_espn_player_stats._espn_player_stats) backs all eight (basketball/football/baseball/hockey share the core-v2splits.categories[].stats[]shape and athlete/team$refgraph). Newseason_type("regular"/"postseason") andtotalparams mirror the wehoop signature. BREAKING:espn_wnba_player_stats/espn_wbb_player_statspreviously hit web-v3 and returned adictof category frames; they now return a single core-v2 seasonDataFrame(the web-v3 payload moved to*_player_stats_v3). _get/_csvsingle source. The HTTP + coercion helpers now live insportsdataverse._codegen_runtime(shared by all generated wrappers);_common_espnre-exports them. Note for test authors: mocksportsdataverse._codegen_runtime.download(not_common_espn._get) to intercept the generated wrappers.- Drift guard.
python tools/codegen/generate.py --check(and thesdv-codegenpre-commit hook) fail if the committed wrappers fall out of sync with the endpoint metadata.
BREAKING (internal): sportsdataverse._common_espn no longer exposes the
factory (make_league_module / _bind / the _*_WRAPPERS tables) or the private
_site_v2_* / _core_v2_* core functions. Public espn_<league>_* wrappers are
unchanged in name and behavior.
NHL native — codegen cutover + clean names (api-web; in progress)
The hand-written NHL native modules are being regenerated from endpoint specs
(via tools/codegen/extract_native.py -> flat-API YAML -> generate.py) with
clean, R-aligned names, family by family. First family: nhl_api_web.
- BREAKING renames (
nhl_web_* -> nhl_*where the clean name is free; the qualifier is kept only on collision with a hand-written composite, sonhl_web_pbpandnhl_web_scheduleare unchanged): e.g.nhl_web_boxscore -> nhl_boxscore,nhl_web_standings -> nhl_standings,nhl_web_roster -> nhl_roster,nhl_web_scoreboard -> nhl_scoreboard(26 functions; full map intools/codegen/rename_map.yaml). Behavior (URL + params + parser) is identical -- faithfulness was verified bytest_parity_nativebefore the swap. nhl_scoreboard(the 3-way team/date/now branch) stays hand-written innhl_api_web_extra.py-- the single-URL-builder codegen can't represent it.- Removed the deprecated
sportsdataverse.nhl.nhl_apimodule (targeted the retiredstatsapi.web.nhl.com); usenhl_api_web/nhl_pbpinstead. nhl_edge(family 2) andnhl_stats_rest(family 3) are now generated too. Both keep their meaningful API namespaces (nhl_edge_*,nhl_stats_rest_*) so they are non-breaking codegen-ifications (35 + 21 functions). stats_rest's arbitrary**filterspower feature (cayenneExp/sort/limit/...) is preserved via a newpassthrough_queryengine mode that forwards None-filtered**kwargsas query params;return_parsedis additionally wired where a parser exists.nhl_records(family 4) is generated too -- keptnhl_records_*(distinct records.nhl.com product), non-breaking (50 functions: 44 generated +passthrough_query, 6 value-embedded/scope-conditional ones preserved hand-written innhl_records_extra.py).mlb_api(family 5, final) is generated too -- keptmlb_api_*(the raw MLB Stats API namespace, distinct from the curatedmlb_*composites), non-breaking (41 names: 26 generated +passthrough_queryfor hydrate/fields, 15 conditional-_csv/ multi-param //api/v1.1/-host functions preserved hand-written inmlb_api_extra.py).- All five native families are now codegen-generated. Only
nhl_api_webwas a breaking rename (itswebqualifier was host-noise); the other four kept their meaningful API namespaces. The codegen engine gained flat-API collision resolution (FlatApi.qualifier+resolve_name),passthrough_query, and abuild_flat/--checkdrift gate.test_parity_nativelocked in each family's faithfulness before its swap.
Dataset loaders — release manifest + drift audit
releases.yamlmanifest expanded 24 -> 92 loaders, seeded from the live sportsdataverse-data release list: every release tag shipping season-partitioned*.parquetassets gets a 404-safe loader entry whose URL is derived from the actual asset names (verified to resolve). New coverage: WNBA (espn_wnba_*+wnba_stats_*), PWHL (15 datasets, a new league), NHL (fullnhl_*family incl. EDGE/lite/boxscores), WBB, NBA, MBB.generate.py --audit-releasescompares the manifest against the live release list (gh CLI) and reports tags with no loader (gaps) + orphans -- a CI-oriented drift gate (separate from the offline--check).tests/codegen/fixtures/release_tags.txtsnapshots the live tags for offline coverage tests.- Release tags that don't yet ship parquet (empty / csv-only / season-less -- e.g.
several
espn_cfb_*advanced-box tags,nba_stats_*boxscores) are intentionally absent and surfaced by the audit until parquet lands. @returncolumn tables (Task 4): every non-stub loader's parquet footer is introspected intotools/codegen/schemas/loader_schemas.yaml(92 datasets) and rendered as a|col_name|type|table in the generated loader docstrings (reproducible viagenerate.py --loader-schemas).- All loader modules are now generated (Task 5 complete). The new
sportsdataverse.pwhlleague (15 loaders) plus the six existing leagues (cfb/mbb/nba/nhl/wbb/wnba) are rendered from the manifest into{league}/{league}_loaders.py-- expanding from 4 hand-written loaders per league to the full release-backed set (nhl 24, wnba 25, wbb 11, nba 9, pwhl 15, ...), each with@returncolumn tables. Zero loss (verified before/after): the season-less / helper functions the loop template can't express are preserved hand-written in{league}_loaders_extra.pyresiduals --cfb:load_cfb_betting_lines+get_cfb_teams;nhl:nhl_teams. The codegenbuild/--checkdrift gate covers all generated loader modules (_GENERATED_LOADER_LEAGUES). Verified live:load_pwhl_pbp(2024)-> 10,456 rows,load_nhl_pbp_lite(2010)-> 400,512,load_wnba_shots(2024)-> 45,480.
Generated documentation — reference pages + drift gate
generate.py --docsrenders the full reference docs tree from the same endpoint/loader/parameter metadata that drives the wrappers, directly into the live Docusaurus "Next" surface (docs/docs/{league}/). 64 files: per-leagueindex.md+_category_.json, a per-API reference page for every ESPN API (site/web/core) and native flat family (nhl_api_web/nhl_edge/nhl_stats_rest/nhl_records/mlb_api), areference/loaders.mdper loader league, and a sharedreference/parameters.md. This replaces the legacy Sphinx apidoc dumps — the 7 per-leagueindex.mdSphinx pages plus the hand-authored NHL/MLB conceptual pages were regenerated/removed; package-wide conceptual pages (intro,quality-of-life,architecture/,parsers/) are preserved untouched.- 8-section function block (
templates/_reference_block.jinja): summary, endpoint URL, a concrete valid example URL, an nba_api-style| API Parameter | Python | Pattern | Required | Nullable |table, a@returncolumn table sourced from thereturns_schema(handles bothkind: dataframeand multi-framekind: framespayloads), a runnablepythonexample, and a validated-date line. - Names never drift from code: the per-endpoint name-resolution passes were
extracted into shared
_espn_league_views()/_flat_views()helpers used by both the module renderer and the docs renderer, so a reference page always documents the exact wrapper name that gets emitted (e.g. the collision-qualifiednhl_web_pbpalongside the cleannhl_boxscore). - Drift gate:
--checknow also fails on stale generated docs (and orphans inside the fully-generated league/reference/dirs — conceptual pages outside them are never flagged); the defaultbuildwrites them.tools/codegen/fetch_packages.pysnapshots the SDV package list for an optional packages page (network tool; the gate stays offline by omitting the page when no snapshot is committed). New offline teststests/codegen/test_docs.py+test_doc_parity.pyassert the 8-section contract across every league x API and that the live tree is current. - Docusaurus migration:
docs/sidebars.tsnow drives each league as a clickable category (link → generatedindex) expanding to an autogenerated reference subtree, so new endpoints surface in the nav with no sidebar edit; added a top-level "Parameter reference" entry. The legacy Sphinx pipeline (create_docs.sh+Sphinx-docs/) is deleted, along with its now-unusedsphinx/sphinx-markdown-builder/sphinx-materialdev dependencies (dropped from thedocsextra/group + theallextra;uv.lockre-resolved).yarn buildpasses with a link-clean/docs/next/surface (remaining broken-anchor warnings are confined to the frozen0.0.50version + the CHANGELOG doctoc fragments). - Example notebooks are CI-executed (
nbmake): the example notebooks were audited against the post-rename API — only02_cfb_introbroke (the standaloneespn_cfb_pbp(game_id=...)is gone), and its PBP cells were rewritten to theCFBPlayProcess(gameId=...).espn_cfb_pbp()+.run_processing_pipeline()flow (verified live).nbmakewas added to thetestdependency group, and the weeklylive-tests-cronworkflow now runspytest --nbmake examples/notebooks/as an informational (non-blocking) leg so notebook breakage surfaces as drift. - Cohesive intro docs: a new Ecosystem & philosophy
page ties the docs together — the design philosophy, the full function-naming
paradigm (
espn_<league>_*, native<league>_*,load_<league>_*,parse_*, plus the R-aligned athlete→player / event→game conventions and collision rules), the Python ↔ R sister mapping (hoopR / wehoop / cfbfastR / baseballr / fastRhockey, plus oddsapiR / recruitR / sportyR / sportypy / sportsdataverse.js), and how the package relates to nflverse (the NFL module mirrors nflreadpy) and the wider PySport ecosystem.intro.mdand all seven example notebooks now open with a consistent philosophy/naming blurb and link to it. The page also documents the companion data repositories (sportsdataverse-data releases, cfbfastR-data, fastRhockey-data, nflverse-data) behind theload_*family and links each league's generated Automation status loader table, and includes a 1:1 function map — a table whosesportsdataverse-pyfunctions deep-link to their reference pages and whose R-sister functions link to the matching hoopR/wehoop/cfbfastR/fastRhockey/baseballr pkgdown docs (verified against each package's NAMESPACE). The ESPN cross-league architecture page was realigned from the retiredmake_league_module()runtime factory to the current declarative-codegen reality. - Docs default flipped to the overhauled tree + per-release snapshot policy:
docusaurus.config.tssetslastVersion: 'current'(labelledmain), so the generated reference + conceptual docs are the live DEFAULT at the root/docs/and auto-refresh on every deploy instead of sitting at/docs/next/behind the frozen 0.0.50 Sphinx dumps; the legacy docs stay archived at/docs/0.0.50/. The site builds on Vercel on push tomain(no in-repo deploy workflow — a GitHub Pages action would double-publish). At each release, freeze a per-release archive with the newcd docs && yarn version:docs <x.y.z>helper (keepingcurrent/mainthe default) — so the live docs never drift from the code (codegen--check-gated) yet every release still gets a frozen record. The release step is documented in CLAUDE.md. - Home page refreshed:
docs/src/pages/index.tsxwas rewritten from the stale MBB/CFB/EPA cards to the full current surface — Basketball / Football / Baseball / Hockey (incl. native NHL & MLB APIs, loaders, the tidy-by-default parser layer) — each card naming its R sister, with an "Ecosystem & philosophy" call-to-action. - Declined follow-up: a data-driven SDV navbar dropdown —
projects.jsoncarries no canonical doc URLs, so the curated navbar indocusaurus.config.ts(which has them) stays authoritative.
CFB — advanced box score expansion (create_box_score)
CFBPlayProcess.create_box_score() (and therefore run_processing_pipeline()'s
advBoxScore) now emits two additional per-player sections alongside the existing eight:
defensive_players— per-defender havoc events attributed by player and defensive team:sacks(+sacks_yards),pass_breakups,interceptions(+interceptions_yards),forced_fumbles,fumble_recoveries(+fumble_recoveries_yards). Keyed bydef_pos_team+player_name. Columns present vary per game (only populated stats appear); all values derive from existing enriched play columns (no new tracking data).specialists— per-player kicking/punting/return production keyed bypos_team+player_name:field_goals(+field_goals_yards),punts(+punts_yards),kick_returns(+kick_returns_yards),punt_returns(+punt_returns_yards).
Both are additive and degrade to [] when no events are attributable. The existing
pass/rush/receiver/team/situational/defensive/turnover/drives sections are
unchanged.
CFB — box-score attribution correctness + ESPN-sourced totals (create_box_score)
A correctness pass on team/player attribution in the advanced box score, reconciled against ESPN's official box score for a 5-game fixture set (all turnover totals now match ESPN exactly). All output is additive — existing field names are preserved; previously wrong values are corrected and new fields/sections are added.
- Per-play attribution layer (
__add_attribution_cols): resolves the credited team for every play from the play text + flags, aware thatpos_team/def_pos_teamswap roles by play type (on a kickoffpos_teamis the receiving team; on a punt it is the punting team). Produceskicking_team,return_team,fumbling_team,recovery_team,recovery_team_2,penalized_team, and per-side turnover flags. - Special-teams turnovers are now counted. Previously the turnover box filtered to
scrimmage plays, dropping muffed punts, kickoff-return fumbles, and blocked-kick
recoveries; these are now included. Muffs (
"muffed by …") are detected as fumbles, and overturned plays ("(Original Play: …)"after a reversed review) are stripped before parsing so a reversed fumble is not counted. - Per-side turnover model. A single play can register a turnover for both teams via
is_pos_team_turnover/is_def_pos_team_turnoverand a 2-deep recovery chain — e.g. an interception returned and fumbled back, or a sack-strip where the recovering defense fumbles it back. Turnover margins/luck are keyed by team identity (fixing a prior group-order bug that could swap or sign-flip them). Theturnoverlist is now ordered[home, away]and every row carriesteam_id— consumers should key byteam_idrather than list position (the previous order came from an unordered group-by). - Correct team attribution for fumble recoveries (own recoveries credited to the
recovering team, not always the defense), punt returns (credited to the returning team,
not the punting team), and penalty yards (charged to the penalized team via
penalty_yards, with the legacytotal_pen_yardsretained). - End-of-period play-drop fix. A dedup heuristic was dropping the real play immediately before an "End of period/half/game" marker (which inherits its start state) — losing end-of-half turnovers such as a Hail Mary interception. Guarded so end markers never trigger dedup of the preceding play.
- ESPN-sourced totals. New
espn_teamandespn_playerssections surface ESPN's official box verbatim (turnovers, fumbles lost, interceptions, total/passing/ rushing yards, penalties, first downs, player stat lines) as the authoritative source for countable totals. Theturnoversection sourcesturnovers/Int/fumbles_lostfrom the ESPN box (espn_sourced=True), keeping the play-by-play derivation under*_pbpkeys as the fallback and as a validated cross-check. - Clean player names.
run_processing_pipeline()joins ESPN's per-play participants (espn_cfb_play_participants) to replace regex-extracted names (which carried team prefixes, e.g."BYU Dayan Ghanwoloku") with clean display names, with graceful fallback to the regex names when offline. Setjoin_participants = Falseto skip the fetch (used by offline reprocessing and the offline test suite).
CFB — play-type reclassification: interception-return-fumble guard (__add_new_play_types)
-
Interceptions are no longer mislabeled as fumble recoveries. The "strip-sack → fumble" reclassification rules fire on
fumble_vec & pass & change_of_poss==1. An interception also setschange_of_poss=1, so a pick whose returner subsequently fumbled matched the predicate and was relabeled"Fumble Recovery (Opponent)", erasing the interception (and, because the downstreamintflag is derived fromtype.text, zeroing it for EPA/WPA and the box score). Both pass strip-sack rules now additionally requiretype.textnot be an interception label (int_vec), so these plays keep their interception classification (normalized to"Interception Return"later in the method). Genuine strip-sacks — and the rush strip-sack rule, which cannot match an interception — are unchanged. Verified across a 20-game / 3,439-play before/after diff: exactly one play changed (Fumble Recovery (Opponent)→Interception Return), zero other plays affected. -
Post-attribution play-type refinement (
__refine_play_types_post_attribution). A new pipeline step (after__add_attribution_cols) corrects two labels that need the turnover signal the step-5 reclassifier lacks (it can only seechange_of_poss, which isTrueon every possession flip, not just turnovers):- A sack-fumble the offense recovers itself was relabeled
Fumble Recovery (Opponent)(spuriouschange_of_poss);is_turnover == FalserestoresFumble Recovery (Own). - A punt-return fumble the punting team recovers (
recovery_team == pos_team) becomesPunt Team Fumble Recoveryinstead of stayingPunt Return.
Only the package's own first-pass relabels are undone (guarded on
orig_play_type); the two frozentype.text-derived columns EPA/WPA read (downs_turnover,pos_score_diff_end) are recomputed so EPA stays consistent (e.g. a 4th-down self-recovery short of the sticks is now correctly scored a turnover on downs). ESPN-sourced box turnover totals are unaffected. Verified across a 20-game / 3,439-play before/after diff: exactly two plays changed (both intended relabels), with EPA moving only on those two plays — no collateral drift. - A sack-fumble the offense recovers itself was relabeled
CFB — blocked-kick turnover flags + ESPN native-flag tripwires
- New
is_blocked_punt_turnover/is_blocked_fg_turnoverper-play flags (additive).is_turnovermodels only giveaways (INT + fumbles lost) to match ESPN's official-boxturnoversdefinition (so the*_pbpcross-check stays exact). A blocked kick the defense recovers is a possession loss but not a giveaway — ESPN's official box does not count it (verified) — so each is surfaced as a standalone flag kept out ofis_turnover/is_st_turnover:Trueon aBlocked Punt/Blocked Field GoalTouchdown, or the non-TD variant with a possession change. These are the possession-losing classes ESPN's per-playisTurnoverflag catches that the giveaway-based derivation does not. - Blocked-FG mislabel fix. ESPN sometimes types a blocked field goal returned by the defense
as
Extra Point Missed, routing it through PAT-scoring EPA logic.__add_new_play_typesnow relabels these toBlocked Field Goal[ Touchdown](gated on"blocked"+ an FG/field goaltext token, so a genuine blocked PAT is untouched), which also corrects the EPA. Because the relabel runs before thetype.text-derived flag computation, all downstream flags recompute cleanly (no staleness). - ESPN native
isTurnover/isPenaltyare kept as cross-checks, not sources of truth. They pass through the flattener as columns (populated back to 2018).isTurnoveris coarser (it silently drops ~16% of plain interceptions on sparse-text plays and has no per-side/ST concept);isPenaltyflags only primary-penalty plays. New regression tripwires (test_espn_flag_tripwires.py) assertisTurnover ⇒ is_turnover OR is_blocked_punt_turnover OR is_blocked_fg_turnoverandisPenalty ⇒ penalty_flagon the fixtures — the first would have caught the interception-erasure bug above. Validated across 150 games (24,876 plays): all blocked-punt and blocked-FG possession losses captured with 100% ESPN agreement and zero leakage into the giveaway signals; penalty tripwire 0 violations;isTurnover/is_turnoveragreed 99.6% (residual disagreements are ESPN false positives — self-recovered fumbles — the stricter derivation correctly excludes).
CFB — pre-2014 era support (CFBPlayProcess)
Validated across a 240-game sweep (15 games × 2004-2019): every game that has play-by-play
produces valid EPA/WPA and a full advanced box score in every era (209/209 of the sampled
games-with-plays; games without PBP exit early gracefully). Legacy ESPN labels that only appear in
older seasons are now normalized in __add_new_play_types (each rule is gated on the raw label, so
it is a no-op on modern data):
2pt Conversion— ESPN's pre-2014 successful two-point label — is resolved viascoringPlaytoTwo-Point Conversion Good/Two-Point Conversion Missed, so it routes through the two-point EPA/scoring path instead of being scored as a generic play.- 2004
Unknownrows are relabeled from their text: period/game markers →End Period(so these non-plays are excluded from aggregates instead of producing garbage EPA), and the handful of misclassified kicks →Field Goal Missed/Extra Point Missed/… Good. Kickoff Return (Defense)(pre-2014 onside-kick-recovered) →Kickoff.- Separate extra-point rows are normalized to the no-down sentinel (
down/distance = -1) for the few pre-2005 games that ship a real down on them (2005+ and two-point rows already use it).
Era notes (documented in the architecture reference): pre-2014 player attribution is
text-extraction only (the participants endpoint returns nothing before 2014; __join_participants
already falls back to regex names); ESPN's own win-probability array is empty before ~2016 but
wpa is computed in-house in every era; and PBP coverage is sparse before 2008 (~47% of 2004
games have no PBP), handled by the existing early-exit.
Removed — NCAA bracketology
espn_mbb_bracketology()/espn_wbb_bracketology()removed. The non-leaguesports.core.api.espn.com/v2/tournament/{22,23}/seasons/{y}/bracketologywrappers added in 0.0.51 — along with the_common_ncaa.pymodule and theregister_ncaa_bracketology()registration machinery — have been removed. The endpoint is ephemeral (ESPN only publishes it during the Jan–Mar projection window) and sat outside the per-league URL pattern, so it is no longer carried. The universalespn_mbb_*/espn_wbb_*wrappers are unaffected.
0.0.52 Release: June 3, 2026
CFB — offline reprocess support (CFBPlayProcess)
Three additive, non-breaking changes that let college-football games be rebuilt from
on-disk raw JSON without re-hitting ESPN, in support of the cfbfastR-cfb-raw scraper's
reprocess pipeline:
- Raw summary allowlist now keeps
injuriesandgameNotes.- Before:
CFBPlayProcess(gameId=..., raw=True).espn_cfb_pbp()filtered the ESPN summary to 15 keys and droppedinjuries/gameNoteseven when ESPN returned them. - After: both keys are retained (defaulting to
[]when ESPN omits them). All previously returned keys are unchanged — this is purely additive.
- Before:
- New
CFBPlayProcess.odds_sourceattribute.- Before: there was no way to tell where the resolved spread/total came from.
- After:
proc.odds_sourceis set to one of"summary_pickcenter","core_odds_api","default", or"injected"during odds resolution.
- New
CFBPlayProcess(odds_override=...)constructor argument.- Before: odds resolution always consulted the summary
pickcenterand, for 2024+ games with an emptypickcenter, cascaded to the livesports.core.api.espn.comodds endpoint — falling back to hardcoded defaults(2.5, 55.5, True, False)on failure. An offline rebuild could therefore silently hit the network or inherit wrong spread inputs that corrupt every play's EPA/WPA. - After: passing
odds_override={"gameSpread": ..., "overUnder": ..., "homeFavorite": ..., "gameSpreadAvailable": ...}short-circuits resolution to use exactly those values, setsodds_source="injected", and never touches the network or the defaults. With no override supplied (the default), behavior is unchanged. The override is validated and type-coerced at the constructor (a missing key or non-dict raisesValueErrorinstead of a laterKeyError).
- Before: odds resolution always consulted the summary
odds_sourceis also written into the returned payload (not just the instance attribute), so dict consumers ofrun_processing_pipeline()/run_cleaning_pipeline()retain odds provenance.
0.0.51 Release: May 30, 2026
User-facing quality-of-life additions
Three top-level helpers that significantly reduce friction for new users and notebook-driven exploration.
sportsdataverse.parsed.* — DataFrame-by-default mirror of every
league's wrappers. The standard sportsdataverse.nba.espn_nba_scoreboard()
returns raw Dict; the new sportsdataverse.parsed.nba.espn_nba_scoreboard()
returns a polars DataFrame. Both share the same underlying function
and accept the same return_parsed=False / return_as_pandas=True
overrides, but the default flips per import path. Available for all 8
leagues (parsed.nba, parsed.wnba, parsed.mbb, parsed.wbb,
parsed.cfb, parsed.nfl, parsed.mlb, parsed.nhl). Wrappers
without a registered parser pass through unchanged.
find_team / find_athlete / find_event — name-to-ID
resolvers in sportsdataverse.find (also re-exported at the package
top level). Eliminates the "what's the magic ID for X" friction:
from sportsdataverse import find_team, find_event
find_team("lakers", league="nba")["id"] # '13'
find_event(date="2024-06-17", league="nba", home="Boston") # NBA Finals G5
All three support multi=True for every match, case-insensitive
substring matching against the relevant fields, and an in-process
team-list cache (clearable via clear_team_cache(league=None)).
list_functions / function_count — searchable function index
in sportsdataverse.discover (also re-exported at the package top
level). Replaces dir() + grep:
from sportsdataverse import list_functions, function_count
function_count()
# {'cfb': 149, 'mbb': 146, 'mlb': 196, 'nba': 143, 'nfl': 208,
# 'nhl': 199, 'wbb': 151, 'wnba': 148} — 1,340 callables total
list_functions(search="pbp") # cross-league PBP wrapper inventory
list_functions(league="mlb", parsers_only=True) # just the parsers
list_functions(league="nhl", wrappers_only=True) # everything except parse_*
24 new offline tests in tests/test_qol.py cover all three QoL
additions including the backwards-compatibility invariant (importing
parsed.* must NOT mutate the raw module's default).
New doc page docs/quality-of-life.md with a side-by-side comparison
showing the four-line "before 0.0.51" equivalent vs the two-line
"after" recipe (find_event → parsed.espn_nba_summary). Intro page
Quickstart updated to show the parsed.* import path first.
Tiered TTL response cache — new sportsdataverse.cache module
adds a six-tier HTTP cache layer that sportsdataverse.dl_utils.download
consults before hitting the network. Three modes (off (default),
memory, filesystem) and six TTL tiers picked by URL inspection:
import sportsdataverse as sdv
sdv.set_cache_mode("filesystem") # persists to ~/.cache/sportsdataverse/
# IMMUTABLE (30d): completed-game PBP/boxscore, glossaries, NHL Records
# REFERENCE (7d): venues, franchises, divisions, seasons, draft picks
# SLOW (24h): team rosters, athlete /landing
# MODERATE (1h): default — leaders, season-to-date stats
# FAST (5m): news, injuries
# LIVE (0): /scoreboard/now, /standings/now — never cached
Scoreboard URLs with dates=YYYYMMDD get special handling: past dates
become IMMUTABLE (game results don't change), future dates stay LIVE.
Per-call cache_ttl= kwarg on download() overrides the tier picker,
and $SDV_PY_CACHE_DIR overrides the on-disk location. Invalidation:
sdv.clear_cache(), sdv.clear_cache(pattern="*roster*"),
sdv.clear_cache(url="https://..."). 19 offline tests in
tests/test_cache.py.
404 error messages with actionable next-action hints —
NoESPNDataError messages now include a tailored Suggestion: line
inferred from the URL. A 404 on /teams/9999/roster suggests
find_team(name, league='nfl'); an athlete 404 suggests
find_athlete(name, league='mlb', team=<team>); a summary 404 suggests
find_event(date, league='nba', home=..., away=...). League is
extracted from both ESPN URL shapes (the flat site.api/.../sports/<sport>/<league>/
form and the nested sports.core.api/v2/sports/<sport>/leagues/<league>/
form). 14 offline tests in tests/test_errors_suggest.py.
sdv console script — argparse-based CLI installed via
[project.scripts] in pyproject.toml. Six subcommands wrap the
top-level QoL helpers so users can poke at the package without
spinning up a Python REPL:
sdv find-team lakers --league nba
sdv find-event 2024-06-17 --league nba --home Boston
sdv list-functions --league mlb --search statcast
sdv function-count
sdv cache mode --set filesystem
sdv cache stats
sdv cache clear --pattern "*roster*"
A --json flag on any command emits raw JSON for piping to jq; the
default is a human-readable format. Exit codes: 0=success, 1=no match,
2=runtime error. 19 offline tests in tests/test_cli.py.
A second big release on top of 0.0.50. The headline items:
- New
sportsdataverse.mlbmodule (greenfield) — 175 functions spanning three data surfaces:- 113 ESPN cross-league wrappers + 5 ESPN originals
- 40 official MLB Stats API wrappers (
statsapi.mlb.com) - 17 Baseball Savant / Statcast wrappers including auto-chunked
25,000-row truncation handling on
/statcast_search/csv
- NHL migrated to
api-web.nhle.com/v1/— the deprecatedstatsapi.web.nhl.comhost is gone; replaced with 26 modernnhl_web_*wrappers grounded in the OpenAPI spec atfastRhockey/data-raw/nhl_api_web_openapi.yaml. - Cross-league ESPN port from hoopR / wehoop / cfbfastR — 804 new
wrappers across 8 leagues (NBA, MBB, WNBA, WBB, CFB, NFL, MLB, NHL)
via a single ~80-function core (
_common_espn.py) parameterized on the(sport, league)slug. Each per-league extension module is a 5-line file callingmake_league_module()to mass-register the wrappers with proper__name__/__qualname__/__doc__for IDE discoverability. - 3 new NHL modules for the historical / Statcast surfaces:
nhl_edge— 35 wrappers for the NHL EDGE player-tracking system (api-web.nhle.com/v1/edge/*)nhl_stats_rest— 21 wrappers for the official stats REST API (api.nhle.com/stats/rest/) with verbatim Cayenne filter expression supportnhl_records— 50 wrappers for the records site (records.nhl.com/site/api/) covering awards, coaches, franchises, HOF, draft, all-star, GMs
- NCAA bracketology —
espn_mbb_bracketology()andespn_wbb_bracketology()for the non-leaguesports.core.api.espn.com/v2/tournament/{22,23}/seasons/{y}/bracketologyendpoint (live during the projection window, Jan-Mar). - 20 polars/pandas parsers in
_common_espn_parsers.pycovering the most-used ESPN payload shapes (scoreboard, teams, standings, groups, athlete overview/stats/gamelog/splits, leaders, coaches, draft, event-competitor surface, team schedule/roster, news, injuries, generic Core v2 paginated lists). - 4 NHL EDGE family parsers + 3 sub-frame parsers in
nhl_edge_parsers.py, schema-grounded against live captures from 2026-05-23. return_parsed=Truedispatch shim — every wrapper whose short name has a registered parser (57 keys currently inENDPOINT_PARSERS) gains an optionalreturn_parsed=Truekwarg that routes the raw response through the parser and returns a polars DataFrame (pandas viareturn_as_pandas=True). The raw-Dict path is unchanged — the shim is backwards-compatible and strictly additive.- 80 offline parser tests (NHL EDGE 32 + universal ESPN 16 + the
cross-league shim suite) + 32 live-gated integration tests under
SDV_PY_LIVE_TESTS=1so default test runs never hit live endpoints.
New: MLB module (greenfield)
- New top-level
sportsdataverse.mlbpackage with 8 submodules. mlb_api.py(40 functions) wraps the official MLB Stats API. IDs to know:sportId=1is MLB,leagueId103=AL /104=NL,gameTypeslugsR/F/D/L/W/S/A/E/PO. Player IDs (personId/batter/pitcher) are the same MLBAM id space shared with Baseball Savant.mlb_statcast.py(17 functions) wraps Baseball Savant. The unofficial CSV search at/statcast_search/csvtruncates at exactly 25,000 rows with no pagination;statcast_searchraisesRuntimeErrorwhen the response hits that cap (default,raise_on_truncation=True). Usestatcast_search_chunkedfor multi-week ranges — it auto-chunks the date range and stitches client-side.mlb_espn_ext.pyregisters 113 cross-league ESPN wrappers viamake_league_module(..., include_mlb=True), which adds the MLB-onlyespn_mlb_athlete_hotzonesto the universal surface.
New: NHL — api-web.nhle.com migration + EDGE / Stats REST / Records
-
The deprecated
statsapi.web.nhl.comis gone.nhl_api.pykeeps a small set of backward-compatible aliases that warn and delegate tonhl_api_web. -
nhl_api_web.py(26 functions) covers the modern game-feed API athttps://api-web.nhle.com/v1/. -
nhl_edge.py(35 functions) wraps the NHL EDGE player-tracking surface — skater / goalie / team detail, shot-location, shot-speed, skating distance, zone time, plus 12*_top_10leaderboards.Note: all 12
*_top_10URL paths return 404 as of 2026-05-23 — the OpenAPI spec lists them but they're not live. The wrappers andparse_edge_top10are kept for forward-compatibility. -
nhl_stats_rest.py(21 functions) wraps the official Stats REST API atapi.nhle.com/stats/rest/. Verbatim Cayenne filter expression support viacayenneExp/factCayenneExpkwargs. -
nhl_records.py(50 functions) wraps the records site atrecords.nhl.com/site/api/— awards, coaches, franchises, skaters, goalies, draft, all-star, HOF, GMs, attendance, fastest goals, team records.
New: ESPN cross-league port
_common_espn.pyexposes ~80 core functions parameterized on(sport, league).make_league_module(sport, league, prefix, globals(), include_ncaa=, include_football=, include_mlb=)mass-registers wrappers in the caller's namespace. Each per-league extension file is a 5-line wrapper.- Wrappers use
functools.partialwith explicit__name__/__qualname__/__doc__so they behave like real functions forhelp(), IDE auto-complete, andinspect.signature(). - The
_NCAA_WRAPPERStable addsrankings,season_recruits,season_week_rankingsformbb,wbb,cfb. - The
_FOOTBALL_WRAPPERStable addsseason_qbr,season_qbr_weekfornfl,cfb. - The new
_MLB_WRAPPERStable addsathlete_hotzonesformlb.
New: NCAA bracketology
espn_mbb_bracketology(season, iteration=None)/espn_wbb_bracketology(...)atsports.core.api.espn.com/v2/tournament/{22,23}/seasons/{y}/bracketology.- The endpoint is seasonal — live during the projection window
(roughly January through March each year) and 404s the rest of the
year. Integration tests handle this with
pytest.xfailso off-season CI runs don't fail.
New: _common_espn_parsers.py (polars / pandas parser layer)
- 20 parsers covering the highest-traffic ESPN payload shapes. All parsers are league-agnostic — the same parser handles MLB, NFL, NBA, etc. because ESPN's payload shapes are identical across leagues.
- Every parser returns polars by default;
return_as_pandas=Trueyields pandas. Empty / malformed payloads return zero-row frames rather than raising. - Output columns snake-cased via
sportsdataverse.dl_utils.underscore. ENDPOINT_PARSERSregistry has 57 short-name keys mapped to 20 unique parsers; covers the universal table plus NCAA / football / MLB extras.parser_for(short_name)lookup helper.
New: return_parsed=True dispatch shim
_bind()in_common_espn.pywas extended with an optionalparser=argument. When present, the bound wrapper is a closure that addsreturn_parsed=Falseandreturn_as_pandas=Falsekwargs; whenreturn_parsed=True, the closure dispatches the raw response through the parser and returns a DataFrame.make_league_module()looks up the parser viaparser_for(short)on each wrapper registration. The lookup is lazy-imported so a missing parsers module doesn't break the package.- API contract: every existing caller continues to get raw
Dict— the shim is opt-in via the new kwargs.
New: nhl_edge_parsers.py
- 4 family parsers (
parse_edge_top10,parse_edge_detail,parse_edge_shot_location,parse_edge_zone_time) + generic fallback (parse_edge_payload). - 3 sub-frame parsers (
parse_edge_sog_details,parse_edge_sog_summary,parse_edge_hardest_shots) for unrolling the rich nested lists inside detail payloads thatparse_edge_detaildeliberately stringifies. EDGE_ENDPOINT_PARSERSregisters 33 of the 35 EDGE wrappers (the remaining 2 fall through to the generic parser viaparser_for_edge).EDGE_SUBFRAME_PARSERSmaps each detail wrapper to the tuple of sub-frame parsers that apply.
New: Site v2 summary dispatcher (20 sub-parsers)
The Site v2 summary endpoint
(espn_{league}_summary(event_id=...)) ships ~19-22 top-level sections
per game (~700 KB to 1.8 MB per call). Rather than collapse that into
one parser, the summary surface now has 20 targeted sub-parsers plus a
dispatcher:
parse_summary_boxscore_player— one row per (team × athlete) with the parallelkeys/statsarrays zipped (e.g. NBA produces 27 rows withmin,fg,3pt,ft,reb,ast, columns).parse_summary_boxscore_team— one row per (team × stat) withstat_name,stat_label,stat_display_value.parse_summary_plays— one row per play (~450 rows per NBA game).parse_summary_winprobability— one row per win-prob tick (joinable to plays viaplay_id).parse_summary_leaders— one row per (team × category × leader) from the 3-levelleaders[]nesting.parse_summary_game_info,parse_summary_officials,parse_summary_header,parse_summary_season_series,parse_summary_against_the_spread,parse_summary_standings,parse_summary_broadcasts,parse_summary_format,parse_summary_pickcenter,parse_summary_odds,parse_summary_article,parse_summary_injuries,parse_summary_news— one row per (or one row total for) the corresponding summary section.parse_summary_drives,parse_summary_scoring_plays— NFL / CFB specific (NFL summary shipsdrives.previous[]+scoringPlaysinstead of top-levelplays). Return zero-row frames for non-football leagues.parse_summary(payload, section=None)— dispatcher. Withsection=Nonereturns a dict of all 20 sub-frames keyed by section name; withsection="<name>"returns just that frame. Empty payload returns a dict of 20 zero-row frames.SUMMARY_SECTION_PARSERS— public registry mapping section name to parser.
Cross-league parity tests verify the dispatcher works against captured fixtures for NBA / MLB / NFL / NHL / WNBA — same code path handles every league's summary endpoint.
New: 100% ENDPOINT_PARSERS coverage (121/121)
Every wrapper short name across all 4 wrapper tables
(_UNIVERSAL_WRAPPERS, _NCAA_WRAPPERS, _FOOTBALL_WRAPPERS,
_MLB_WRAPPERS) is now registered in ENDPOINT_PARSERS. Every
factory-bound wrapper plus the hand-bound NCAA bracketology helpers
accepts return_parsed=True and return_as_pandas=True.
Two new generic fall-through parsers cover the long tail:
parse_single_entity— flattens any single-resource Core v2 payload (team, venue, franchise, coach, award, position, season_info, athlete_core, event_competitor, etc.) to a one-row frame.parse_itemswas already generic for{items: [...]}Core v2 lists and Core v2{entries: [...]}(athlete_statisticslog); this release expands its registration to ~30 more list-shape endpoints (calendar variants, event lists, season_powerindex, talentpicks, etc.).
register_ncaa_bracketology was upgraded to wrap the bracketology
helpers in the same return_parsed=True shim used by make_league_module
— previously they were hand-bound without the shim.
Three regression tests lock in the invariant:
test_every_wrapper_short_name_has_a_registered_parsertest_no_stale_entries_in_endpoint_parsers_registrytest_return_parsed_shim_active_on_every_wrapper_across_all_leagues(walks the__all__of every league extension module and verifies 819+ wrappers carry the shim).
New: weekly cron live-test drift detector
.github/workflows/live-tests-cron.yml runs the full live test suite
(tests/test_espn_live.py and any other SDV_PY_LIVE_TESTS=1 gated
tests) every Monday 13:00 UTC and on workflow_dispatch. On failure,
the workflow uses actions/github-script to find or create a tracking
issue labeled live-tests:drift:
- First failure opens a new issue with the last 4 KB of pytest output plus a run URL.
- Subsequent failures comment on the existing open issue instead of duplicating.
- Closing the issue resets state.
Catches upstream API drift (ESPN schema changes, NHL EDGE 404s, MLB Stats API URL moves) on a regular cadence even when the repo is otherwise quiet between releases.
New: MLB Stats API parser layer
sportsdataverse.mlb.mlb_api_parsers turns the 40 raw-Dict
mlb_api_* wrappers into tidy polars / pandas DataFrames. Mirrors
the design of _common_espn_parsers:
- Every parser returns polars by default; pandas via
return_as_pandas=True. - Empty / malformed payloads return zero-row frames.
- Output columns snake-cased via
sportsdataverse.dl_utils.underscore. - Most parsers use
pandas.json_normalizefor one-pass flattening.
Five dedicated parsers handle the high-traffic endpoints with their own unrolling logic:
parse_mlb_api_schedule— walksdates[].games[]and prefixes the schedule date onto each game row (one row per game withteams.home.*/teams.away.*/venue.*/status.*flattened).parse_mlb_api_teams— one row per team fromteams[].parse_mlb_api_team_roster— one row per player fromroster[]withperson,position,statussub-dicts flattened.parse_mlb_api_standings— walksrecords[].teamRecords[], prefixes division identifiers (namespacedstandings_*to avoid column collisions with team-record fields likelastUpdated), and produces one row per (division × team).parse_mlb_api_person_stats— walksstats[].splits[](also handlesmlb_api_team_statswith the same shape), prefixesstats_type/stats_groupfrom the parent block, and flattens the innerstatblock to wide stat columns.
A generic parse_mlb_api_list fallback handles every list-shape
endpoint that doesn't need extra unrolling (venues, sports, leagues,
divisions, seasons, awards, umpires, draft, draft_prospects,
attendance, team_leaders, team_alumni, team_affiliates, stats,
stats_leaders, stats_streaks, people, sport_players).
MLB_API_ENDPOINT_PARSERS registry has 26 entries (7 dedicated + 19
generic). parser_for_mlb_api(fn_name) returns the registered
parser; unknown names fall back to parse_mlb_api_list so the
caller always gets a DataFrame-returning callable.
Test fixtures captured 2026-05-24 from statsapi.mlb.com (8 captures
in tests/fixtures/mlb_api/). 17 offline tests in
tests/test_mlb_api_parsers.py exercise each dedicated parser plus
the generic fallback against the live fixtures.
New: NHL Stats REST + Records parser layers
sportsdataverse.nhl.nhl_stats_rest_parsers.parse_nhl_stats_rest and
sportsdataverse.nhl.nhl_records_parsers.parse_nhl_records turn every
wrapper in their respective surfaces into a tidy polars / pandas
DataFrame.
Both APIs ship the identical {data: [...], total: N} envelope on
every endpoint, so a single parser handles every wrapper:
parse_nhl_stats_restcovers the 21 wrappers insportsdataverse.nhl.nhl_stats_rest(api.nhle.com/stats/rest/en/*).parse_nhl_recordscovers the 50 wrappers insportsdataverse.nhl.nhl_records(records.nhl.com/site/api/*).
The meta Stats REST endpoints (config, componentSeason, ping) ship
non-data-keyed payloads — both parsers return zero-row frames for
those instead of raising.
Registries: NHL_STATS_REST_ENDPOINT_PARSERS has 17 entries (excluding
the meta endpoints). parser_for_nhl_stats_rest and
parser_for_nhl_records always return a callable (fall back to the
generic parser — never return None).
New: NHL api-web parser layer
sportsdataverse.nhl.nhl_api_web_parsers covers the modern game-feed
API at api-web.nhle.com/v1/ — 16 dedicated parsers + 2 dispatchers
covering all 26 nhl_web_* wrappers across game-center, schedule,
score, scoreboard, standings, team, player, leaders, and draft
families.
Game-center parsers:
parse_nhl_web_pbp— one row per play (~330 plays per game) witheventId,typeCode,typeDescKey,periodDescriptor,detailsflattened.parse_nhl_web_boxscore— unrolls the 6-bucketplayerByGameStats: {away,home}Team.{forwards,defense,goalies}structure into one long-form frame, tagging each row withhome_awayandposition_group.parse_nhl_web_landing— single-row game profile with venue, teams, periodDescriptor, gameState, summary stringified.parse_nhl_web_right_rail— dispatcher returning 6 sub-frames:season_series,shots_by_period,team_game_stats,game_info,linescore_by_period,season_series_wins. Withsection="..."returns just one frame.
Schedule / score parsers:
parse_nhl_web_schedule— walksgameWeek[].games[], prefixes the day's date onto each game row.parse_nhl_web_score— flattensgames[]for a single date.parse_nhl_web_scoreboard— walksgamesByDate[].games[], prefixesscoreboard_date(multi-day scoreboard).parse_nhl_web_club_schedule— flattensgames[]withclub_timezone/club_current_season/club_previous_season/club_next_seasoncontext columns from the parent payload.
Standings + team / player parsers:
parse_nhl_web_standings— one row per team (84 stat columns covering full win/loss/OT/SO/ROW/L10/streak/home/away breakdowns).parse_nhl_web_standings_season— one row per season (108 NHL seasons since 1917-18).parse_nhl_web_club_stats— dispatcher returning{skaters, goalies}as separate frames.parse_nhl_web_roster— mergesforwards,defensemen,goaliesinto one long-form frame with aposition_groupcolumn.parse_nhl_web_player_landing— single-row player profile (~130 columns for a player like McDavid with full career totals, features, recent games).parse_nhl_web_player_game_log— one row per game fromgameLog[].
Leaders + draft:
parse_nhl_web_leaders— walks the category-keyed leaders payload ({points: [...], goals: [...]}for skaters;{wins: [...], savePctg: [...]}for goalies), tags each row with the category it came from, concatenates.parse_nhl_web_draft_picks— one row per pick.
Registry: NHL_API_WEB_ENDPOINT_PARSERS has 24 entries covering all
the data endpoints. parser_for_nhl_api_web(fn_name) returns the
registered parser or None for the 2 idiosyncratic endpoints
(playoff_series, player_spotlight, draft_rankings,
draft_rankings_now) whose payloads are too idiosyncratic for a
useful generic fallback — callers null-check.
Test fixtures captured 2026-05-24 (17 captures from
api-web.nhle.com/v1/). 37 offline tests in
tests/test_nhl_api_web_parsers.py verify each parser against the
captured fixtures plus dispatcher contracts, empty payload contract,
pandas opt-in, and registry consistency.
Test fixtures captured 2026-05-24 (8 from api.nhle.com/stats/rest/,
6 from records.nhl.com/site/api/). 21 offline tests in
tests/test_nhl_aux_parsers.py verify parsing across:
- 7 Stats REST data endpoints (season, franchise, country, glossary, skater_summary, goalie_summary, team_summary).
- 6 Records endpoints (franchise, franchise_team_totals, coach, draft, player, attendance).
- Empty-payload contract, pandas opt-in, registry consistency, and the config-as-meta zero-row case.
Bug fixes
parse_team_rosternow handles both ESPN roster shapes. The flat shape (athletes[]= list of athlete dicts; used by NBA / WNBA / MBB / WBB) continues to work unchanged. The newly- handled position-grouped shape (athletes[i] = {position, items}; used by MLB / NFL / NHL / CFB) is auto-detected by inspecting the first element — each player from a group'sitems[]is tagged with aposition_groupcolumn carried over from the parent group. Without the fix, MLB / NFL / NHL / CFB rosters were collapsing to ~5-6 group rows instead of unrolling to the full per-player list (e.g. Alabama CFB went from 6 group rows to 100 player rows).
New: NFL drive-plays parser (true PBP parity)
parse_summary_drive_plays rounds out the football PBP story. NFL
and CFB summary payloads don't ship a top-level plays[] array (the
NBA / MLB / NHL / WNBA convention); they nest plays inside each
drive at drives.previous[i].plays[]. The existing
parse_summary_drives returns one row per drive with the plays
stringified. This new parser unrolls those nested plays into a true
one-row-per-play frame with drive_id + drive_sequence columns
carried over from the parent drive — letting callers join back to
the drives frame for drive-level context.
Verified against Super Bowl LIX: 26 drives + 186 plays unrolled
into a 186-row × 43-column polars frame. Returns zero rows for
NBA / MLB / NHL / WNBA fixtures (those leagues use top-level
plays[], exercised by parse_summary_plays).
SUMMARY_SECTION_PARSERS registry grows from 20 to 21 entries.
The summary dispatcher's output dict now includes the drive_plays
section alongside drives and scoring_plays.
Test infrastructure
- New
tests/test_espn_universal_parsers.py(128 tests, +22 since last roll-up: 8 sparse-section tests coveringbroadcasts(present for MLB / NHL, empty for NBA / NFL / WNBA in past-game captures) and the universally-sparseagainst_the_spread/pickcenter/odds; 3 MBB/WBB/CFB NCAA summary fixture additions to the cross-league parametrized tests, expanding the dispatcher + boxscore_player + plays + drives + officials assertions from 5 leagues to all 8 ESPN leagues),tests/test_mlb_api_parsers.py(17 tests),tests/test_nhl_aux_parsers.py(21 tests),tests/test_nhl_api_web_parsers.py(37 tests), andtests/test_nhl_edge_parsers.py(32 tests) run offline against captured fixtures. - New
tests/test_espn_live.py(56 live tests, +24 since last roll-up: 9 NCAA-side wrapper tests (CFB/MBB/WBB × team_roster/ news/team_schedule), 3 NCAA summary dispatcher tests, 3 MLB Statcast pitch-search tests (small-range happy path / multi-week chunked stitch / raise-on-truncation guard), plus 9 parametrizedreturn_parsed=Trueshim-parity tests confirming the raw-Dict / polars / pandas round-trip is internally consistent for the NCAA surface — same wrapper invocation with vs without the kwarg must produce equivalent data, andreturn_as_pandas=Truerow count must match the polars row count). Gated bySDV_PY_LIVE_TESTS=1for live integration verification. - Captured fixtures live under
tests/fixtures/espn/(43 captures — the original 7 plus summary captures for all 8 ESPN leagues (NBA / MLB / NFL / NHL / WNBA + the new NCAA captures: MBB final Purdue@UConn, WBB final Iowa@SC, CFB national championship OSU@ND) plus the 28-fixture cross-league parity set coveringteam_roster/team_schedule/news/injuriesfor each league),tests/fixtures/mlb_api/(8 captures: schedule, teams, roster, standings, person_stats, venues, sports, divisions),tests/fixtures/nhl_stats_rest/(8 captures: season, franchise, country, glossary, config, skater_summary, goalie_summary, team_summary),tests/fixtures/nhl_records/(6 captures: franchise, franchise_team_totals, coach, draft, player, attendance),tests/fixtures/nhl_api_web/(17 captures: pbp, boxscore, landing, right_rail, schedule, score, scoreboard, standings, standings_season, club_schedule, club_stats, roster, player_landing, player_gamelog, skater_leaders, goalie_leaders, draft_picks), andtests/fixtures/nhl_edge/(7 captures), each with a README documenting provenance. - Parametrized cross-league parity tests in
test_espn_universal_parsers.pyexercise the summary dispatcher against all 5 captured leagues and assert the full 20-section dispatch contract for each (boxscore_player + boxscore_team + plays + winprobability + leaders + 13 metadata sections + 2 football-only).
Documentation
- README.md and docs/docs/intro.md both gain two new sections:
- "Supported leagues and data sources" — a per-league table showing every module + the data surfaces it covers + wrapper counts (NBA=118, WNBA=124, MBB=121, WBB=126, CFB=123, NFL=119, MLB=175, NHL=132, total ~1,030).
- "Polars / pandas parser layer" — quick overview of the
return_parsed=Trueshim for ESPN wrappers + the compose-wrapper-with-parser pattern for the NHL / MLB sibling APIs. Links to the architecture + parsers docs pages.
- New documentation pages:
docs/architecture/espn-cross-league.md— the factory + shim architecture.docs/parsers/index.md— the parser layer +ENDPOINT_PARSERS.docs/mlb/index.md— MLB module overview (ESPN + Stats API + Statcast); brief pointers to the new dedicatedparsersandstatcastpages.docs/mlb/parsers.md— dedicated MLB Stats API parsers page (split out fromindex.md) with the full parser table, registry +parser_for_mlb_api, four chaining examples, and a fixture inventory.docs/mlb/statcast.md— dedicated Baseball Savant / Statcast page (split out fromindex.md) covering the 17statcast_*wrappers, the 25,000-row truncation handling + thestatcast_search_chunkedauto-chunked variant, Statcast coverage windows by metric, MLBAM ID-space chaining with the Stats API, and two end-to-end examples (catcher pop times + World Series pitch-by-pitch). Both new pages are wired into the MLB category indocs/sidebars.ts.docs/parsers/fixtures.md— comprehensive index of all 89 captured live payloads across the 6 fixture directories (espn/,mlb_api/,nhl_api_web/,nhl_edge/,nhl_stats_rest/,nhl_records/). Includes the full endpoint mapping table per directory, the championship-game event IDs used for the cross-league summary captures, and a maintenance section explaining how to refresh a fixture.docs/architecture/building-blocks.md— meta-documentation page enumerating the five low-level patterns reused across every parser module:_bindshim factory,make_league_modulefactory call,_row_per_item/_single_rowjson_normalize helpers, theENDPOINT_PARSERSregistry +parser_for_*lookup, and the dispatcher pattern (used byparse_summary,parse_nhl_web_right_rail,parse_nhl_web_club_stats). Closes with a step-by-step "Adding a new parser" checklist. Sidebar entry added under the Architecture category.
docs/sidebars.tsregrouped by sport family — leagues now cluster by basketball (NBA / WNBA / MBB / WBB) / football (NFL / CFB) / baseball (MLB) / hockey (NHL) instead of alphabetical, surfacing the cross-league helper relationships (e.g. NCAA basketball pair with NBA via the same ESPN factory). Architecture + Parsers categories now default to expanded (collapsed: false) so newcomers see the package-wide overview first.nhl/nhl_loaders.pylint cleanup: 4 sites ofif type(seasons) is int:replaced withisinstance(seasons, int)to clear pre-existingE721ruff warnings (no behaviour change — both forms accept the same input).tests/conftest.pyis now the single source of truth for theSDV_PY_LIVE_TESTS=1gating mechanism.tests/test_espn_live.pywas previously redefiningLIVE+ its ownpytestmark.skipifmarker; it now imports the sharedskip_if_no_livefromconftestand assigns it directly topytestmark. Behaviour is identical (no env var → 56 tests skip; env var set → 56 tests run) but the duplication is gone and the conftest docstring now documents both the per-test decorator and module-level marker patterns for futuretest_*_live.pyfiles.tests/conftest.pyalso gains a sharedload_fixture(category, stem)helper that all 5 parser test modules now use instead of each carrying their own copy of the samejson.loads((FIXTURE_DIR / f"{stem}.json").read_text(...))boilerplate + per-fileFIXTURE_DIRconstant. The helper raisesFileNotFoundErrorwith the expected path baked into the message when a fixture is missing — easier debugging of typo'd stems. Each test file still keeps its thin local_load(stem)alias bound to its category, so call sites (_load("summary_nba")) remain unchanged.test_nhl_aux_parsers.pykeeps its 2-arg_load(directory, stem)signature for its dual-category load pattern but the underlying helper is now shared.pyproject.tomlkeywordsexpanded from 6 to 21 entries reflecting the 0.0.51 surface — full league set (nba, wnba, nfl, college football, ncaa basketball, mlb, nhl), data sources (espn, mlb stats api, statcast, baseball savant, nhl edge, nhl api-web), and concepts (data, epa, statistics, win probability, play-by-play, web scraping, polars, parser). Improves PyPI search discoverability for users searching by individual league or data source.- New
localpre-commit hooksync-docs-changelog(in.pre-commit-config.yaml): when staging changes toCHANGELOG.md, automatically re-copies the file todocs/src/pages/CHANGELOG.md(the docusaurus-rendered copy) and stages the synced file so both copies land in the same commit. Replaces the manualcp CHANGELOG.md docs/src/pages/CHANGELOG.mdstep that contributors used to remember by hand. - Module docstrings on every parser + wrapper module now carry a
Documentation:block linking to the matching docs page sohelp()/pydocusers land on the right reference without hunting. Updated modules:_common_espn.py,_common_espn_parsers.py,nhl/nhl_api_web.py,nhl/nhl_api_web_parsers.py,nhl/nhl_edge.py,nhl/nhl_edge_parsers.py,nhl/nhl_stats_rest.py,nhl/nhl_stats_rest_parsers.py,nhl/nhl_records.py,nhl/nhl_records_parsers.py,mlb/mlb_api.py,mlb/mlb_api_parsers.py,mlb/mlb_statcast.py. nhl/nhl_pbp.py::espn_nhl_pbpdocstring gains a prominent cross-reference + comparison table distinguishing it from the modernnhl_web_pbp/parse_nhl_web_pbpsurface (different ID spaces, different schemas, not interchangeable). A matching:::caution:::admonition added todocs/nhl/api-web.mdso users coming from either direction find the cross-reference.docs/nhl/api-web.mdgains a "Parser deep-dive" section between the registry and the full example: documents theparse_nhl_web_boxscore6-bucket unrolling pattern, both dispatchers (right_rail6-section +club_stats2-section breakdowns with example invocations), the roster merge-with-tag pattern, and the leaders category-keyed payload unrolling.
docs/docs/intro.mdgains a "Quickstart" section directly under the goal paragraph showing three one-liners across NBA / MLB / NHL covering the three primary usage modes (return_parsed shim, Stats API compose-with-parser, NHL EDGE compose-with-parser).CLAUDE.mdgains two new top-level sections ("ESPN Cross-League Architecture (0.0.51+)" and "Parser Layer (0.0.51+)") that document the factory pattern,make_league_module,_bind+shim, ENDPOINT_PARSERS invariant, summary dispatcher contract, cross-league shape divergences captured by tests, the fixture inventory, and the test-file structure. ~210 lines added to keep future AI assistants and contributors aligned on the parser-layer conventions.docs/nhl/api-web.md— the modern game-feed surface (api-web.nhle.com/v1/) with the full endpoint table and a parser layer section covering all 16 dedicated parsers + 2 dispatchers (right_rail,club_stats).docs/nhl/edge.md,edge-parsers.md,stats-rest.md,records.md— the NHL surface (EDGE, Stats REST, Records). Each now includes cross-links to the other three NHL docs pages and a "Parser layer" section.
0.0.50 Release: May 7, 2026
This release is a big one. The headline items:
- A near-drop-in nflreadpy-parity surface inside
sportsdataverse.nfl: six new loaders, two unified per-type loaders, a caching layer, runtime config, three static datasets, 25load_*aliases, and current-season / current-week helpers. - 11 new ESPN scrape modules across
wbbandwnba(team rosters, season player & team stats, standings, draft, event officials), each with full@overloadtyping. - A new
cfb_play_participantsmodule and a corresponding ~340-line collapse insidecfb_pbp.__add_player_cols. - The long-running
0.36-live→mainpolars-1.x reconciliation across all seven*_pbp.pymodules (~165 API translation sites). - Packaging fully modernized to PEP 621
pyproject.toml(no moresetup.py), conda-installable via the newrecipe/meta.yaml. - Lint chain re-baselined on Ruff (replacing black + isort + pycln + flake8) plus a richer pre-commit set.
- Runnable
Example:sections on ~190 public callables and seven new intro / intermediate Jupyter notebooks underexamples/notebooks/. - Sphinx docs build is clean under
sphinx-build -W.
Round bump to 0.0.50 (rather than 0.0.41) to signal scope; we are still alpha.
Packaging modernization
- Migrated all packaging metadata from
setup.pyto PEP 621[project]inpyproject.toml.setup.pyis removed;python -m buildis the only supported build path. - License switched from classifier (
License :: OSI Approved :: MIT License) to SPDX expression (license = "MIT"+license-files = ["LICENSE"]) for Metadata 2.4 compliance. - Python target widened to 3.9–3.14 (3.6/3.7/3.8 dropped). Dependency lower bounds modernized (
polars>=1.0,<2.0,pyarrow>=14.0,numpy>=1.23,pandas>=2.0, etc.). [tool.setuptools.packages.find]excludestests*,Sphinx-docs*,docs*,examples*,archive*,recipe*,dev*from the wheel.[tool.setuptools.package-data]retains thecfb/models/*+nfl/models/*shipping list.MANIFEST.intrimmed to current-relevance patterns..gitignoreextended to ignoredev/,dist_check/, and the Sphinx_build/+_static/artifacts; trackedSphinx-docs/_build/files were untracked.
Conda installability
- New
recipe/meta.yaml:noarch: pythonconda-build recipe that mirrors[project.dependencies]and consumespyproject.tomldirectly. Two source modes documented — localpath: ..for dev, PyPIurl:+sha256:for conda-forge submission. - New
recipe/README.md: walks through the localconda build recipe/workflow and the conda-forgestaged-recipessubmission flow. - New
.github/workflows/conda-build.yml: verifies the recipe on every PR that touchesrecipe/orpyproject.toml, plus on every release. Usesconda-incubator/setup-miniconda@v3+ miniforge / mamba; builds, installs the resulting.conda, smoke-imports all seven sport subpackages, uploads the built package as a workflow artifact.
Linting & pre-commit modernization
- Replaced the legacy black + isort + pycln + flake8 chain with Ruff (lint, import-sort, pyupgrade, format, unused-import removal).
pyproject.toml [tool.ruff]pinsline-length = 120,fix = true,show-fixes = true. The standaloneisorthook is retained ONLY to injectfrom __future__ import annotationsat the top of every Python file via its--add-importflag — Ruff handles all other import concerns. pyproject.toml [tool.ruff.lint]ignoresE712(intentionalpl.col(...) == True/Falsefor polars boolean masks),E501/E402(long-URL docstrings + module-level imports),F601/F841(legacy parser idioms). Per-file ignores cover star-imports + re-exports in__init__.pyfiles (F401/F403).- New pre-commit hooks alongside Ruff:
pre-commit-hooks(trailing-whitespace, check-merge-conflict, check-ast, check-toml/json/xml/yaml, check-symlinks, end-of-file-fixer, requirements-txt-fixer, check-added-large-files, debug-statements). Thecheck-yamlhook excludesrecipe/meta.yamlbecause its Jinja2 templating isn't valid pre-substitution YAML.pygrep-hooks:python-use-type-annotations,python-no-eval,python-no-log-warn,rst-backticks,rst-directive-colons,rst-inline-touching-normal,text-unicode-replacement-char,python-check-mock-methods,python-check-blanket-noqa,python-check-blanket-type-ignore.add-trailing-comma,sync-pre-commit-deps.check-jsonschema --check-github-workflowsvalidates.github/workflows/*.ymlagainst the GitHub Actions schema.actionlintfor workflow expressions / shell.yamlfmt(config in.yamlfmt:line_ending: lf,eof_newline: true).doctocregenerates Markdown TOCs.markdownlint-cli2against.markdownlint-cli2.yaml. The config disables a handful of rules that fight legacy README / CHANGELOG content (MD013 line-length, MD030 list-marker-space, MD045 alt-text, MD051 link-fragments, MD060 table-column-style) and allows<a>,<img>,<br>,<sub>,<sup>inMD033for the README's badge / logo HTML.
Documentation toolchain
- Added
sphinx.ext.napoleontoSphinx-docs/conf.pywith explicit Google-style settings — the newwbb/wnba/nfl/cfbmodules use Google-style docstrings (Args:/Returns:/Raises:) and these were producing 22 docutils warnings on build before napoleon was wired up. - Added a no-op
visit_abbreviationshim to the markdown translator inSphinx-docs/conf.py. Sphinx 9 emitsabbreviationnodes for the keyword-only*separator in rendered function signatures, andsphinx-markdown-builder0.6.10 has no visitor for that node type. The shim emits the inner text and skips the node, so the build is now warning-free undersphinx-build -W. - Module docstrings in
cfb_play_participants.pyandnfl/utils_date.pyhad bullet lists immediately following aCaveats:/NFL season convention:paragraph header. Added the required blank line + asterisk markers so docutils parses them as proper RST bullet lists. Sphinx-docs/sportsdataverse.{cfb,mbb,nba,nfl,nhl,wbb,wnba}.rstregisterautomoduleentries for every new ESPN scrape module shipped this release.Sphinx-docs/setup.rstdeleted (was an auto-generated apidoc page for the now-removedsetup.py).Sphinx-docs/index.rstfixed a single-backtick\toctree`typo so therst-backticks` pre-commit hook passes.
Runnable docstring examples (~190 functions)
- Every public callable across
cfb,nfl,nba,nhl,mbb,wbb,wnba,dl_utils,decorators,errors,nfl/cache,nfl/config,nfl/datasets,nfl/utils_date, and the top-level package now ships a multi-blockExample:section: a quick-start invocation, one or two useful parameter combinations, a one-line pipeline next-step, and aSee Also:block with cross-links to companion R packages (wehoop,hoopR,cfbfastR,baseballr,fastRhockey),nflverse,nflreadpy,nba_api, andnhl-api-pywhere applicable. - Examples use the napoleon literal-block format (heading +
::+ 4-space indented code) so they render as proper code blocks in the markdown docs without triggeringsphinx.ext.doctest. Users can copy-paste any block and run it as-is. - Existing one-line backtick-wrapped examples (the legacy
Example: <inline call>shape) were replaced (not appended) so each function has exactly oneExample:section.
Example notebooks
- Seven new Jupyter notebooks under
examples/notebooks/:01_quickstart.ipynb,02_cfb_intro.ipynb,03_nfl_intro.ipynb,04_nba_intro.ipynb,05_wbb_wnba_intro.ipynb,06_mbb_intro.ipynb,07_nhl_intro.ipynb. Intro / intermediate level — schedule, pbp, team / player / season-stats endpoints, thenfl.update_config/clear_cache/get_current_*runtime surface, and a small pipeline example per sport. Outputs cleared so the user runs them locally; cross-references link to companion R packages and alternative Python libraries. .gitignorekeeps*.ipynbignored at the repo level (so scratch + checkpoint notebooks aren't accidentally tracked) but adds a negative pattern!examples/notebooks/*.ipynbso the curated tutorial notebooks are explicitly tracked.
Contributor docs and templates
- New
CLAUDE.mdand.github/copilot-instructions.mdcapture the project conventions for AI-assisted development: branching, conventional commit messages, polars 1.x rules, HTTP layer, module patterns, NFL nflreadpy-parity surface, CFBcfb_play_participants, test conventions, packaging, Sphinx toolchain, the docstring conventions for new functions, common pitfalls. - New
CONTRIBUTING.md: canonical onboarding doc covering uv workflow, conda fallback, Python target 3.9–3.14, code standards (ruff, mypy), polars 1.x rules, test gating withskip_if_no_live, new-module spec. - New
.github/PULL_REQUEST_TEMPLATE.mdand.github/ISSUE_TEMPLATE/(config.yml,bug_report.yml,feature_request.yml,data_quality.yml). The PR template includes an "I have NOT included AI agents (Claude / Copilot / Cursor / GPT / Gemini) as commit co-authors" checkbox enforcing project policy.
NFL — nflreadpy parity
- Six new loaders:
load_nfl_team_stats,load_nfl_ftn_charting,load_nfl_trades,load_nfl_ff_playerids,load_nfl_ff_rankings,load_nfl_ff_opportunity. - Two new utility helpers in
nfl/utils_date.py:get_current_nfl_season(),get_current_nfl_week(). - Unified
load_nfl_nextgen_stats(stat_type=...)consolidating the per-type variants. The per-type functions are kept as aliases that emitDeprecationWarningand forward to the unified entry point. - Unified
load_nfl_pfr_advstats(stat_type=, summary_level=)consolidating eight per-type / per-summary functions, with the same deprecation alias pattern. - 25 nflreadpy-parity aliases inside
sportsdataverse.nfl(load_pbp↔load_nfl_pbp, etc.). Identity-equivalent — no perf overhead, just a friendlier import surface for nflreadpy users. kind=parameter added toload_nfl_ff_rankingsas the preferred name;type=retained for nflreadpy parity.
NFL — caching and configuration
- New caching layer in
sportsdataverse.nfl.cachewith both memory and filesystem backends and TTL support. clear_cache()for explicit invalidation.- New
NflConfigplusupdate_config()/get_config()/reset_config(), with env-var initialization:SDV_PY_NFL_CACHE,SDV_PY_NFL_CACHE_DIR,SDV_PY_NFL_CACHE_DURATION,SDV_PY_NFL_VERBOSE,SDV_PY_NFL_TIMEOUT,SDV_PY_NFL_USER_AGENT. - All 23 canonical loaders plus the 11 deprecated aliases are decorated with
@cached_loader. return_as_pandas=Trueround-trips correctly through the cache: a single polars frame is stored, and conversion happens on read.
NFL — static datasets
team_abbr_mapping(143 entries, relocations folded into the modern abbreviation:OAK -> LV,SD -> LAC,STL -> LA).team_abbr_mapping_norelocate(143 entries, history preserved).player_name_mapping(136 entries, common-variant → canonical).- All three are eagerly loaded at import time and inline-bundled in the package — no separate JSON files to ship.
NFL — pickcenter / odds modern path
__helper__espn_nfl_odds_information__now hits the modernsports.core.api.espn.com/v2/.../events/{gid}/competitions/{gid}/oddsendpoint when the legacysummary?event=pickcenterarray is empty (true for all 2024+ games).- Cascades to defaults
(2.5, 55.5, True, False)only if both modern and legacy paths fail. - For example, the 2024 CFP semifinal previously returned
(2.5, 55.5, True, False)and now correctly returns(-3.5, 67.5, True, True).
NFL — load_nfl_schedule parquet port
- Switched from the stale
nflverse-pbp/master/schedules/sched_{season}.rds(which was 404'ing on every season) to the modernnflverse-data/releases/download/schedules/games.parquet. One combined file, 1999–2025, 7,276 rows × 46 cols.
WBB / WNBA — new ESPN scrape modules
Eleven new modules across sportsdataverse.wbb and sportsdataverse.wnba, plus their __init__.py re-exports and live-gated smoke tests. The WNBA modules (other than wnba_draft) are thin shims onto a shared _espn_basketball_* helper that lives in the corresponding wbb_*.py file (league slug fixed to "wnba"), keeping the wbb/wnba pair DRY.
wbb_team_roster/wnba_team_roster: per(team_id, season)roster, flattened to one row per athlete. Snake-case columns; stable schema on empty rosters.wbb_player_stats/wnba_player_stats: per(athlete_id, season)stats. Multi-table dict with canonical keysAverages/Totals/Misc(always present, empty-frame fallback) plus anOtherbucket only added when ESPN ships a non-canonical category.wbb_team_stats/wnba_team_stats: per(team_id, season)stats. Same multi-table shape as player stats; ESPN ships these asGeneral/Offensive/Defensivecategories that map onto the canonical Averages / Totals / Misc keys. Endpoint corrected tosite.web.api.espn.com/.../teams/{id}/statistics?season=...(thecommon/v3path the original spec named 404s).wbb_standings/wnba_standings: one-row-per-team season standings. WBB defaults togroup=50(Division I women); WNBA has no group filter.wnba_draft: one-row-per-pick draft history. Modern endpoint atsite.web.api.espn.com/apis/site/v2/sports/basketball/wnba/draft(thesite/v3variant 404s).wbb_event_officials/wnba_event_officials: one-row-per-official game-level officials list.- All eleven ship with full
@overloadtyping (mypy-strict), polars 1.x APIs, andsnake_casecolumns viadl_utils.underscore.
CFB — cfb_play_participants and __add_player_cols collapse
- New
cfb_play_participantsmodule hits the ESPNevents/{gid}/competitions/{gid}/playsparticipants endpoint, with$refresolution (default-on,resolve_missing=True) for athletes missing from the sidecar. cfb_pbp.__add_player_colsshrunk from 471 lines of regex extraction to ~130 lines that delegate to the participants module.- All 19 legacy
_player_namecolumns preserved via an alias mapping. - Hybrid scalar + list-column output:
{type}_player_nameplus{type}_player_names, so multi-entry types like split sacks aren't silently collapsed to a single name. - Targeted regex fallbacks retained as a tertiary safety net for
sack_player_name2,fg_block_player_name,punt_block_player_name, andinterception_player_name— ESPN's sidecar has documented gaps for those.
CFB — pandas → polars 1.x bug-fix reconciliation (0.36-live → main)
- Foundation: new
cleaned_textcolumn normalizes ESPN play descriptions and is the single source of truth for downstream feature extraction. - Behavioral: kneel-down semantics flag plus
scrimmage_playexclusion. - Yardage: structural rewrite of
__add_yardage_cols(~150-linenp.selectchain →pl.when().then()chain), pass-yards regex tightened from(?<=for)to(?<=[\s,]for), full punt rewrite, fair-catch fix. - Helper-features: end-state edge cases, NCG 2025 GW play hardcode,
lead_halfend-of-half fix, OOB punts block, FG classification correction,end.TimeSecsRemshift direction flipped from lag to lead — which is what WPA inputs expected all along. - WPA:
__process_wpaend-of-game branch rewrite plus onside-kick rewrite, pluspenalty_assessed_on_kickoffplumbing across__setup_penalty_data+__process_epa+__process_wpa. - Player names: extraction migrated to
cleaned_texteverywhere.
Infrastructure and tooling
- Polars 1.x migration across
cfb/cfb_pbp.py,nfl/nfl_pbp.py,mbb/mbb_pbp.py,nba/nba_pbp.py,nhl/nhl_pbp.py,wbb/wbb_pbp.py,wnba/wnba_pbp.py. Roughly 165 API translation sites:groupby→group_by,with_row_count→with_row_index,apply→map_elements(with explicitreturn_dtype), struct list-arg → varargs,shift_and_fill→shift,cumsum→cum_sum,str.strip→str.strip_chars,str.n_chars→str.len_chars, outer-join →full+coalesce,write_jsonkwargs. - Polars 1.x
is_insame-datatype deprecation: switched to.implode()for the global-containment idiom. pkg_resources.resource_filename→importlib.resources.files()incfb_pbp.pyandnfl_pbp.pyvia small_cfb_resource_filename/_nfl_resource_filenamehelpers. Setuptools 81+ removedpkg_resources, which made the legacy import emit aUserWarningat module load and (eventually) break entirely.download()retry rewrite: iterative loop instead of recursion, defensiveresponse = Noneinit, re-raises the last captured exception when the retry budget is exhausted.psutilmade optional indecorators.py(lazy import, previously an undeclared transitive dep that broke autodoc).pytest.inifilterwarnings for the transitivesphinxcontrib-jsmathlegacynspkg.pthUserWarningand thepkg_resourcesAPIDeprecationWarningsurfacing from setuptools 81+.- New tests under
tests/wbb/,tests/wnba/,tests/conftest.py(with the@skip_if_no_livedecorator gated bySDV_PY_LIVE_TESTS=1), andtests/README.mdcapturing the test conventions. NFL test files renamed to drop legacy-phase-jargon filenames in favor of descriptive names (test_nfl_loaders_parity_loaders.py,_unified.py,_aliases.py).
Bug fixes
test_havoc_ratecorrected for bothcfbandnfl:def_intfield name fix, bounded<=assertion,def_box.sort()for deterministic group_by emit order, andturnover_boxnow produces a cli warning instead of silently padding an empty dict.yds_puntedduplicate definition removed.drive.idNCG 2025 GW play hardcode.is_in(col)→is_in(col.implode())for global containment, applied acrosscfb_pbpandnfl_pbp.- Pickcenter regression test added for both CFB and NFL: a 2024+ game must NOT silently fall back to the
(2.5, 55.5, True, False)defaults; a pre-2024 game with populated legacypickcentermust continue to use that legacy path. - NFL
__helper_nfl_pbp_featuresdefensive cast for the case where ESPN returnsoverUnderas a Python float (no.astype()); same shape fix as the cfb_pbp version.
Deprecations
- Four NFL loader families now consolidate per-type variants into a single unified function:
load_nfl_nextgen_stats(stat_type=...)andload_nfl_pfr_advstats(stat_type=, summary_level=). The per-type names continue to work but emit aDeprecationWarningpointing at the unified function. No removal yet.
0.0.40 Release: December 6, 2025
- Minor changes to mbb_calendar and wbb_calendar functions to include all games, even when top 25 teams are not competing
0.0.38-39 Release: August 28, 2023
- Minor changes to cfb_pbp functions to improve WP calculation and player parsing.
0.0.36-37 Release: July 9, 2023
- Switched most under the hood dataframe operations to use the python
polarslibrary and many functions now have a parameterreturn_as_pandaswhich defaults toFalsebut can be set toTrueto return a pandas dataframe instead of a polars dataframe. This is a breaking change. - Added
**kwargswhich pass arguments to thedl_utils.download()function, includingheaders,proxy,timeout(default 30s),num_retries(default = 15),logger(default = None) - Function
espn_cfb_game_rosters()added. - Function
espn_nba_game_rosters()added. - Function
espn_nfl_game_rosters()added. - Function
espn_nhl_game_rosters()added. - Function
espn_wbb_game_rosters()added. - Function
espn_wnba_game_rosters()added. - Function
load_cfb_betting_lines()added (only 2006 through 2019).
0.0.34-35 Release: May 7-9, 2023
- Reconfigured some imports
- Improved compliance with pandas upgrades
- Updated loader locations to use sportsdataverse-data releases and nflverse releases
- Flattened the returned results somewhat for "sportsdataverse.cfb.espn_cfb_schedule()" functions, but also now including some nested data frame and list columns
0.0.18 Release: July 25, 2022
- Added ondays parameter to ESPN calendar functions
- Renamed "sportsdataverse.cfb.cfb_teams()" to "sportsdataverse.cfb.espn_cfb_teams()" to avoid an edge case issue when running the function.
0.0.17 Release: July 9, 2022
- Added MLBAM API functionality to the sportsdataverse-py package. For more information on how to use these new functions, refer to the docs.
- Fixed a bug where the "sportsdataverse.nfl.load_nfl_schedule()" function would cause a 404 error when run.
- For functions where multiple files are loaded in, progress bars have been added to indicate how far along the sportsdataverse-py package is in completing its task(s).
- Renamed "sportsdataverse.cfb.cfb_teams()" to "sportsdataverse.cfb.get_cfb_teams()" to avoid an edge case issue when running the function.
0.0.15 Release: May 8, 2022
- Refactor schedule and teams functions for all existing leagues.
- Created more robust home/away mappings to simplify assignment.
0.0.14 Release: March 16, 2022
- Refactor schedule and teams functions for all existing leagues.
- Created more robust home/away mappings to simplify assignment.
0.0.12 Release: February 24, 2022
- Minor refactor to all the pbp functions, attempting to normalize behavior.
- Adding raw parameter to same functions to return object as it comes in without any transformation
- Adding some config file corrections.
0.0.5 Release: October 20, 2021
- f'in round
- findin' out