NHL API Module

NHL API client with retry logic and rate limiting.

The API module provides an async HTTP client for fetching roster data from the official NHL API with built-in error handling, retries, and rate limiting.

NHL API client module.

class nhl_scrabble.api.NHLApiClient(base_url=None, timeout=10, retries=3, rate_limit_max_requests=30, rate_limit_window=60.0, backoff_factor=2.0, max_backoff=30.0, cache_enabled=True, cache_expiry=3600, cache_dir=None, verify_ssl=True, dos_max_connections=10, dos_max_per_host=5, dos_circuit_breaker_threshold=5, dos_circuit_breaker_timeout=60.0)[source]

Bases: object

Client for interacting with the NHL API.

This client provides methods to fetch team standings and roster data from the official NHL API with built-in retry logic, rate limiting, SSRF protection, DoS prevention, and enforced SSL/TLS certificate verification.

SSL/TLS Security:
  • Certificate verification is always enabled and cannot be disabled

  • Uses certifi CA bundle for up-to-date certificate authorities

  • SSL errors are caught and logged for security monitoring

DoS Prevention:
  • Circuit breaker pattern to prevent cascading failures

  • Connection pool limits to prevent resource exhaustion

  • Configurable failure thresholds and timeouts

base_url

Base URL for the NHL API (SSRF-validated)

timeout

Request timeout in seconds

retries

Number of retry attempts for failed requests

rate_limiter

Token bucket rate limiter for API requests

circuit_breaker

Circuit breaker for DoS prevention

ca_bundle

Path to CA bundle for SSL verification (uses certifi)

BASE_URL = 'https://api-web.nhle.com/v1'
__init__(base_url=None, timeout=10, retries=3, rate_limit_max_requests=30, rate_limit_window=60.0, backoff_factor=2.0, max_backoff=30.0, cache_enabled=True, cache_expiry=3600, cache_dir=None, verify_ssl=True, dos_max_connections=10, dos_max_per_host=5, dos_circuit_breaker_threshold=5, dos_circuit_breaker_timeout=60.0)[source]

Initialize the NHL API client.

Parameters:
  • base_url (str | None) – Base URL for NHL API (default: https://api-web.nhle.com/v1). Will be validated for SSRF protection on first request.

  • timeout (int) – Request timeout in seconds (default: 10)

  • retries (int) – Number of retry attempts for failed requests (default: 3)

  • rate_limit_max_requests (int) – Maximum requests per time window (default: 30)

  • rate_limit_window (float) – Time window for rate limiting in seconds (default: 60.0)

  • backoff_factor (float) – Exponential backoff multiplier (default: 2.0)

  • max_backoff (float) – Maximum backoff delay in seconds (default: 30.0)

  • cache_enabled (bool) – Enable HTTP caching (default: True)

  • cache_expiry (int) – Cache expiration in seconds (default: 3600 = 1 hour)

  • cache_dir (str | Path | None) – Cache directory path (default: platform-specific user cache directory)

  • verify_ssl (bool) – SSL verification (must be True, cannot be disabled for security)

  • dos_max_connections (int) – Maximum connection pool connections (default: 10)

  • dos_max_per_host (int) – Maximum connections per host (default: 5)

  • dos_circuit_breaker_threshold (int) – Circuit breaker failure threshold (default: 5)

  • dos_circuit_breaker_timeout (float) – Circuit breaker timeout in seconds (default: 60.0)

Raises:
  • NHLApiError – If base_url fails SSRF protection validation or cache directory not writable

  • ValueError – If verify_ssl is False (SSL verification cannot be disabled)

__del__()[source]

Destructor - close session if not already closed (safety net).

Return type:

None

get_teams(season=None)[source]

Fetch all NHL teams with division and conference information.

This method uses the retry decorator to automatically retry on network errors. The URL is validated with SSRF protection before making the request.

Parameters:

season (str | None) – Optional season in format ‘YYYYYYYY’ (e.g., ‘20222023’ for 2022-23). If None, fetches current season data.

Returns:

{

‘TOR’: {‘division’: ‘Atlantic’, ‘conference’: ‘Eastern’}, ‘MTL’: {‘division’: ‘Atlantic’, ‘conference’: ‘Eastern’}, …

}

Return type:

dict[str, dict[str, str]]

Raises:

Examples

>>> client = NHLApiClient()
>>> teams = client.get_teams()
>>> "TOR" in teams
True
>>> teams_2022 = client.get_teams(season="20222023")
>>> "TOR" in teams_2022
True
get_team_roster(team_abbrev, season=None)[source]

Fetch the roster for a specific team with input and response validation.

Validates team abbreviation before making API call and validates response structure to prevent errors from malformed data.

The URL is validated with SSRF protection before making the request.

Parameters:
  • team_abbrev (str) – Team abbreviation (e.g., ‘TOR’, ‘MTL’)

  • season (str | None) – Optional season in format ‘YYYYYYYY’ (e.g., ‘20222023’ for 2022-23). If None, fetches current season roster.

Return type:

dict[str, Any]

Returns:

Dictionary containing roster data with ‘forwards’, ‘defensemen’, and ‘goalies’ keys

Raises:
  • ValidationError – If team abbreviation is invalid

  • NHLApiNotFoundError – If the roster is not found (404 response)

  • NHLApiConnectionError – If unable to connect to the API after all retries

  • NHLApiError – For other API errors, including SSRF protection blocks and malformed responses

Security:
  • Validates team abbreviation to prevent injection attacks

  • Validates response structure to prevent KeyError exceptions

  • Sanitizes player names from API responses

  • SSRF protection on all API requests

Examples

>>> client = NHLApiClient()
>>> roster = client.get_team_roster("TOR")
>>> "forwards" in roster
True
>>> roster_2022 = client.get_team_roster("TOR", season="20222023")
>>> "forwards" in roster_2022
True
>>> client.get_team_roster("INVALID")
Traceback (most recent call last):
ValidationError: Team abbreviation must be 2-3 characters...
get_rate_limit_stats()[source]

Get rate limiter statistics.

Returns:

  • total_requests: Total requests made

  • total_waits: Total times waited for tokens

  • total_wait_time: Total time spent waiting

  • average_wait: Average wait time per wait

  • current_tokens: Current token count

  • max_tokens: Maximum token capacity

Return type:

dict[str, Any]

Examples

>>> client = NHLApiClient()
>>> stats = client.get_rate_limit_stats()
>>> "total_requests" in stats
True
clear_cache()[source]

Clear the HTTP cache.

Return type:

None

get_cache_info()[source]

Get cache statistics and information.

Returns:

  • enabled (bool): Whether caching is enabled

  • backend (str | None): Cache backend type (e.g., “sqlite”)

  • size (int | None): Number of cached responses

  • expiry (int): Cache expiry time in seconds

Return type:

dict[str, Any]

Examples

>>> client = NHLApiClient(cache_enabled=True)
>>> info = client.get_cache_info()
>>> print(info["enabled"])
True
>>> print(info["backend"])
'sqlite'
close()[source]

Close the session and release resources.

Return type:

None

__enter__()[source]

Support context manager protocol.

Return type:

NHLApiClient

__exit__(exc_type, exc_val, exc_tb)[source]

Close session when exiting context manager.

Return type:

None

exception nhl_scrabble.api.NHLApiConnectionError[source]

Bases: NHLApiError

Raised when unable to connect to the NHL API.

This includes network timeouts, connection refused, DNS failures, and other connection-related issues.

Examples

>>> raise NHLApiConnectionError("Unable to connect to NHL API after 3 retries")
Traceback (most recent call last):
NHLApiConnectionError: Unable to connect to NHL API after 3 retries
exception nhl_scrabble.api.NHLApiError[source]

Bases: APIError

Base exception for NHL API errors.

Raised for NHL API-specific errors including HTTP errors, invalid responses, and API-level failures.

Examples

>>> raise NHLApiError("NHL API returned invalid data")
Traceback (most recent call last):
NHLApiError: NHL API returned invalid data
exception nhl_scrabble.api.NHLApiNotFoundError[source]

Bases: NHLApiError

Raised when the requested resource is not found (404).

Indicates that the NHL API returned a 404 status code, meaning the requested resource (team, roster, etc.) does not exist.

Examples

>>> raise NHLApiNotFoundError("Roster not found for team: XYZ")
Traceback (most recent call last):
NHLApiNotFoundError: Roster not found for team: XYZ
exception nhl_scrabble.api.NHLApiSSLError[source]

Bases: NHLApiError

Raised when SSL/TLS certificate verification fails.

Indicates a problem with SSL/TLS certificate validation, which could be a security issue or a configuration problem.

Examples

>>> raise NHLApiSSLError("SSL certificate verification failed for api.nhle.com")
Traceback (most recent call last):
NHLApiSSLError: SSL certificate verification failed for api.nhle.com

NHL Client

NHL API client for fetching team and roster data.

class nhl_scrabble.api.nhl_client.NHLApiClient(base_url=None, timeout=10, retries=3, rate_limit_max_requests=30, rate_limit_window=60.0, backoff_factor=2.0, max_backoff=30.0, cache_enabled=True, cache_expiry=3600, cache_dir=None, verify_ssl=True, dos_max_connections=10, dos_max_per_host=5, dos_circuit_breaker_threshold=5, dos_circuit_breaker_timeout=60.0)[source]

Bases: object

Client for interacting with the NHL API.

This client provides methods to fetch team standings and roster data from the official NHL API with built-in retry logic, rate limiting, SSRF protection, DoS prevention, and enforced SSL/TLS certificate verification.

SSL/TLS Security:
  • Certificate verification is always enabled and cannot be disabled

  • Uses certifi CA bundle for up-to-date certificate authorities

  • SSL errors are caught and logged for security monitoring

DoS Prevention:
  • Circuit breaker pattern to prevent cascading failures

  • Connection pool limits to prevent resource exhaustion

  • Configurable failure thresholds and timeouts

base_url

Base URL for the NHL API (SSRF-validated)

timeout

Request timeout in seconds

retries

Number of retry attempts for failed requests

rate_limiter

Token bucket rate limiter for API requests

circuit_breaker

Circuit breaker for DoS prevention

ca_bundle

Path to CA bundle for SSL verification (uses certifi)

BASE_URL = 'https://api-web.nhle.com/v1'
__init__(base_url=None, timeout=10, retries=3, rate_limit_max_requests=30, rate_limit_window=60.0, backoff_factor=2.0, max_backoff=30.0, cache_enabled=True, cache_expiry=3600, cache_dir=None, verify_ssl=True, dos_max_connections=10, dos_max_per_host=5, dos_circuit_breaker_threshold=5, dos_circuit_breaker_timeout=60.0)[source]

Initialize the NHL API client.

Parameters:
  • base_url (str | None) – Base URL for NHL API (default: https://api-web.nhle.com/v1). Will be validated for SSRF protection on first request.

  • timeout (int) – Request timeout in seconds (default: 10)

  • retries (int) – Number of retry attempts for failed requests (default: 3)

  • rate_limit_max_requests (int) – Maximum requests per time window (default: 30)

  • rate_limit_window (float) – Time window for rate limiting in seconds (default: 60.0)

  • backoff_factor (float) – Exponential backoff multiplier (default: 2.0)

  • max_backoff (float) – Maximum backoff delay in seconds (default: 30.0)

  • cache_enabled (bool) – Enable HTTP caching (default: True)

  • cache_expiry (int) – Cache expiration in seconds (default: 3600 = 1 hour)

  • cache_dir (str | Path | None) – Cache directory path (default: platform-specific user cache directory)

  • verify_ssl (bool) – SSL verification (must be True, cannot be disabled for security)

  • dos_max_connections (int) – Maximum connection pool connections (default: 10)

  • dos_max_per_host (int) – Maximum connections per host (default: 5)

  • dos_circuit_breaker_threshold (int) – Circuit breaker failure threshold (default: 5)

  • dos_circuit_breaker_timeout (float) – Circuit breaker timeout in seconds (default: 60.0)

Raises:
  • NHLApiError – If base_url fails SSRF protection validation or cache directory not writable

  • ValueError – If verify_ssl is False (SSL verification cannot be disabled)

session: CachedSession | Session
__del__()[source]

Destructor - close session if not already closed (safety net).

Return type:

None

get_teams(season=None)[source]

Fetch all NHL teams with division and conference information.

This method uses the retry decorator to automatically retry on network errors. The URL is validated with SSRF protection before making the request.

Parameters:

season (str | None) – Optional season in format ‘YYYYYYYY’ (e.g., ‘20222023’ for 2022-23). If None, fetches current season data.

Returns:

{

‘TOR’: {‘division’: ‘Atlantic’, ‘conference’: ‘Eastern’}, ‘MTL’: {‘division’: ‘Atlantic’, ‘conference’: ‘Eastern’}, …

}

Return type:

dict[str, dict[str, str]]

Raises:

Examples

>>> client = NHLApiClient()
>>> teams = client.get_teams()
>>> "TOR" in teams
True
>>> teams_2022 = client.get_teams(season="20222023")
>>> "TOR" in teams_2022
True
get_team_roster(team_abbrev, season=None)[source]

Fetch the roster for a specific team with input and response validation.

Validates team abbreviation before making API call and validates response structure to prevent errors from malformed data.

The URL is validated with SSRF protection before making the request.

Parameters:
  • team_abbrev (str) – Team abbreviation (e.g., ‘TOR’, ‘MTL’)

  • season (str | None) – Optional season in format ‘YYYYYYYY’ (e.g., ‘20222023’ for 2022-23). If None, fetches current season roster.

Return type:

dict[str, Any]

Returns:

Dictionary containing roster data with ‘forwards’, ‘defensemen’, and ‘goalies’ keys

Raises:
  • ValidationError – If team abbreviation is invalid

  • NHLApiNotFoundError – If the roster is not found (404 response)

  • NHLApiConnectionError – If unable to connect to the API after all retries

  • NHLApiError – For other API errors, including SSRF protection blocks and malformed responses

Security:
  • Validates team abbreviation to prevent injection attacks

  • Validates response structure to prevent KeyError exceptions

  • Sanitizes player names from API responses

  • SSRF protection on all API requests

Examples

>>> client = NHLApiClient()
>>> roster = client.get_team_roster("TOR")
>>> "forwards" in roster
True
>>> roster_2022 = client.get_team_roster("TOR", season="20222023")
>>> "forwards" in roster_2022
True
>>> client.get_team_roster("INVALID")
Traceback (most recent call last):
ValidationError: Team abbreviation must be 2-3 characters...
get_rate_limit_stats()[source]

Get rate limiter statistics.

Returns:

  • total_requests: Total requests made

  • total_waits: Total times waited for tokens

  • total_wait_time: Total time spent waiting

  • average_wait: Average wait time per wait

  • current_tokens: Current token count

  • max_tokens: Maximum token capacity

Return type:

dict[str, Any]

Examples

>>> client = NHLApiClient()
>>> stats = client.get_rate_limit_stats()
>>> "total_requests" in stats
True
clear_cache()[source]

Clear the HTTP cache.

Return type:

None

get_cache_info()[source]

Get cache statistics and information.

Returns:

  • enabled (bool): Whether caching is enabled

  • backend (str | None): Cache backend type (e.g., “sqlite”)

  • size (int | None): Number of cached responses

  • expiry (int): Cache expiry time in seconds

Return type:

dict[str, Any]

Examples

>>> client = NHLApiClient(cache_enabled=True)
>>> info = client.get_cache_info()
>>> print(info["enabled"])
True
>>> print(info["backend"])
'sqlite'
close()[source]

Close the session and release resources.

Return type:

None

__enter__()[source]

Support context manager protocol.

Return type:

NHLApiClient

__exit__(exc_type, exc_val, exc_tb)[source]

Close session when exiting context manager.

Return type:

None

exception nhl_scrabble.api.nhl_client.NHLApiConnectionError[source]

Bases: NHLApiError

Raised when unable to connect to the NHL API.

This includes network timeouts, connection refused, DNS failures, and other connection-related issues.

Examples

>>> raise NHLApiConnectionError("Unable to connect to NHL API after 3 retries")
Traceback (most recent call last):
NHLApiConnectionError: Unable to connect to NHL API after 3 retries
exception nhl_scrabble.api.nhl_client.NHLApiError[source]

Bases: APIError

Base exception for NHL API errors.

Raised for NHL API-specific errors including HTTP errors, invalid responses, and API-level failures.

Examples

>>> raise NHLApiError("NHL API returned invalid data")
Traceback (most recent call last):
NHLApiError: NHL API returned invalid data
exception nhl_scrabble.api.nhl_client.NHLApiNotFoundError[source]

Bases: NHLApiError

Raised when the requested resource is not found (404).

Indicates that the NHL API returned a 404 status code, meaning the requested resource (team, roster, etc.) does not exist.

Examples

>>> raise NHLApiNotFoundError("Roster not found for team: XYZ")
Traceback (most recent call last):
NHLApiNotFoundError: Roster not found for team: XYZ
exception nhl_scrabble.api.nhl_client.NHLApiSSLError[source]

Bases: NHLApiError

Raised when SSL/TLS certificate verification fails.

Indicates a problem with SSL/TLS certificate validation, which could be a security issue or a configuration problem.

Examples

>>> raise NHLApiSSLError("SSL certificate verification failed for api.nhle.com")
Traceback (most recent call last):
NHLApiSSLError: SSL certificate verification failed for api.nhle.com

NHLClient

Async HTTP client for NHL API with context manager support.

Features:

  • Async/await support with aiohttp

  • Automatic retry with exponential backoff

  • Rate limiting with configurable delay

  • Timeout handling

  • Session management via context manager

  • Comprehensive error handling

Configuration:

Environment variables for customization:

  • NHL_SCRABBLE_API_TIMEOUT - Request timeout in seconds (default: 10)

  • NHL_SCRABBLE_API_RETRIES - Number of retry attempts (default: 3)

  • NHL_SCRABBLE_RATE_LIMIT_DELAY - Delay between requests in seconds (default: 0.3)

Usage:

from nhl_scrabble.api import NHLClient
import asyncio


async def fetch_data():
    async with NHLClient() as client:
        # Fetch all teams
        teams = await client.fetch_all_teams()

        # Fetch roster for specific team
        roster = await client.fetch_team_roster("TOR")

        # Fetch all rosters (with rate limiting)
        all_rosters = await client.fetch_all_rosters()

    return teams, all_rosters


# Run async code
teams, rosters = asyncio.run(fetch_data())

Methods

fetch_all_teams

Fetch metadata for all NHL teams including division and conference.

Returns:

  • List of Team objects with: * Team ID, abbreviation, full name * Division and conference assignments * Logo URL

Endpoint:

GET https://api-web.nhle.com/v1/standings/now

Example:

async with NHLClient() as client:
    teams = await client.fetch_all_teams()

for team in teams:
    print(f"{team.name} ({team.abbrev}) - {team.division}, {team.conference}")

fetch_team_roster

Fetch current roster for a specific team.

Parameters:

  • team_abbrev - Team abbreviation (e.g., ‘TOR’, ‘MTL’, ‘NYR’)

Returns:

  • List of Player objects for the team’s current roster

Endpoint:

GET https://api-web.nhle.com/v1/roster/{team_abbrev}/current

Example:

async with NHLClient() as client:
    roster = await client.fetch_team_roster("TOR")

print(f"Toronto Maple Leafs roster: {len(roster)} players")
for player in roster[:5]:
    print(f"  {player.firstName} {player.lastName}")

fetch_all_rosters

Fetch rosters for all NHL teams with rate limiting.

Returns:

  • Dictionary mapping team abbreviations to lists of Player objects

Example:

async with NHLClient() as client:
    all_rosters = await client.fetch_all_rosters()

total_players = sum(len(roster) for roster in all_rosters.values())
print(f"Total players across NHL: {total_players}")

Error Handling

The client handles various error conditions:

Network Errors:

  • Connection timeouts → Automatic retry with exponential backoff

  • Network unavailable → Raises with descriptive error message

  • DNS failures → Raises with connection details

HTTP Errors:

  • 404 Not Found → Logs warning, returns empty list

  • 429 Too Many Requests → Respects retry-after header

  • 500+ Server Errors → Retries with backoff

Example Error Handling:

from nhl_scrabble.api import NHLClient
import aiohttp
import asyncio


async def safe_fetch():
    try:
        async with NHLClient() as client:
            teams = await client.fetch_all_teams()
    except aiohttp.ClientError as e:
        print(f"Network error: {e}")
        teams = []
    except asyncio.TimeoutError:
        print("Request timed out")
        teams = []

    return teams

Rate Limiting

The client implements rate limiting to be respectful to the NHL API:

  • Default delay: 0.3 seconds between roster fetches

  • Configurable via NHL_SCRABBLE_RATE_LIMIT_DELAY

  • Applied in fetch_all_rosters() method

  • Uses asyncio.sleep() for non-blocking delays

Custom Rate Limit:

import os

os.environ["NHL_SCRABBLE_RATE_LIMIT_DELAY"] = "0.5"  # 500ms delay

async with NHLClient() as client:
    rosters = await client.fetch_all_rosters()  # Uses 500ms delay

Retry Logic

Automatic retry with exponential backoff:

  • Retries: Configurable (default: 3)

  • Backoff: Exponential (1s, 2s, 4s)

  • Conditions: Network errors, timeouts, 5xx errors

  • No Retry: 4xx errors (client errors)

Custom Retry Configuration:

import os

os.environ["NHL_SCRABBLE_API_RETRIES"] = "5"  # 5 retry attempts

async with NHLClient() as client:
    teams = await client.fetch_all_teams()  # Up to 5 retries