pippen.data.hoopr¶
Bulk historical download from the hoopR NBA data repository, which publishes one Parquet file per season under CC BY 4.0.
Downloads are atomic. Each file streams to a temporary sibling, has its Parquet footer validated, and only then replaces the target. A crash partway through cannot leave a truncated file where a later run would skip it as already present, which would silently poison every calculation downstream.
pippen.data.hoopr ¶
Downloader for hoopR's bulk historical NBA data.
hoopR (part of the SportsDataverse project) publishes ESPN-sourced NBA data as
one Parquet file per dataset per season, hosted in the hoopR-nba-data
GitHub repository::
https://github.com/sportsdataverse/hoopR-nba-data
Attribution and licence
That repository is licensed CC BY 4.0, which permits redistribution
with attribution. :data:ATTRIBUTION holds the required credit line;
anything this project publishes that is derived from hoopR data must
carry it (see docs/research/06_data_sources.md).
Season numbering
season throughout this module is the season's end year: 2024 means
the 2023-24 season. This matches pippen.paths.season_file.
Known limitation -- load-bearing for the rest of the pipeline
hoopR's play_by_play dataset is ESPN-sourced and records only event
participants (athlete_id_1/2/3 columns). It has no on-court
lineup column. RAPM needs the full five-man lineup for every
possession, so it cannot be built from this dataset alone; that requires
pbpstats running against NBA API data instead. This module only
downloads the file -- it makes no claim about what can be computed from
its contents.
File layout
Most datasets nest their Parquet files under a parquet/
subdirectory (nba/{dataset}/parquet/{dataset}_{season}.parquet); at
least one does not. Rather than hard-coding one guess, every download
probes both layouts with a HEAD request and uses whichever one answers.
One dataset's *directory* name diverges from its file-name prefix:
``play_by_play`` is published under ``nba/pbp/parquet/``, not
``nba/play_by_play/parquet/``, even though the files inside it are still
named ``play_by_play_{season}.parquet``. Confirmed live against the
repository on 2026-09-10 -- the URL pattern in the project's own data
source notes does not account for this, and probing alone cannot recover
it, since ``nba/play_by_play/`` simply does not exist. See
:data:`_REMOTE_DIRECTORY`.
TransientDownloadError ¶
Bases: Exception
A network failure worth retrying: timeout, connection reset, or 5xx.
Anything else -- a 404, a malformed URL, a disk error while writing -- is treated as permanent for the attempt and is not retried.
Source code in src/pippen/data/hoopr.py
DownloadResult
dataclass
¶
Outcome of trying to get one season of one dataset onto disk.
Attributes:
| Name | Type | Description |
|---|---|---|
dataset |
str
|
The hoopR dataset name. |
season |
int
|
Season end year, e.g. 2024 for the 2023-24 season. |
status |
DownloadStatus
|
|
path |
Path | None
|
Where the file ended up on disk. Set for |
reason |
str | None
|
Human-readable cause of failure. Set only when |
Source code in src/pippen/data/hoopr.py
download_season ¶
Download one season of one hoopR dataset into the raw data stage.
The file is streamed to a temporary path beside the destination and only moved into place after it passes an integrity check, so a crash or a truncated transfer never leaves a half-written file at the final path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
hoopR dataset name, e.g. |
required |
season
|
int
|
Season end year, e.g. 2024 for the 2023-24 season. |
required |
force
|
bool
|
When true, re-download even if a valid file already exists at the destination. When false (the default), an existing file that passes the integrity check is left alone. |
False
|
timeout
|
float
|
Per-request timeout in seconds, applied to every HTTP call this makes. |
_DEFAULT_TIMEOUT_SECONDS
|
session
|
Session | None
|
HTTP session to issue requests on. A short-lived session is
created and closed automatically when omitted; callers doing many
downloads should pass one in to reuse connections (see
:func: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
DownloadResult
|
class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/pippen/data/hoopr.py
download_seasons ¶
Download a range of seasons of one hoopR dataset, reporting progress.
A single HTTP session is reused across the whole range, so the underlying
TCP/TLS connection to GitHub is kept warm instead of renegotiated for
every file. One season failing does not stop the rest: each season gets
its own :class:DownloadResult, so a caller can see exactly which ones
need a retry later.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
hoopR dataset name, e.g. |
required |
seasons
|
Iterable[int]
|
Season end years to fetch, e.g. |
required |
force
|
bool
|
When true, re-download every season even if a valid file already exists. |
False
|
timeout
|
float
|
Per-request timeout in seconds, applied to every HTTP call. |
_DEFAULT_TIMEOUT_SECONDS
|
console
|
Console | None
|
Where to print progress. Defaults to a new
:class: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
One |
list[DownloadResult]
|
class: |
list[DownloadResult]
|
iterated. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/pippen/data/hoopr.py
is_master_dataset ¶
Whether a dataset is published as one file rather than one per season.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
hoopR dataset name. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the dataset has a single master file covering every season. |
Source code in src/pippen/data/hoopr.py
download_master ¶
Download a dataset published as one file covering every season.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
hoopR dataset name, which must be a master dataset. Use
:func: |
required |
force
|
bool
|
Re-download even when a valid file is already present. |
False
|
timeout
|
float
|
Per-request timeout in seconds. |
30.0
|
session
|
Session | None
|
HTTP session to reuse. A new one is created if omitted. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
DownloadResult
|
class: |
DownloadResult
|
every season and belongs to none of them. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |