Architecture Overview¶
This document describes the high-level architecture of gamesheet-sdk-py, explaining how the major components fit together and the design decisions behind
them.
System Design¶
The SDK is organized into three pillars:
┌─────────────────────────────────────────────────────────────────────────────┐
│ gamesheet_sdk (top-level) │
│ Re-exports for backward compatibility │
└─────────────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ common/ │ │ admin/ │ │ teams/ │
│ Shared infra │ │ Admin dashboard │ │ Teams dashboard │
│ │ │ │ │ │
│ • auth/ │ │ • associations.py │ │ • cli/ (stub) │
│ • cli/core │ │ • divisions.py │ │ - login (NYI) │
│ • browser.py │ │ • leagues.py │ │ - completion │
│ • config.py │ │ • seasons.py │ │ │
│ • session.py │ │ • teams.py │ │ (domain modules │
│ • output.py │ │ • referees.py │ │ forthcoming) │
│ • errors.py │ │ • ipad_keys.py │ │ │
│ • exceptions.py │ │ • games/ │ │ │
│ • constants.py │ │ • roster/ │ │ │
│ • shared/ │ │ • cli/ (full) │ │ │
└─────────────────┘ └─────────────────────┘ └─────────────────────┘
Each pillar has a clear responsibility:
common/— Infrastructure shared by both dashboards: authentication, HTTP sessions, browser automation, configuration, output formatting, error messages.admin/— Domain modules and CLI for the admin dashboard (gamesheet-admin). Contains all resource models and action functions.teams/— CLI and domain modules for the teams dashboard (gamesheet-teams). Currently a stub with login not yet implemented.
Dependencies flow inward: admin/ and teams/ depend on common/, but never on each other.
Component Relationships¶
Core Components¶
1. Configuration (common/config.py)¶
Central configuration object that resolves settings from multiple sources:
Config(
# Sources in priority order:
# 1. Constructor arguments (highest priority)
# 2. Environment variables (GAMESHEET_*)
# 3. Default values (lowest priority)
)
Responsibilities:
Environment variable resolution
Default value management
Path configuration (tokens, sessions, browser state)
Timeout and retry settings
2. Session Layer¶
Base Session (common/session.py):
Wraps
requests.Sessionwith GameSheet-specific defaultsAutomatic retries on transient failures
Cookie persistence across process invocations
User-Agent header management
Authenticated Session (common/auth/session.py):
Extends base session with token auto-refresh
Intercepts HTTP 401/403 responses
Refreshes access token using refresh token
Invokes callback to persist new tokens
Browser Session (common/browser.py):
Playwright-based headless Chromium automation
Used when HTTP requests are insufficient (JavaScript rendering, anti-bot measures)
Lazy initialization (only starts browser when needed)
Storage state persistence (cookies + localStorage)
3. Authentication (common/auth/)¶
Login Flow (common/auth/login.py):
Opens GameSheet login page in headless browser
Captures Firebase Authentication response
Extracts
idToken(access token) andrefreshTokenSaves tokens to
~/.gamesheet/access_tokenand~/.gamesheet/refresh_token
Token Management (common/auth/tokens.py):
load_access_token()— Read from diskload_refresh_token()— Read from disksave_tokens()— Write both to diskrefresh_access_token()— Exchange refresh token for new access token
Storage (common/auth/storage.py):
File I/O for token files
Directory creation with safe permissions
Path resolution
4. Domain Modules (admin/)¶
Each domain module follows a consistent pattern:
# Example: src/gamesheet_sdk/admin/associations.py
# 1. Pydantic model for data validation
class Association(BaseModel):
id: str
title: str
# ... other fields
# 2. Action functions
def list_associations(config: Config | None = None) -> list[Association]:
"""Retrieve all associations."""
# Implementation using Session or BrowserSession
Current Domain Modules (all under admin/):
associations.py— Sports associationsleagues.py— Leagues within associationsseasons.py— Seasons within leaguesdivisions.py— Divisions within seasonsteams.py— Teams within divisionsgames/— Game management (scheduled, completed, brackets)referees.py— Referee managementroster/— Player and coach roster managementipad_keys.py— iPad/Scoring access keys
5. CLI Layer¶
The CLI is split into two entry points with shared infrastructure:
Common CLI (common/cli/):
core.py—ResourceGroupclass,confirm_destructivedecorator, logging setup, exit code resolutionconstants.py— Shared constants (SHELL_TYPES)
Admin CLI (admin/cli/):
main.py— Root CLI group (gamesheet-admin) andmain()entry pointconstants.py— Admin-specific constants (format choices, column specs)helpers.py— Session building, action helperscommands/— Individual command modulesshared/— Shared decorators and rendering utilities
Teams CLI (teams/cli/):
main.py— Root CLI group (gamesheet-teams) andmain()entry pointcommands/— Login (stub) and completion commands
Resource-Oriented Design:
gamesheet-admin <resource> <verb> [options]
└─────┬────┘ └─┬──┘
Noun Verb
Examples:
gamesheet-admin associations list
gamesheet-admin teams get --team-id 123
gamesheet-admin referees create --first-name John --last-name Doe
Canonical Verbs and Aliases:
create(aliases:add,new)get(aliases:show,view)list(aliases:ls)update(aliases:set,edit)delete(aliases:rm,remove)
Data Flow¶
Typical Read Operation (GET)¶
User Command
│
▼
CLI Command (e.g., seasons list)
│
▼
Domain Function (list_seasons)
│
▼
Session.get("/api/seasons")
│
▼
Retry Logic (on 5xx errors)
│
▼
JSON Response
│
▼
Pydantic Model Validation
│
▼
List[Season] returned to CLI
│
▼
Output Formatter (JSON/YAML/table)
│
▼
User sees formatted output
Authenticated Operation with Auto-Refresh¶
User Command
│
▼
CLI Command (requires auth)
│
▼
Domain Function
│
▼
AuthenticatedSession.get(...)
│
▼
HTTP 401 Unauthorized
│
▼
Auto-refresh: refresh_access_token()
│
▼
Save new tokens via callback
│
▼
Retry original request with new token
│
▼
Success: process response
Browser-Based Operation¶
User Command (e.g., login)
│
▼
common/auth/login.py
│
▼
BrowserSession.goto("/login")
│
▼
Playwright launches Chromium
│
▼
Inject email/password, submit form
│
▼
Capture Firebase Auth response
│
▼
Extract tokens from XHR response
│
▼
Save tokens to disk
│
▼
BrowserSession saves storage state
│
▼
Browser closes
Design Decisions¶
Why Both HTTP and Browser?¶
HTTP (requests) is used for:
Most API operations (list, get, create, update, delete)
Fast, lightweight, easy to test
VCR cassettes for reproducible tests
Browser (Playwright) is used for:
Login flow (JavaScript-heavy, Firebase Auth)
Operations blocked by anti-bot measures
Workflows requiring full page rendering
Trade-off: Complexity (two session types) vs. reliability (can handle any GameSheet workflow).
Why Three Pillars?¶
The admin and teams dashboards are separate GameSheet products with different URLs, different authentication flows, and potentially different data models (admin
uses integer IDs, teams may use UUIDs). Splitting the codebase into common/, admin/, and teams/ ensures:
No cross-contamination of dashboard-specific logic
Shared infrastructure is maintained once
Each CLI can evolve independently
The
gamesheet-adminandgamesheet-teamsentry points are distinct user experiences
Why Pydantic Models?¶
Benefits:
Runtime validation of API responses
Type hints for IDE autocomplete
Automatic JSON serialization/deserialization
Self-documenting API (field descriptions in docstrings)
Trade-off: Tight coupling to API shape (breaking changes upstream require model updates).
Why Click for CLI?¶
Benefits:
Widely used, well-documented
Subcommand support (nested resource groups)
Auto-generated
--helptextShell completion support (bash/zsh/fish)
Alternative considered: argparse (stdlib, no deps), rejected because subcommand support is verbose.
Why src/ Layout?¶
Benefits:
Tests import from installed package (catches packaging issues)
Prevents accidentally importing from source instead of installed version
Clear separation between source and tests
Trade-off: Slightly more setup (need pip install -e .), but worth it for packaging correctness.
Why 100% Test Coverage?¶
Benefits:
Confidence in refactoring (know what breaks)
Forces thinking about edge cases
Documentation via tests (shows how to use the API)
Trade-off: More upfront work, but pays dividends in maintenance.
Why Complexity Gate (Grade A)?¶
Benefits:
Forces decomposition of complex logic
Easier to test (small functions)
Easier to review (less mental load)
Self-documenting (function names describe intent)
Trade-off: More functions, but each is simpler.
Extension Points¶
Adding a New Admin Domain Module¶
Create
src/gamesheet_sdk/admin/<resource>.pyDefine Pydantic model(s)
Implement action functions (
list_<resource>,get_<resource>, etc.)Create
src/gamesheet_sdk/admin/cli/commands/<resource>.pyRegister CLI group in
admin/cli/main.pyAdd tests under
tests/unit/<resource>/andtests/cli/<resource>/
Adding a New CLI Verb¶
If the standard CRUD verbs (create, get, list, update, delete) are insufficient:
Add custom command to resource group:
@<resource>_group.command("custom-verb")Update aliases map in
ResourceGroupinstantiation if neededAdd corresponding domain function
Add tests
Adding Teams Domain Modules¶
The teams pillar follows the same pattern as admin. When the teams authentication flow is implemented:
Create domain modules under
src/gamesheet_sdk/teams/Create CLI commands under
src/gamesheet_sdk/teams/cli/commands/Register commands in
teams/cli/main.py
Adding a New Output Format¶
The SDK supports 15 output formats (see src/gamesheet_sdk/common/output.py). To add a new one:
Add formatter to
render()functionUpdate
FORMAT_CHOICESinadmin/cli/constants.pyUpdate
--formatoption help textAdd integration test
Testing Strategy¶
Test Organization¶
tests/
├── unit/ # Domain module tests
│ ├── associations/
│ ├── leagues/
│ └── ...
├── cli/ # CLI command tests
│ ├── associations/
│ ├── leagues/
│ └── ...
├── admin/cli/ # Admin CLI entry point tests
├── teams/cli/ # Teams CLI entry point tests
├── integration/ # Multi-component tests
│ ├── browser/
│ ├── cli_games/
│ └── ...
└── fixtures/ # Shared test fixtures
Test Categories¶
Unit Tests:
Domain function behavior
Pydantic model validation
Utility functions
VCR cassettes for HTTP responses
CLI Tests:
Command invocation
Argument parsing
Output formatting
Error handling
Integration Tests:
Browser flows (login, multi-step workflows)
Multi-domain workflows (e.g., list leagues → list seasons)
Configuration resolution
End-to-end smoke tests
Test Markers¶
@pytest.mark.vcr— Replays HTTP from cassette@pytest.mark.browser— Requires headless Chromium (slow, opt-in)
Run fast tests: pytest -m "not browser"
Security Considerations¶
Token Storage¶
Tokens are stored in ~/.gamesheet/ with restrictive permissions (0600 on Unix). Tokens are:
Access tokens: Short-lived (hours), used for API requests
Refresh tokens: Long-lived (weeks/months), used to obtain new access tokens
Risk: If refresh token is compromised, attacker can obtain access tokens until refresh token expires.
Mitigation: Store tokens with restricted filesystem permissions, don’t log token values.
Browser State¶
Browser storage state (cookies + localStorage) is saved to ~/.gamesheet/browser-state.json. This file contains session cookies that could be used to
impersonate the user.
Risk: If browser state is compromised, attacker has same access as logged-in user.
Mitigation: Restrict file permissions, clear on logout.
Secrets in Logs¶
The SDK uses logging with care to avoid logging sensitive values:
Tokens are never logged
Passwords are never logged
Email addresses are logged only at INFO level (not in production)
Guideline: Use _LOGGER.debug() for details, _LOGGER.info() for user-visible messages.
Performance Characteristics¶
HTTP Operations¶
Latency: ~100–500ms per request (network-bound)
Retries: 3 retries on transient failures (configurable via
Config.request_retries)Timeout: 30s default (configurable via
Config.timeout)
Browser Operations¶
Startup: ~500ms to launch Chromium (one-time per session)
Page load: ~1–3s for JavaScript-heavy pages
Recommendation: Minimize browser usage (use HTTP when possible)
Token Refresh¶
Frequency: Only on HTTP 401 (not proactive)
Latency: ~200–500ms (one HTTP request)
Caching: New access token is saved immediately (subsequent requests use it)
Future Directions¶
Planned Features¶
Teams dashboard support: Full domain modules and CLI commands for the teams dashboard
Auth strategy pattern:
LoginFlowABC to support different authentication flows per dashboardID abstraction: Admin uses integers, teams may use UUIDs
Async support:
async/awaitvariants of domain functions for concurrent operationsCaching layer: In-memory cache for frequently-accessed resources (leagues, associations)
API Stability¶
The SDK is alpha (0.x.y versions). Breaking changes are possible until 1.0.0. After 1.0.0:
Major version bump (1.x → 2.x): Breaking changes allowed
Minor version bump (1.0 → 1.1): New features, backward compatible
Patch version bump (1.0.0 → 1.0.1): Bug fixes only
See Release Process for versioning details.