Metadata-Version: 2.5
Name: pyvexevents
Version: 0.1.0
Summary: A CPython client for the Public VEX Events API v2 (events.vex.com)
Project-URL: Homepage, https://github.com/ksbarnt/pyvexevents
Project-URL: Repository, https://github.com/ksbarnt/pyvexevents
Author: Kenny Barnt
License-Expression: MIT
License-File: LICENSE
Keywords: api-client,robotics,vex,vex-events
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == 'dev'
Description-Content-Type: text/markdown

# pyvexevents

A CPython client for the [Public VEX Events API v2](https://events.vex.com/api/v2/swagger.yml)
(the API that backs [events.vex.com](https://events.vex.com)). Built for
regular desktop/server Python 3.9+ - scripts, notebooks, backends, CI jobs -
and installed as a normal `pip` package rather than copied onto a device.

It covers all 20 read endpoints in the spec: Events, Teams, Programs, and
Seasons, including their nested sub-resources (a division's matches and
rankings, a team's skills runs and awards, and so on).

> **Sibling projects:** [`uvexevents`](../uvexevents) (plain MicroPython,
> built on `urequests`) and [`cpvexevents`](../cpvexevents) (CircuitPython,
> takes a bring-your-own `adafruit_requests.Session`) are dependency-light
> single-file clients for microcontrollers. `pyvexevents` is the
> desktop-CPython cousin: it targets a normal `pip install`, takes (or
> owns) a real `requests.Session` for connection pooling, uses real
> `enum.Enum` constants and full type hints (PEP 561, ships `py.typed`),
> and is laid out as a small installable package instead of a single file
> you copy onto a board. The three are independent - changes to one are
> not synced to the others - but the method names, parameters, and
> response shapes are kept in sync deliberately, so switching between
> them (or reading one's docs while using another) should feel familiar.

## Requirements

- CPython 3.9+.
- [`requests`](https://pypi.org/project/requests/) (installed
  automatically as a dependency).
- A VEX Events API bearer token. Generate one from your events.vex.com
  account (Developer / API settings) - it's a JWT, passed as
  `Authorization: Bearer <token>` on every request.

## Install

```
pip install pyvexevents
```

Or install directly from this repo:

```
pip install git+https://github.com/ksbarnt/pyvexevents.git
```

Or for local development:

```
git clone https://github.com/ksbarnt/pyvexevents.git
cd pyvexevents
pip install -e ".[dev]"
```

## Quickstart

```python
from pyvexevents import VexEventsClient, EventLevel

with VexEventsClient(token="<your JWT>") as client:
    # Every get_* method returns one page as a plain dict: {"meta": {...}, "data": [...]}
    page = client.get_events(seasons=[181], levels=[EventLevel.WORLD], per_page=10)
    for event in page["data"]:
        print(event["sku"], event["name"])

    # Every iter_* method is a generator that walks all pages automatically
    # and yields individual items.
    for team in client.iter_event_teams(page["data"][0]["id"]):
        print(team["number"], team.get("team_name"))
```

`VexEventsClient` also works without the `with` block - just call
`client.close()` yourself when you're done - but the context manager is
the easiest way to make sure the underlying connection pool gets torn down.

See `examples/` for pagination, error handling, and custom-session
walkthroughs.

## Design notes

- **Owns or borrows a `requests.Session`.** By default `VexEventsClient`
  creates and owns a private `requests.Session`, so repeated calls reuse
  pooled connections; `close()` (or exiting the `with` block) tears it
  down. Pass your own `session=` - e.g. one wrapped with a
  `requests.adapters.HTTPAdapter` for retry/backoff, or a shared session
  reused across multiple clients - and `pyvexevents` will use it without
  taking ownership (`close()` then leaves it open). This mirrors
  `cpvexevents`'s bring-your-own-session pattern, translated to the
  desktop-native `requests` ecosystem.
- **`get_*` vs. `iter_*`.** Every list endpoint has both: `get_X(...)`
  fetches exactly one page (you control `page`/`per_page`, and get back
  the raw `meta`/`data` dict - handy for building a paged UI or a
  "load more" button); `iter_X(...)` is a generator that fetches pages
  lazily and yields one item at a time, walking `meta.current_page` /
  `meta.last_page` until exhausted. Prefer `iter_*` unless you
  specifically need page metadata or a specific page.
- **Filter parameters are plural where the API takes an array** (e.g.
  `seasons=[181, 182]` maps to the API's `season[]=181&season[]=182`), and
  singular where it takes a scalar (e.g. `region="CA"`, `registered=True`).
  Pass `None` (the default) to omit a filter entirely.
- **Errors** are typed exceptions (see below), not error dicts or return
  codes, so a normal `try/except` works and you can't accidentally
  ignore a failure.
- **Real `enum.Enum`/`IntEnum` constants.** Unlike the MicroPython/
  CircuitPython siblings (which fall back to plain classes of constants
  because `enum` isn't reliably available there), `pyvexevents` uses
  actual enums - `EventLevel.WORLD` is an `EventLevel` instance, not just
  a string. Passing the raw string/int value works identically; the enum
  exists for autocomplete, typo protection, and exhaustiveness checks
  under a type checker.
- **Full type hints, `py.typed` shipped.** Method signatures are fully
  typed and the package includes a `py.typed` marker (PEP 561), so mypy/
  pyright will type-check calls against it. Response bodies themselves
  stay `dict`/`list` (the API's JSON schemas aren't modeled as
  dataclasses or per-object `TypedDict`s) - only the common `{"meta":
  ..., "data": [...]}` page envelope has a `VexEventsPage` TypedDict.
- **Single module, flat namespace.** Everything - the client, exceptions,
  and constants classes - lives in `pyvexevents/__init__.py` and is
  importable straight from `pyvexevents` (e.g. `from pyvexevents import
  VexEventsClient, VexEventsNotFoundError, EventLevel`). There's no
  `pyvexevents.errors` or `pyvexevents.constants` submodule to import
  from separately - the package layout (`src/`, `pyproject.toml`) is
  what makes this a normal pip-installable project, not a multi-module
  split.

## API reference

All methods live on `VexEventsClient`. `page`/`per_page` (default 25, max
250 per the API) are accepted by every `get_*` list method; `per_page`
alone is accepted by every `iter_*` method (page is managed internally).

### Events

| Method | API operation | Notes |
|---|---|---|
| `get_events(ids, skus, teams, seasons, start, end, region, levels, my_events, event_types, page, per_page)` / `iter_events(...)` | `event_getEvents` | List/search events |
| `get_event(event_id)` | `event_getEvent` | Single event; raises `VexEventsNotFoundError` if unknown |
| `get_event_teams(event_id, numbers, registered, grades, countries, my_teams, page, per_page)` / `iter_event_teams(...)` | `event_getTeams` | Teams present at an event |
| `get_event_skills(event_id, teams, types, page, per_page)` / `iter_event_skills(...)` | `event_getSkills` | Skills runs at an event |
| `get_event_awards(event_id, teams, winners, page, per_page)` / `iter_event_awards(...)` | `event_getAwards` | Awards given at an event |
| `get_event_division_matches(event_id, division_id, teams, rounds, instances, matchnums, page, per_page)` / `iter_event_division_matches(...)` | `event_getDivisionMatches` | Matches in one division |
| `get_event_division_rankings(event_id, division_id, teams, ranks, page, per_page)` / `iter_event_division_rankings(...)` | `event_getDivisionRankings` | Qual rankings in one division |
| `get_event_division_finalist_rankings(event_id, division_id, teams, ranks, page, per_page)` / `iter_event_division_finalist_rankings(...)` | `event_getDivisionFinalistRankings` | Finalist rankings in one division |

### Teams

| Method | API operation | Notes |
|---|---|---|
| `get_teams(ids, numbers, events, registered, programs, grades, countries, my_teams, page, per_page)` / `iter_teams(...)` | `team_getTeams` | List/search teams |
| `get_team(team_id)` | `team_getTeam` | Single team; raises `VexEventsNotFoundError` if unknown |
| `get_team_events(team_id, skus, seasons, start, end, levels, page, per_page)` / `iter_team_events(...)` | `team_getEvents` | Events a team attended |
| `get_team_matches(team_id, events, seasons, rounds, instances, matchnums, page, per_page)` / `iter_team_matches(...)` | `team_getMatches` | Matches a team played |
| `get_team_rankings(team_id, events, ranks, seasons, page, per_page)` / `iter_team_rankings(...)` | `team_getRankings` | Rankings a team achieved |
| `get_team_skills(team_id, events, types, seasons, page, per_page)` / `iter_team_skills(...)` | `team_getSkills` | Skills runs a team performed |
| `get_team_awards(team_id, events, seasons, page, per_page)` / `iter_team_awards(...)` | `team_getAwards` | Awards a team received |

### Programs

| Method | API operation | Notes |
|---|---|---|
| `get_program(program_id)` | `program_getProgram` | Single program |
| `get_programs(ids, page, per_page)` / `iter_programs(...)` | `program_getPrograms` | List programs |

### Seasons

| Method | API operation | Notes |
|---|---|---|
| `get_seasons(ids, programs, teams, start, end, active, page, per_page)` / `iter_seasons(...)` | `season_getSeasons` | List/search seasons |
| `get_season(season_id)` | `season_getSeason` | Single season |
| `get_season_events(season_id, skus, teams, start, end, levels, page, per_page)` / `iter_season_events(...)` | `season_getEvents` | Events in a season |

`start`/`end` filters take an RFC3339 datetime string (e.g.
`"2024-01-01T00:00:00Z"`), matching what the API expects - build one with
`datetime.isoformat()` (see `examples/pagination.py`).

## Errors

All exceptions are importable directly from `pyvexevents` and subclass
`VexEventsError`, which has `.message`, `.code` (the API's own error
code, if any) and `.status` (HTTP status code, `None` for connection
failures):

- `VexEventsConnectionError` - the request itself failed (no network,
  DNS, TLS, timeout) before any HTTP response was received.
- `VexEventsHTTPError` - the API responded with a 4xx/5xx status.
  - `VexEventsNotFoundError` - 404 (unknown event/team/program/season id).
  - `VexEventsAuthError` - 401/403 (missing, invalid, or expired token).

```python
from pyvexevents import VexEventsError, VexEventsNotFoundError

try:
    client.get_team(999999999)
except VexEventsNotFoundError:
    print("no such team")
except VexEventsError as exc:
    print("something else went wrong:", exc)
```

## Constants

Importable directly from `pyvexevents` as `enum.Enum`/`enum.IntEnum`
subclasses:

- `EventType` - `TOURNAMENT`, `LEAGUE`, `WORKSHOP`, `VIRTUAL`
- `EventLevel` - `WORLD`, `NATIONAL`, `REGIONAL`, `STATE`, `SIGNATURE`, `OTHER`
- `Grade` - `COLLEGE`, `HIGH_SCHOOL`, `MIDDLE_SCHOOL`, `ELEMENTARY_SCHOOL`
- `SkillType` - `DRIVER`, `PROGRAMMING`, `PACKAGE_DELIVERY_TIME`
- `AllianceColor` - `RED`, `BLUE`
- `AwardDesignation` - `TOURNAMENT`, `DIVISION`
- `AwardClassification` - `CHAMPION`, `FINALIST`, `SEMIFINALIST`, `QUARTERFINALIST`
- `MatchRound` (`IntEnum`) - `PRACTICE` (1), `QUALIFICATION` (2),
  `QUARTERFINALS` (3), `SEMIFINALS` (4), `FINALS` (5), `ROUND_OF_16` (6) -
  the API documents these as "typical values"; other integers can appear
  for program-specific bracket formats.

## Response shapes

Responses are returned as plain `dict`/`list` (decoded straight from
JSON by `requests`), matching the schemas in the OpenAPI spec:

- List endpoints return a `VexEventsPage`: `{"meta": {...page info...},
  "data": [...]}`. `meta` includes `current_page`, `last_page`,
  `per_page`, `total`, etc.
- Single-item endpoints (`get_event`, `get_team`, `get_program`,
  `get_season`) return the object dict directly.
- Nested references (an event's `season`, a match's `event`/`division`, a
  ranking's `team`, etc.) are small `{"id": ..., "name": ..., "code": ...}`
  dicts, per the spec's `IdInfo` schema.

Refer to the [swagger spec](https://events.vex.com/api/v2/swagger.yml)
for the exact field list per object type (`Event`, `Team`, `MatchObj`,
`Alliance`, `Ranking`, `Skill`, `Award`, `Program`, `Season`, ...).

## Testing

```
pip install -e ".[dev]"
pytest
```

`tests/` mocks the client's `requests.Session` (via `unittest.mock`, no
network access or extra test-only dependency required) to cover query-
string construction, pagination walking, and the HTTP-status-to-exception
mapping.
