Source code for gamesheet_sdk.admin.leagues
# Copyright (c) 2026 bdperkin
# SPDX-License-Identifier: MIT
"""GameSheet leagues: divisions within an association.
A league is a division, tier, or other grouping within an association (e.g., "18U AAA", "Bantam", etc.). Each
league belongs to exactly one association. The dashboard displays leagues after navigating into an association
view. This module talks to the GameSheet JSON:API at ``/api/associations/{association_id}/leagues`` directly
with the lightweight :class:`gamesheet_sdk.Session` path -- no Playwright needed for read-only access once a
bearer token has been obtained (typically by reading the SPA's ``accessToken`` from the saved browser storage
state via :func:`gamesheet_sdk.common.auth.load_access_token`).
Example:
Retrieve all leagues for a given association:
.. code-block:: python
from gamesheet_sdk.admin.leagues import list_leagues
from gamesheet_sdk.common.auth import load_access_token
from gamesheet_sdk.common.constants import PLAY_GAMESHEET_APP
from gamesheet_sdk.common.session import Session
# Create authenticated session
session = Session(base_url=PLAY_GAMESHEET_APP)
token = load_access_token()
session.set_bearer_token(token)
# List leagues for association "12345"
leagues = list_leagues(session, association_id="12345")
for league in leagues:
print(f"{league.title} (ID: {league.id})")
"""
from __future__ import annotations
from datetime import datetime
from typing import TYPE_CHECKING, Any
from pydantic import BaseModel, Field
from gamesheet_sdk.admin.shared import parse_jsonapi_resource
from gamesheet_sdk.common.shared import JSONAPI_HEADERS, handle_response
from gamesheet_sdk.common.shared.constants import FIELD_DESC_PARENT_ASSOCIATION_ID
if TYPE_CHECKING:
from gamesheet_sdk.common.session import Session
_ENDPOINT_TEMPLATE = "/api/associations/{association_id}/leagues"
[docs]
class League(BaseModel):
"""A single league.
Maps the ``data[*]`` items in the JSON: API response of ``GET /api/associations/{id}/leagues`` to a flat
typed model.
Attributes:
id (str): League identifier (string in JSON:API).
association_id (str): Parent association identifier.
title (str): Display name of the league.
created_at (datetime): When the league was created.
updated_at (datetime): Last time the league was updated.
"""
id: str = Field(description="League identifier (string in JSON:API).")
association_id: str = Field(description=FIELD_DESC_PARENT_ASSOCIATION_ID)
title: str = Field(description="Display name of the league.")
created_at: datetime = Field(description="When the league was created.")
updated_at: datetime = Field(description="Last time the league was updated.")
def _parse(item: dict[str, Any], association_id: str) -> League:
"""Flatten a JSON:API resource object into a :class:`League`.
Extracts the ``id`` from the top-level resource object and merges ``attributes`` to produce a flat
pydantic model. Internal helper for :func:`list_leagues`.
Args:
item (dict[str, Any]): A single JSON:API resource object from the ``data`` array, with top-level
``id`` and nested ``attributes``.
association_id (str): The parent association identifier to attach to the resulting model.
Returns:
League: Return value.
"""
data = parse_jsonapi_resource(item)
data["association_id"] = association_id
return League(**data)
[docs]
def get_league(session: Session, association_id: str, league_id: str) -> League:
"""Get a single league by ID.
The supplied :class:`Session` must already carry a bearer token (e.g. via
:meth:`Session.set_bearer_token`); the call is otherwise unauthenticated and will 401.
Args:
session (Session): An authenticated :class:`Session`.
association_id (str): The parent association identifier.
league_id (str): The league identifier to retrieve.
Returns:
League: Return value.
"""
endpoint = f"{_ENDPOINT_TEMPLATE.format(association_id=association_id)}/{league_id}"
response = session.get(endpoint, headers=JSONAPI_HEADERS)
handle_response(response, endpoint, "GET league")
body: dict[str, Any] = response.json()
return _parse(body["data"], association_id)
[docs]
def list_leagues(session: Session, association_id: str) -> list[League]:
"""Return every league in the specified association.
The supplied :class:`Session` must already carry a bearer token (e.g. via
:meth:`Session.set_bearer_token`); the call is otherwise unauthenticated and will 401.
Args:
session (Session): An authenticated :class:`Session`.
association_id (str): The association identifier whose leagues to list.
Returns:
list[League]: A list of :class:`League`, in the order the server returned them. The list may be empty
if the association has no leagues.
"""
endpoint = _ENDPOINT_TEMPLATE.format(association_id=association_id)
response = session.get(endpoint, headers=JSONAPI_HEADERS)
handle_response(response, endpoint, "GET leagues")
body: dict[str, Any] = response.json()
return [_parse(item, association_id) for item in body.get("data", [])]