Metadata-Version: 2.4
Name: gamesheet-sdk-py
Version: 0.1.38
Summary: Unofficial Python SDK and CLI for the GameSheet Inc. platform — automates WebUI procedures where a native API is absent.
Project-URL: Documentation, https://github.com/bdperkin/gamesheet-sdk-py#readme
Project-URL: Homepage, https://github.com/bdperkin/gamesheet-sdk-py
Project-URL: Issues, https://github.com/bdperkin/gamesheet-sdk-py/issues
Project-URL: Repository, https://github.com/bdperkin/gamesheet-sdk-py
Author-email: bdperkin <bdperkin@gmail.com>
License: MIT License
        
        Copyright (c) 2026 bdperkin
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: automation,cli,gamesheet,hockey,scraper,sdk,sports
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: click
Requires-Dist: colorlog
Requires-Dist: playwright
Requires-Dist: pydantic
Requires-Dist: pydantic-settings
Requires-Dist: pyyaml
Requires-Dist: requests
Requires-Dist: rich
Requires-Dist: rich-click
Requires-Dist: tabulate
Requires-Dist: urllib3
Provides-Extra: absolufy-imports
Requires-Dist: absolufy-imports; extra == 'absolufy-imports'
Provides-Extra: add-trailing-comma
Requires-Dist: add-trailing-comma; extra == 'add-trailing-comma'
Provides-Extra: all
Requires-Dist: absolufy-imports; extra == 'all'
Requires-Dist: add-trailing-comma; extra == 'all'
Requires-Dist: autoflake; extra == 'all'
Requires-Dist: autopep8; extra == 'all'
Requires-Dist: bandit; extra == 'all'
Requires-Dist: black; extra == 'all'
Requires-Dist: blacken-docs; extra == 'all'
Requires-Dist: blocklint; extra == 'all'
Requires-Dist: build; extra == 'all'
Requires-Dist: codespell; extra == 'all'
Requires-Dist: deptry; extra == 'all'
Requires-Dist: docconvert; extra == 'all'
Requires-Dist: docformatter; extra == 'all'
Requires-Dist: editorconfig-checker; extra == 'all'
Requires-Dist: eradicate; extra == 'all'
Requires-Dist: fastapi; extra == 'all'
Requires-Dist: flake8-2020; extra == 'all'
Requires-Dist: flake8-annotations; extra == 'all'
Requires-Dist: flake8-async; extra == 'all'
Requires-Dist: flake8-bandit; extra == 'all'
Requires-Dist: flake8-blind-except; extra == 'all'
Requires-Dist: flake8-boolean-trap; extra == 'all'
Requires-Dist: flake8-bugbear; extra == 'all'
Requires-Dist: flake8-builtins; extra == 'all'
Requires-Dist: flake8-commas; extra == 'all'
Requires-Dist: flake8-comprehensions; extra == 'all'
Requires-Dist: flake8-copyright; extra == 'all'
Requires-Dist: flake8-debugger; extra == 'all'
Requires-Dist: flake8-django; extra == 'all'
Requires-Dist: flake8-errmsg; extra == 'all'
Requires-Dist: flake8-executable; extra == 'all'
Requires-Dist: flake8-fixme; extra == 'all'
Requires-Dist: flake8-future-annotations; extra == 'all'
Requires-Dist: flake8-implicit-str-concat; extra == 'all'
Requires-Dist: flake8-import-conventions; extra == 'all'
Requires-Dist: flake8-logging; extra == 'all'
Requires-Dist: flake8-no-pep420; extra == 'all'
Requires-Dist: flake8-print; extra == 'all'
Requires-Dist: flake8-pyi; extra == 'all'
Requires-Dist: flake8-pyproject; extra == 'all'
Requires-Dist: flake8-pytest-style; extra == 'all'
Requires-Dist: flake8-quotes; extra == 'all'
Requires-Dist: flake8-raise; extra == 'all'
Requires-Dist: flake8-self; extra == 'all'
Requires-Dist: flake8-simplify; extra == 'all'
Requires-Dist: flake8-tidy-imports; extra == 'all'
Requires-Dist: flake8-todos; extra == 'all'
Requires-Dist: flake8-type-checking; extra == 'all'
Requires-Dist: flake8-unused-arguments; extra == 'all'
Requires-Dist: flynt; extra == 'all'
Requires-Dist: furo; extra == 'all'
Requires-Dist: interrogate; extra == 'all'
Requires-Dist: isort; extra == 'all'
Requires-Dist: mccabe; extra == 'all'
Requires-Dist: mdformat-gfm; extra == 'all'
Requires-Dist: mdformat-pyproject; extra == 'all'
Requires-Dist: mypy; extra == 'all'
Requires-Dist: myst-parser[linkify]; extra == 'all'
Requires-Dist: pep8-naming; extra == 'all'
Requires-Dist: pre-commit; extra == 'all'
Requires-Dist: pycodestyle; extra == 'all'
Requires-Dist: pydantic; extra == 'all'
Requires-Dist: pydoclint; extra == 'all'
Requires-Dist: pydocstyle; extra == 'all'
Requires-Dist: pyflakes; extra == 'all'
Requires-Dist: pylint; extra == 'all'
Requires-Dist: pylint-per-file-ignores; extra == 'all'
Requires-Dist: pymarkdownlnt; extra == 'all'
Requires-Dist: pyproject-fmt; extra == 'all'
Requires-Dist: pyrefly; extra == 'all'
Requires-Dist: pyright; extra == 'all'
Requires-Dist: pyroma; extra == 'all'
Requires-Dist: pytest; extra == 'all'
Requires-Dist: pytest-cov; extra == 'all'
Requires-Dist: pytest-playwright; extra == 'all'
Requires-Dist: pytest-recording; extra == 'all'
Requires-Dist: python-semantic-release; extra == 'all'
Requires-Dist: pyupgrade; extra == 'all'
Requires-Dist: radon; extra == 'all'
Requires-Dist: refurb; extra == 'all'
Requires-Dist: responses; extra == 'all'
Requires-Dist: sphinx; extra == 'all'
Requires-Dist: sphinx-autobuild; extra == 'all'
Requires-Dist: sphinx-click; extra == 'all'
Requires-Dist: sphinx-copybutton; extra == 'all'
Requires-Dist: sphinx-design; extra == 'all'
Requires-Dist: sphinx-lint; extra == 'all'
Requires-Dist: ssort; extra == 'all'
Requires-Dist: tox-ini-fmt; extra == 'all'
Requires-Dist: tox-workdir; extra == 'all'
Requires-Dist: tryceratops; extra == 'all'
Requires-Dist: types-certifi; extra == 'all'
Requires-Dist: types-click; extra == 'all'
Requires-Dist: types-docutils; extra == 'all'
Requires-Dist: types-greenlet; extra == 'all'
Requires-Dist: types-pygments; extra == 'all'
Requires-Dist: types-pyyaml; extra == 'all'
Requires-Dist: types-requests; extra == 'all'
Requires-Dist: types-tabulate; extra == 'all'
Requires-Dist: types-urllib3; extra == 'all'
Requires-Dist: unimport; extra == 'all'
Requires-Dist: validate-pyproject; extra == 'all'
Requires-Dist: virtualenv; extra == 'all'
Requires-Dist: vulture; extra == 'all'
Requires-Dist: yamllint; extra == 'all'
Provides-Extra: autoflake
Requires-Dist: autoflake; extra == 'autoflake'
Provides-Extra: autopep8
Requires-Dist: autopep8; extra == 'autopep8'
Provides-Extra: bandit
Requires-Dist: bandit; extra == 'bandit'
Provides-Extra: black
Requires-Dist: black; extra == 'black'
Provides-Extra: blacken-docs
Requires-Dist: blacken-docs; extra == 'blacken-docs'
Provides-Extra: blocklint
Requires-Dist: blocklint; extra == 'blocklint'
Provides-Extra: codespell
Requires-Dist: codespell; extra == 'codespell'
Provides-Extra: deptry
Requires-Dist: deptry; extra == 'deptry'
Requires-Dist: pytest; extra == 'deptry'
Requires-Dist: pytest-cov; extra == 'deptry'
Requires-Dist: pytest-playwright; extra == 'deptry'
Requires-Dist: pytest-recording; extra == 'deptry'
Requires-Dist: responses; extra == 'deptry'
Requires-Dist: types-certifi; extra == 'deptry'
Requires-Dist: types-click; extra == 'deptry'
Requires-Dist: types-docutils; extra == 'deptry'
Requires-Dist: types-greenlet; extra == 'deptry'
Requires-Dist: types-pygments; extra == 'deptry'
Requires-Dist: types-pyyaml; extra == 'deptry'
Requires-Dist: types-requests; extra == 'deptry'
Requires-Dist: types-tabulate; extra == 'deptry'
Requires-Dist: types-urllib3; extra == 'deptry'
Provides-Extra: dev
Requires-Dist: pre-commit; extra == 'dev'
Requires-Dist: tox-workdir; extra == 'dev'
Provides-Extra: docconvert
Requires-Dist: docconvert; extra == 'docconvert'
Provides-Extra: docformatter
Requires-Dist: docformatter; extra == 'docformatter'
Provides-Extra: docs
Requires-Dist: furo; extra == 'docs'
Requires-Dist: myst-parser[linkify]; extra == 'docs'
Requires-Dist: sphinx; extra == 'docs'
Requires-Dist: sphinx-autobuild; extra == 'docs'
Requires-Dist: sphinx-click; extra == 'docs'
Requires-Dist: sphinx-copybutton; extra == 'docs'
Requires-Dist: sphinx-design; extra == 'docs'
Requires-Dist: sphinx-lint; extra == 'docs'
Provides-Extra: editorconfig-checker
Requires-Dist: editorconfig-checker; extra == 'editorconfig-checker'
Provides-Extra: flake8
Requires-Dist: eradicate; extra == 'flake8'
Requires-Dist: fastapi; extra == 'flake8'
Requires-Dist: flake8-2020; extra == 'flake8'
Requires-Dist: flake8-annotations; extra == 'flake8'
Requires-Dist: flake8-async; extra == 'flake8'
Requires-Dist: flake8-bandit; extra == 'flake8'
Requires-Dist: flake8-blind-except; extra == 'flake8'
Requires-Dist: flake8-boolean-trap; extra == 'flake8'
Requires-Dist: flake8-bugbear; extra == 'flake8'
Requires-Dist: flake8-builtins; extra == 'flake8'
Requires-Dist: flake8-commas; extra == 'flake8'
Requires-Dist: flake8-comprehensions; extra == 'flake8'
Requires-Dist: flake8-copyright; extra == 'flake8'
Requires-Dist: flake8-debugger; extra == 'flake8'
Requires-Dist: flake8-django; extra == 'flake8'
Requires-Dist: flake8-errmsg; extra == 'flake8'
Requires-Dist: flake8-executable; extra == 'flake8'
Requires-Dist: flake8-fixme; extra == 'flake8'
Requires-Dist: flake8-future-annotations; extra == 'flake8'
Requires-Dist: flake8-implicit-str-concat; extra == 'flake8'
Requires-Dist: flake8-import-conventions; extra == 'flake8'
Requires-Dist: flake8-logging; extra == 'flake8'
Requires-Dist: flake8-no-pep420; extra == 'flake8'
Requires-Dist: flake8-print; extra == 'flake8'
Requires-Dist: flake8-pyi; extra == 'flake8'
Requires-Dist: flake8-pyproject; extra == 'flake8'
Requires-Dist: flake8-pytest-style; extra == 'flake8'
Requires-Dist: flake8-quotes; extra == 'flake8'
Requires-Dist: flake8-raise; extra == 'flake8'
Requires-Dist: flake8-self; extra == 'flake8'
Requires-Dist: flake8-simplify; extra == 'flake8'
Requires-Dist: flake8-tidy-imports; extra == 'flake8'
Requires-Dist: flake8-todos; extra == 'flake8'
Requires-Dist: flake8-type-checking; extra == 'flake8'
Requires-Dist: flake8-unused-arguments; extra == 'flake8'
Requires-Dist: flynt; extra == 'flake8'
Requires-Dist: isort; extra == 'flake8'
Requires-Dist: mccabe; extra == 'flake8'
Requires-Dist: pep8-naming; extra == 'flake8'
Requires-Dist: pycodestyle; extra == 'flake8'
Requires-Dist: pydoclint; extra == 'flake8'
Requires-Dist: pydocstyle; extra == 'flake8'
Requires-Dist: pyflakes; extra == 'flake8'
Requires-Dist: pylint; extra == 'flake8'
Requires-Dist: pylint-per-file-ignores; extra == 'flake8'
Requires-Dist: pytest; extra == 'flake8'
Requires-Dist: pytest-cov; extra == 'flake8'
Requires-Dist: pytest-playwright; extra == 'flake8'
Requires-Dist: pytest-recording; extra == 'flake8'
Requires-Dist: pyupgrade; extra == 'flake8'
Requires-Dist: refurb; extra == 'flake8'
Requires-Dist: responses; extra == 'flake8'
Requires-Dist: tryceratops; extra == 'flake8'
Provides-Extra: flake8-plugins
Requires-Dist: eradicate; extra == 'flake8-plugins'
Requires-Dist: fastapi; extra == 'flake8-plugins'
Requires-Dist: flake8-2020; extra == 'flake8-plugins'
Requires-Dist: flake8-annotations; extra == 'flake8-plugins'
Requires-Dist: flake8-async; extra == 'flake8-plugins'
Requires-Dist: flake8-bandit; extra == 'flake8-plugins'
Requires-Dist: flake8-blind-except; extra == 'flake8-plugins'
Requires-Dist: flake8-boolean-trap; extra == 'flake8-plugins'
Requires-Dist: flake8-bugbear; extra == 'flake8-plugins'
Requires-Dist: flake8-builtins; extra == 'flake8-plugins'
Requires-Dist: flake8-commas; extra == 'flake8-plugins'
Requires-Dist: flake8-comprehensions; extra == 'flake8-plugins'
Requires-Dist: flake8-copyright; extra == 'flake8-plugins'
Requires-Dist: flake8-debugger; extra == 'flake8-plugins'
Requires-Dist: flake8-django; extra == 'flake8-plugins'
Requires-Dist: flake8-errmsg; extra == 'flake8-plugins'
Requires-Dist: flake8-executable; extra == 'flake8-plugins'
Requires-Dist: flake8-fixme; extra == 'flake8-plugins'
Requires-Dist: flake8-future-annotations; extra == 'flake8-plugins'
Requires-Dist: flake8-implicit-str-concat; extra == 'flake8-plugins'
Requires-Dist: flake8-import-conventions; extra == 'flake8-plugins'
Requires-Dist: flake8-logging; extra == 'flake8-plugins'
Requires-Dist: flake8-no-pep420; extra == 'flake8-plugins'
Requires-Dist: flake8-print; extra == 'flake8-plugins'
Requires-Dist: flake8-pyi; extra == 'flake8-plugins'
Requires-Dist: flake8-pytest-style; extra == 'flake8-plugins'
Requires-Dist: flake8-quotes; extra == 'flake8-plugins'
Requires-Dist: flake8-raise; extra == 'flake8-plugins'
Requires-Dist: flake8-self; extra == 'flake8-plugins'
Requires-Dist: flake8-simplify; extra == 'flake8-plugins'
Requires-Dist: flake8-tidy-imports; extra == 'flake8-plugins'
Requires-Dist: flake8-todos; extra == 'flake8-plugins'
Requires-Dist: flake8-type-checking; extra == 'flake8-plugins'
Requires-Dist: flake8-unused-arguments; extra == 'flake8-plugins'
Requires-Dist: flynt; extra == 'flake8-plugins'
Requires-Dist: isort; extra == 'flake8-plugins'
Requires-Dist: mccabe; extra == 'flake8-plugins'
Requires-Dist: pep8-naming; extra == 'flake8-plugins'
Requires-Dist: pycodestyle; extra == 'flake8-plugins'
Requires-Dist: pydoclint; extra == 'flake8-plugins'
Requires-Dist: pydocstyle; extra == 'flake8-plugins'
Requires-Dist: pyflakes; extra == 'flake8-plugins'
Requires-Dist: pylint; extra == 'flake8-plugins'
Requires-Dist: pyupgrade; extra == 'flake8-plugins'
Requires-Dist: refurb; extra == 'flake8-plugins'
Requires-Dist: tryceratops; extra == 'flake8-plugins'
Provides-Extra: interrogate
Requires-Dist: interrogate; extra == 'interrogate'
Provides-Extra: isort
Requires-Dist: isort; extra == 'isort'
Provides-Extra: mdformat
Requires-Dist: mdformat-gfm; extra == 'mdformat'
Requires-Dist: mdformat-pyproject; extra == 'mdformat'
Provides-Extra: mypy
Requires-Dist: mypy; extra == 'mypy'
Requires-Dist: pytest; extra == 'mypy'
Requires-Dist: pytest-cov; extra == 'mypy'
Requires-Dist: pytest-playwright; extra == 'mypy'
Requires-Dist: pytest-recording; extra == 'mypy'
Requires-Dist: responses; extra == 'mypy'
Requires-Dist: types-certifi; extra == 'mypy'
Requires-Dist: types-click; extra == 'mypy'
Requires-Dist: types-docutils; extra == 'mypy'
Requires-Dist: types-greenlet; extra == 'mypy'
Requires-Dist: types-pygments; extra == 'mypy'
Requires-Dist: types-pyyaml; extra == 'mypy'
Requires-Dist: types-requests; extra == 'mypy'
Requires-Dist: types-tabulate; extra == 'mypy'
Requires-Dist: types-urllib3; extra == 'mypy'
Provides-Extra: psr
Requires-Dist: build; extra == 'psr'
Requires-Dist: pre-commit; extra == 'psr'
Requires-Dist: python-semantic-release; extra == 'psr'
Provides-Extra: pydocstyle
Requires-Dist: pydocstyle; extra == 'pydocstyle'
Provides-Extra: pylint
Requires-Dist: pylint-per-file-ignores; extra == 'pylint'
Requires-Dist: pytest; extra == 'pylint'
Requires-Dist: pytest-cov; extra == 'pylint'
Requires-Dist: pytest-playwright; extra == 'pylint'
Requires-Dist: pytest-recording; extra == 'pylint'
Requires-Dist: responses; extra == 'pylint'
Provides-Extra: pymarkdown
Requires-Dist: pymarkdownlnt; extra == 'pymarkdown'
Provides-Extra: pyproject-fmt
Requires-Dist: pyproject-fmt; extra == 'pyproject-fmt'
Provides-Extra: pyrefly
Requires-Dist: pyrefly; extra == 'pyrefly'
Requires-Dist: pytest; extra == 'pyrefly'
Requires-Dist: pytest-cov; extra == 'pyrefly'
Requires-Dist: pytest-playwright; extra == 'pyrefly'
Requires-Dist: pytest-recording; extra == 'pyrefly'
Requires-Dist: responses; extra == 'pyrefly'
Provides-Extra: pyright
Requires-Dist: pyright; extra == 'pyright'
Requires-Dist: pytest; extra == 'pyright'
Requires-Dist: pytest-cov; extra == 'pyright'
Requires-Dist: pytest-playwright; extra == 'pyright'
Requires-Dist: pytest-recording; extra == 'pyright'
Requires-Dist: responses; extra == 'pyright'
Provides-Extra: pyroma
Requires-Dist: pyroma; extra == 'pyroma'
Requires-Dist: virtualenv; extra == 'pyroma'
Provides-Extra: pytest
Requires-Dist: pytest; extra == 'pytest'
Requires-Dist: pytest-cov; extra == 'pytest'
Requires-Dist: pytest-playwright; extra == 'pytest'
Requires-Dist: pytest-recording; extra == 'pytest'
Requires-Dist: responses; extra == 'pytest'
Provides-Extra: pyupgrade
Requires-Dist: pyupgrade; extra == 'pyupgrade'
Provides-Extra: radon
Requires-Dist: radon; extra == 'radon'
Provides-Extra: refurb
Requires-Dist: pydantic; extra == 'refurb'
Requires-Dist: refurb; extra == 'refurb'
Provides-Extra: sphinx-lint
Requires-Dist: sphinx-lint; extra == 'sphinx-lint'
Provides-Extra: ssort
Requires-Dist: ssort; extra == 'ssort'
Provides-Extra: tox-ini-fmt
Requires-Dist: tox-ini-fmt; extra == 'tox-ini-fmt'
Provides-Extra: type-stubs
Requires-Dist: types-certifi; extra == 'type-stubs'
Requires-Dist: types-click; extra == 'type-stubs'
Requires-Dist: types-docutils; extra == 'type-stubs'
Requires-Dist: types-greenlet; extra == 'type-stubs'
Requires-Dist: types-pygments; extra == 'type-stubs'
Requires-Dist: types-pyyaml; extra == 'type-stubs'
Requires-Dist: types-requests; extra == 'type-stubs'
Requires-Dist: types-tabulate; extra == 'type-stubs'
Requires-Dist: types-urllib3; extra == 'type-stubs'
Provides-Extra: unimport
Requires-Dist: unimport; extra == 'unimport'
Provides-Extra: validate-pyproject
Requires-Dist: validate-pyproject; extra == 'validate-pyproject'
Provides-Extra: vulture
Requires-Dist: vulture; extra == 'vulture'
Provides-Extra: yamllint
Requires-Dist: yamllint; extra == 'yamllint'
Description-Content-Type: text/markdown

# gamesheet-sdk-py

> **Unofficial** Python SDK and command-line interface for the [GameSheet Inc.](https://gamesheetinc.com) platform.

<!-- Build & Quality -->

[![CI](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/ci.yml)
[![Tests](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/tests.yml)
[![CodeQL](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/codeql.yml)
[![Docs](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/docs.yml/badge.svg?branch=main)](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/docs.yml)
[![Dependency review](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/dependency-review.yml/badge.svg)](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/dependency-review.yml)
[![pre-commit](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/pre-commit.yml/badge.svg?branch=main)](https://github.com/bdperkin/gamesheet-sdk-py/actions/workflows/pre-commit.yml)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/bdperkin/gamesheet-sdk-py/main.svg)](https://results.pre-commit.ci/latest/github/bdperkin/gamesheet-sdk-py/main)
[![codecov](https://codecov.io/gh/bdperkin/gamesheet-sdk-py/graph/badge.svg?token=8608BKui41)](https://codecov.io/gh/bdperkin/gamesheet-sdk-py)

<!-- Code Quality -->

[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Imports: isort](https://img.shields.io/badge/imports-isort-1674b1.svg?labelColor=ef8336)](https://pycqa.github.io/isort/)
[![Linting: flake8](https://img.shields.io/badge/linting-flake8-yellow.svg)](https://flake8.pycqa.org/)
[![Linting: pylint](https://img.shields.io/badge/linting-pylint-yellowgreen.svg)](https://pylint.readthedocs.io/)
[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
[![Security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)

<!-- Package Info -->

[![PyPI version](https://img.shields.io/pypi/v/gamesheet-sdk-py.svg)](https://pypi.org/project/gamesheet-sdk-py/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/gamesheet-sdk-py.svg)](https://pypi.org/project/gamesheet-sdk-py/)
[![PyPI - Wheel](https://img.shields.io/pypi/wheel/gamesheet-sdk-py.svg)](https://pypi.org/project/gamesheet-sdk-py/)
[![PyPI - Status](https://img.shields.io/pypi/status/gamesheet-sdk-py.svg)](https://pypi.org/project/gamesheet-sdk-py/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/gamesheet-sdk-py.svg)](https://pypi.org/project/gamesheet-sdk-py/)
[![License: MIT](https://img.shields.io/github/license/bdperkin/gamesheet-sdk-py.svg)](LICENSE)
[![Typed](https://img.shields.io/pypi/types/gamesheet-sdk-py.svg)](https://peps.python.org/pep-0561/)
[![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch)

<!-- Community & Activity -->

[![GitHub release](https://img.shields.io/github/v/release/bdperkin/gamesheet-sdk-py?include_prereleases&sort=semver)](https://github.com/bdperkin/gamesheet-sdk-py/releases)
[![GitHub stars](https://img.shields.io/github/stars/bdperkin/gamesheet-sdk-py.svg?style=social)](https://github.com/bdperkin/gamesheet-sdk-py/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/bdperkin/gamesheet-sdk-py.svg?style=social)](https://github.com/bdperkin/gamesheet-sdk-py/network/members)
[![GitHub issues](https://img.shields.io/github/issues/bdperkin/gamesheet-sdk-py.svg)](https://github.com/bdperkin/gamesheet-sdk-py/issues)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/bdperkin/gamesheet-sdk-py.svg)](https://github.com/bdperkin/gamesheet-sdk-py/pulls)
[![GitHub contributors](https://img.shields.io/github/contributors/bdperkin/gamesheet-sdk-py.svg)](https://github.com/bdperkin/gamesheet-sdk-py/graphs/contributors)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/bdperkin/gamesheet-sdk-py.svg)](https://github.com/bdperkin/gamesheet-sdk-py/commits/main)

<!-- Maintenance -->

[![GitHub last commit](https://img.shields.io/github/last-commit/bdperkin/gamesheet-sdk-py.svg)](https://github.com/bdperkin/gamesheet-sdk-py/commits/main)
[![Maintenance](https://img.shields.io/maintenance/yes/2026.svg)](https://github.com/bdperkin/gamesheet-sdk-py/graphs/commit-activity)
[![Dependabot](https://img.shields.io/badge/dependabot-enabled-025E8C?logo=dependabot)](https://github.com/bdperkin/gamesheet-sdk-py/network/updates)
[![GitHub repo size](https://img.shields.io/github/repo-size/bdperkin/gamesheet-sdk-py.svg)](https://github.com/bdperkin/gamesheet-sdk-py)
[![GitHub code size](https://img.shields.io/github/languages/code-size/bdperkin/gamesheet-sdk-py.svg)](https://github.com/bdperkin/gamesheet-sdk-py)

______________________________________________________________________

## ⚠️ Disclaimer

This project is **not affiliated with, endorsed by, or sponsored by GameSheet Inc.** GameSheet Inc. does not publish a public REST/GraphQL API for the
operations this SDK covers. Where a native API is absent, this library **automates the GameSheet WebUI** (using HTTP requests, HTML parsing, and
headless-browser automation).

Because this approach depends on third-party UI structure, **it may break without warning** when GameSheet ships changes. Check the
[GitHub Releases](https://github.com/bdperkin/gamesheet-sdk-py/releases) page before upgrading in production.

Use of this software must comply with the GameSheet Inc. Terms of Service. You are responsible for any automation you perform.

______________________________________________________________________

## Quick Links

- **[Documentation](https://bdperkin.github.io/gamesheet-sdk-py/)** — Full documentation (tutorials, how-tos, API reference)
- **[Installation](#installation)** — Get started quickly
- **[Quick Start](#quick-start)** — First commands to try
- **[CLI Reference](docs/reference/cli.md)** — Command-line usage
- **[Configuration](docs/reference/configuration.md)** — Environment variables and settings
- **[Development Setup](docs/how-to/development-setup.md)** — Contributing guide
- **[Release Process](docs/how-to/release-process.md)** — Automated releases with Conventional Commits
- **[CHANGELOG](CHANGELOG.md)** — Release history

______________________________________________________________________

## Features

- **Authentication** — Browser-driven login flow with persistent session storage
- **Resource-oriented CLI** — `associations`, `leagues`, `seasons`, `ipad-keys` commands with intuitive aliases
- **Python API** — `list_associations()`, `list_leagues()`, `list_seasons()`, `get_season()`, `list_ipad_keys()`
- **Multiple output formats** — JSON, YAML, CSV, TSV, or 13 tabulate table formats
- **Shell completion** — Tab completion for bash, zsh, fish
- **Typed (PEP 561)** — Ships `py.typed` marker, passes `mypy --strict`
- **Automated releases** — [Conventional Commits](https://www.conventionalcommits.org/) +
  [python-semantic-release](https://python-semantic-release.readthedocs.io/)

______________________________________________________________________

## Requirements

- **Python 3.11+** (3.11, 3.12, 3.13, or 3.14)
- **Chromium** (managed by Playwright) — required for login flow

______________________________________________________________________

## Installation

### Via PyPI

```bash
pip install gamesheet-sdk-py

# Install Playwright browser (required for login)
python -m playwright install chromium
```

### Via Docker

```bash
# Pull the latest image from GitHub Container Registry
docker pull ghcr.io/bdperkin/gamesheet-sdk-py:latest

# Run the CLI
docker run --rm ghcr.io/bdperkin/gamesheet-sdk-py:latest --help

# Run with persistent session storage
docker run --rm -v ~/.gamesheet:/home/gamesheet/.gamesheet ghcr.io/bdperkin/gamesheet-sdk-py:latest associations list
```

Available Docker tags:

- `latest` — most recent release from main branch
- `<version>` — specific version (e.g., `0.1.8`, `0.1`, `0`)
- `<branch>-<sha>` — specific commit for traceability

### From Source

```bash
git clone https://github.com/bdperkin/gamesheet-sdk-py.git
cd gamesheet-sdk-py
pip install -e ".[all]"
python -m playwright install chromium

# Or build the Docker image locally
make docker-build
make docker-run
```

See [Development Setup](docs/how-to/development-setup.md) for detailed instructions.

______________________________________________________________________

## Quick Start

### CLI

```bash
# Authenticate (credentials can also come from env vars)
gamesheet-sdk-py login --email you@example.com

# List associations
gamesheet-sdk-py associations list --format json

# List leagues in an association
gamesheet-sdk-py leagues list 38 --format json

# List seasons in a league
gamesheet-sdk-py seasons list 1148580 --format json

# Get season details
gamesheet-sdk-py seasons get --season-id 15020 --format json

# Get iPad/Scoring keys
gamesheet-sdk-py ipad-keys get 15020 --format json
```

See the [CLI Reference](docs/reference/cli.md) for complete usage.

### Python API

```python
from gamesheet_sdk import (
    AuthenticatedSession,
    Config,
    get_season,
    list_associations,
    list_ipad_keys,
    list_leagues,
    list_seasons,
    load_access_token,
    load_refresh_token,
    save_tokens,
)

config = Config()
access = load_access_token(config)
refresh = load_refresh_token(config)

with AuthenticatedSession(
    config,
    access_token=access or "",
    refresh_token=refresh or "",
    on_refresh=lambda tokens: save_tokens(config, **tokens),
) as session:
    # List all associations
    for assoc in list_associations(session):
        print(f"Association: {assoc.title}")

        # List leagues
        for league in list_leagues(session, assoc.id):
            print(f"  League: {league.title}")

            # List seasons
            for season in list_seasons(session, league.id):
                print(f"    Season: {season.title}")

                # Get detailed info
                detail = get_season(session, season.id)
                print(f"      Sport: {detail.sport}")

                # Get iPad keys
                keys = list_ipad_keys(session, season.id)
                for key in keys:
                    print(f"        Key: {key.value}")
```

______________________________________________________________________

## Configuration

Configuration via environment variables or CLI flags. See [Configuration Reference](docs/reference/configuration.md) for details.

```bash
export GAMESHEET_USERNAME=you@example.com
export GAMESHEET_PASSWORD=secret
export GAMESHEET_TIMEOUT=60

gamesheet-sdk-py login
```

______________________________________________________________________

## Documentation

Full documentation is available at **<https://bdperkin.github.io/gamesheet-sdk-py/>**

The docs follow the [Diátaxis](https://diataxis.fr/) framework:

- **[Tutorials](docs/tutorials/)** — Step-by-step learning guides
- **[How-To Guides](docs/how-to/)** — Task-oriented recipes
- **[Reference](docs/reference/)** — API and CLI documentation
- **[Explanation](docs/explanation/)** — Understanding the architecture

______________________________________________________________________

## Project Status

**Status:** Alpha — Active development, breaking changes possible before 1.0.0

- **Version strategy:** Patch-only bumps until 1.0.0 (see [Release Process](docs/how-to/release-process.md))
- **Test coverage:** 100% (enforced via Codecov)
- **Type checking:** `mypy --strict` passes
- **Complexity:** All blocks at cyclomatic complexity grade A (cc ≤ 5)

______________________________________________________________________

## Contributing

Contributions are welcome! Before opening a PR:

1. Read [Development Setup](docs/how-to/development-setup.md)
2. Follow [Conventional Commits](https://www.conventionalcommits.org/) format
3. Ensure tests pass: `pytest --cov`
4. Run quality gates: `pre-commit run --all-files`
5. Maintain 100% test coverage
6. Keep complexity at grade A (run `make metrics`)

See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.

______________________________________________________________________

## Security

To report a vulnerability, see [SECURITY.md](SECURITY.md). Please use the private reporting channel — do not open public issues for security reports.

______________________________________________________________________

## License

Distributed under the [MIT License](LICENSE). © 2026 bdperkin.

______________________________________________________________________

## Links

- **PyPI:** <https://pypi.org/project/gamesheet-sdk-py/>
- **Documentation:** <https://bdperkin.github.io/gamesheet-sdk-py/>
- **Source:** <https://github.com/bdperkin/gamesheet-sdk-py>
- **Issues:** <https://github.com/bdperkin/gamesheet-sdk-py/issues>
- **Changelog:** [CHANGELOG.md](CHANGELOG.md)
- **Releases:** <https://github.com/bdperkin/gamesheet-sdk-py/releases>
