Skip to main content

A synchronous and asynchronous client for the unofficial NHL API

Project description

slapshot

Python Version License

Purpose

slapshot is a synchronous and asynchronous client for the unofficial NHL API. It covers the four public NHL services behind a single, typed interface: api-web.nhle.com, api.nhle.com/stats/rest, records.nhl.com, and the player search at search.d3.nhle.com.

Why slapshot?

The NHL exposes a large, undocumented surface of endpoints spread across several hosts, each with its own path conventions, query parameters, and payload shapes. slapshot wraps them behind one typed interface, so the caller works with the data rather than with URL construction and response parsing.

Stable payloads decode into msgspec structs with snake_case attributes, while volatile payloads come back as decoded JSON. Decoding ignores unknown fields, so new NHL fields never break existing code. Every URL template lives in an enum, so no path is ever built from a loose string.

How It Works

slapshot has four layers:

  • Client (NHL, AsyncNHL): owns the httpx transport and exposes one attribute per resource.
  • Resources (client.standings, client.players, ...): group related endpoints into methods.
  • Request builders (slapshot.resources.*): validate arguments and produce a Request of URL and query parameters.
  • Endpoints (WebEndpoint, StatsEndpoint, RecordsEndpoint, SearchEndpoint): hold every URL template as a StrEnum.

The synchronous NHL and asynchronous AsyncNHL clients expose the same resources and the same method names. AsyncNHL mirrors NHL; its methods are awaitable.

Core Features

  • Synchronous and Asynchronous: an identical API on NHL and AsyncNHL, chosen per call site.
  • Typed Models: stable payloads decode into msgspec structs; volatile payloads return raw JSON.
  • Forward Compatible: decoding ignores unknown response fields, so new NHL fields never break existing code.
  • No Loose Paths: every endpoint is a StrEnum template, and slapshot validates identifiers, seasons, and dates before it sends a request.
  • Bring Your Own Transport: inject a preconfigured httpx client for proxies, retries, or custom headers.
  • Typed Errors: a single NHLError hierarchy covers transport, HTTP status, decode, and parameter failures.

Backing APIs

Service Base URL Resources
Web https://api-web.nhle.com/v1 standings, schedule, games, players, teams, draft
Stats https://api.nhle.com/stats/rest stats
Records https://records.nhl.com/site/api records
Search https://search.d3.nhle.com/api/v1 players.search

Installation

Prerequisites

  • Python 3.11 or newer
  • uv (recommended) or pip

From PyPI

uv add slapshot

Or with pip:

pip install slapshot

From Source

git clone https://github.com/braycarlson/slapshot
cd slapshot
uv sync

Usage

Runnable scripts for every resource live in examples/. Run one with uv run python examples/quickstart.py.

Synchronous

from slapshot import NHL, Milestone, Team


with NHL() as client:
    standings = client.standings.now()
    print(standings.standings[0].team_name.default)

    player = client.players.landing(8478402)
    print(player.first_name.default, player.last_name.default)

    skaters = client.stats.skaters(season=20252026, limit=5)
    milestone = client.records.milestone(Milestone.GOAL_CAREER_500)
    roster = client.teams.roster(Team.EDMONTON)

Asynchronous

import asyncio

from slapshot import AsyncNHL, GameType, Team


async def main() -> None:
    async with AsyncNHL() as client:
        schedule = await client.schedule.club_week(Team.EDMONTON)
        log = await client.players.game_log(8478402, season=20252026, game_type=GameType.REGULAR)
        results = await client.players.search('mcdavid', limit=3)


asyncio.run(main())

Resources

Every resource comes in both a synchronous and an asynchronous form, under the same attribute name and with the same method signatures.

Resource Backing API Methods
client.standings api-web.nhle.com now, by_date, seasons
client.schedule api-web.nhle.com now, by_date, club_week, club_month, club_season
client.games api-web.nhle.com landing, boxscore, play_by_play, right_rail, story, scores, scoreboard
client.players api-web.nhle.com, search.d3.nhle.com landing, game_log, spotlight, search, skater_leaders, goalie_leaders
client.teams api-web.nhle.com roster, roster_seasons, prospects, club_stats, club_stats_seasons
client.draft api-web.nhle.com rankings, picks
client.stats api.nhle.com/stats/rest skaters, goalies, teams, report, config, franchises, seasons, glossary
client.records records.nhl.com attendance, draft, franchises, franchise_details, franchise_season_records, franchise_season_results, franchise_team_totals, franchise_goalie_records, franchise_skater_records, milestone, officials, players, playoff_series, all_time_record, trophies

Models

Stable payloads decode into msgspec structs with snake_case attributes: StandingsResponse / StandingsTeam, Roster / RosterPlayer, PlayerLanding, PlayerSearchResult, and the ResultSet envelope (data, total) shared by every stats and records report. Volatile payloads (gamecenter, play-by-play, schedules, draft, leaders, config) return decoded JSON as-is.

A LocalizedName wraps localized text; its default attribute holds the English value.

The raw payload for any endpoint remains available through the request builders:

from slapshot.resources.standings import standings_now

request = standings_now()
raw = client.get(request)

Every URL template lives in slapshot.endpoints as a StrEnum (WebEndpoint, StatsEndpoint, RecordsEndpoint, and SearchEndpoint), so no path is built from a loose string:

from slapshot import WebEndpoint, request_build

url = WebEndpoint.PLAYER_LANDING.format(player=8478402)
request = request_build(url)
raw = client.get(request)

Enums

Team, GameType, Language, Milestone, and SortDirection are StrEnum / IntEnum values accepted anywhere the corresponding string or integer is.

Errors

Every exception inherits from slapshot.NHLError.

  • HTTP status: BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError, RateLimitError, ServerError, or the base APIStatusError.
  • Transport: NHLTimeoutError or NHLConnectionError.
  • Response: UnexpectedContentTypeError for a non-JSON response, and ResponseDecodeError when a body cannot be decoded or does not match the expected model.
  • Parameters: InvalidParameterError, which is also a ValueError, so existing except ValueError handlers keep working.

Detecting Unknown Fields

By default, decoding ignores fields the models do not define. To surface them instead, so nothing is silently dropped, construct a client with warn_unknown_fields=True. Any typed response whose payload carries an undefined field emits an UnknownFieldWarning naming the field paths.

from slapshot import NHL

with NHL(warn_unknown_fields=True) as client:
    client.standings.now()

The unknown_fields helper performs the same check directly, and tests/test_drift.py (marked network) runs it against the live API for every typed endpoint:

from slapshot import unknown_fields
from slapshot.models import StandingsResponse

missing = unknown_fields(raw_payload, StandingsResponse)

client.get(request) always returns the raw payload, so no data is lost regardless of the models.


Documentation

Full documentation lives in docs/, built with MkDocs. Serve it locally:

uv run --with mkdocs-material mkdocs serve
  • Installation: How to install slapshot and its dependencies
  • Quickstart: A first synchronous and asynchronous request
  • Client: Constructing NHL and AsyncNHL, timeouts, and custom transports
  • Resources: Every resource and method, with parameters and return types
  • Models: The msgspec structs returned by typed endpoints
  • Enums: Team, GameType, Language, Milestone, and SortDirection
  • Errors: The exception hierarchy and when each error is raised
  • Raw Requests: Dropping down to request builders and endpoints

Development

uv sync installs the development dependencies. Three tools check the project:

Command Purpose
uv run ruff check Lint
uv run ty check Type check
uv run python -m pytest Run the test suite

Tests marked network hit the live NHL API; pytest deselects them by default. Run them with uv run python -m pytest -m network.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

slapshot-0.1.0.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

slapshot-0.1.0-py3-none-any.whl (29.2 kB view details)

Uploaded Python 3

File details

Details for the file slapshot-0.1.0.tar.gz.

File metadata

  • Download URL: slapshot-0.1.0.tar.gz
  • Upload date:
  • Size: 20.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for slapshot-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9221465468c8613b189e8127428a77227855da87227c76dc244c37e0358977ac
MD5 92712b166fb81e08ba6537ae19ed8af9
BLAKE2b-256 e864f315fd38c951f277dec8cef0ab0a85086fccc875146ccd1df5231515fc99

See more details on using hashes here.

File details

Details for the file slapshot-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: slapshot-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 29.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for slapshot-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7d4b7884f963e28e4e629c1ce112c17339021e2b9bf0bf79ed9db662fab5b2a2
MD5 0de996e5e0e627a274fd2a4b47949d6a
BLAKE2b-256 6470a9ac57ff7e0ef22ee7142988879ffedf22f53b0305c856a72757ccf6a65a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page