Source code for gamesheet_sdk.admin.associations

# Copyright (c) 2026 bdperkin
# SPDX-License-Identifier: MIT

"""GameSheet associations: the top-level organizational unit of the platform.

An association corresponds to a league operator (a hockey association, a tournament series, a district body,
etc.). The dashboard's first view after login lists the associations the signed-in user has access to. This
module talks to the GameSheet JSON:API at ``/api/associations`` 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 associations accessible by the authenticated user:

    .. code-block:: python

        from gamesheet_sdk.common.auth import load_access_token
        from gamesheet_sdk.common.session import Session
        from gamesheet_sdk.admin.associations import list_associations

        # Load the saved access token from previous login
        token = load_access_token()
        # Create an authenticated session
        session = Session(base_url="https://gamesheet.app")
        session.set_bearer_token(token)
        # Fetch all associations
        associations = list_associations(session)
        # Display results
        for assoc in associations:
            print(f"{assoc.title} (ID: {assoc.id})")
            print(f"  Created: {assoc.created_at}")
            print(f"  Logo: {assoc.logo or '(none)'}")

"""

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

if TYPE_CHECKING:
    from gamesheet_sdk.common.session import Session

_ENDPOINT = "/api/associations"


[docs] class Association(BaseModel): """A single association. Maps the ``data[*]`` items in the JSON: API response of ``GET /api/associations`` to a flat typed model. Attributes: id (str): Association identifier (string in JSON:API). title (str): Display name of the association. logo (str): Logo asset URL, possibly empty string. created_at (datetime): When the association was created. updated_at (datetime): Last time the association was updated. """ id: str = Field(description="Association identifier (string in JSON:API).") title: str = Field(description="Display name of the association.") logo: str = Field(default="", description="Logo asset URL, possibly empty.") created_at: datetime = Field(description="When the association was created.") updated_at: datetime = Field(description="Last time the association was updated.")
def _parse(item: dict[str, Any]) -> Association: """Flatten a JSON:API resource object into an :class:`Association`. Args: item (dict[str, Any]): A JSON:API resource object with ``id`` and ``attributes`` keys. Returns: Association: Parsed Association model instance. """ return Association(**parse_jsonapi_resource(item))
[docs] def get_association(session: Session, association_id: str) -> Association: """Get a single association 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 association identifier to retrieve. Returns: Association: The requested Association model instance. """ endpoint = f"{_ENDPOINT}/{association_id}" response = session.get(endpoint, headers=JSONAPI_HEADERS) handle_response(response, endpoint, "GET association") body: dict[str, Any] = response.json() return _parse(body["data"])
[docs] def list_associations(session: Session) -> list[Association]: """Return every association the authenticated user can see. 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`. Returns: list[Association]: A list of :class:`Association`, in the order the server returned them. The list may be empty if the user has access to no associations. """ response = session.get(_ENDPOINT, headers=JSONAPI_HEADERS) handle_response(response, _ENDPOINT, "GET associations") body: dict[str, Any] = response.json() return [_parse(item) for item in body.get("data", [])]