Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions .github/copilot-instructions.md

This file was deleted.

82 changes: 82 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# AI Coding Agent Instructions

Guidance for AI assistants (Claude Code, GitHub Copilot, Cursor, etc.) working in this repository.

## Project Overview

Python 3.12+ wrapper around the Sportradar DataCore REST API (Handball).

- Public API: `src/sportradar_datacore_api/` — centered on `HandballAPI`
- Generated OpenAPI client: `src/_vendor/datacore_client/` (mirrors `build/`)
- Full architecture: [docs/architecture.md](docs/architecture.md)

## Hard Rules

- **Never edit `src/_vendor/` or `build/` by hand.** These are fully generated. Regenerate via `scripts/codegen.sh` (Linux/Mac) or `scripts/codegen.ps1` (Windows).
- **Never commit `.env` or credential files.** Credentials are managed through environment variables only. See [README.md](README.md#configuration) for required variable names.
- **Never add `# type: ignore` or `Any` casts in `src/sportradar_datacore_api/`.** Maintain full type safety in hand-written code. Vendor code is excluded from mypy via config.

## Code Style

- Target Python 3.12+. Use built-in generics (`list[X]`, `dict[K, V]`, `X | Y`) — no `from __future__ import annotations`.
- Formatter and linter: `ruff` (line length 88, config in `pyproject.toml`). Run `ruff check --fix` and `ruff format` before committing.
- Static typing: `mypy --strict` (applied to `src/sportradar_datacore_api/` only).
- Pre-commit hooks enforce all of the above automatically. Install with `pre-commit install`.

## Adding Functionality

1. Add high-level helpers to `HandballAPI` in `src/sportradar_datacore_api/handball.py`.
2. Use generated models from `datacore_client.models` for all typed returns.
3. Raise typed errors from `src/sportradar_datacore_api/errors.py` — never raise bare `Exception`.
4. Write tests in `test/` using `pytest`. Tests use live API calls and are skipped when env vars are absent.
5. Update [README.md](README.md) examples if the public surface changes.

## Testing

```bash
pytest
```

Live API calls require env vars. Tests are skipped automatically when credentials are not set. Do not mock the HTTP transport layer — tests validate real API contract behaviour.

## Code Generation

When the upstream OpenAPI spec changes, regenerate the vendor client:

```bash
# Linux / Mac
./scripts/codegen.sh

# Windows (PowerShell)
./scripts/codegen.ps1
```

The generator fetches the spec from the Sportradar developer portal, runs `openapi-python-client`, and moves the output into `src/_vendor/datacore_client/`.

## Repository Layout

```
src/
sportradar_datacore_api/ # Hand-written public API (edit here)
api.py # DataCoreAPI base: OAuth2 auth, token refresh
handball.py # HandballAPI: high-level helpers
errors.py # Typed exception hierarchy
__init__.py
_vendor/
datacore_client/ # Generated OpenAPI client (do not edit)
scripts/
codegen.sh # Client regeneration (Linux/Mac)
codegen.ps1 # Client regeneration (Windows)
openapi/
config.yaml # openapi-python-client generator config
test/
test_handball.py # Integration tests (live API)
docs/ # Extended documentation
```

## Further Reading

- [docs/architecture.md](docs/architecture.md) — design decisions, layering, auth flow
- [docs/api-reference.md](docs/api-reference.md) — `HandballAPI` public method reference
- [docs/development.md](docs/development.md) — setup, CI, releasing
- [docs/errors.md](docs/errors.md) — exception hierarchy
92 changes: 90 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

A Python wrapper for the Sportradar DataCore REST API (Handball).

The package also includes a separate client for the DataCore Streaming API, keeping the REST and MQTT/WebSocket surfaces isolated.

This library simplifies interaction with the Sportradar API by handling OpenID Connect (OIDC) authentication automatically and providing a fully typed interface for all API endpoints.

## Features
Expand Down Expand Up @@ -36,6 +38,12 @@ If you prefer an editable install instead of syncing a lockfile:
uv pip install -e "."
```

To enable the streaming client as well:

```bash
uv pip install -e ".[stream]"
```

## Configuration

The library uses **pydantic** and **python-dotenv** to manage configuration. You can provide credentials via a `.env` file in your project root or via environment variables.
Expand All @@ -48,8 +56,13 @@ AUTH_URL=https://token.connect.sportradar.com/v1/oauth2/rest/token
CLIENT_ID=your_client_id
CLIENT_SECRET=your_client_secret
CLIENT_ORGANIZATION_ID=your_org_id
STREAM_TOKEN_BASE_URL=https://token.connect.sportradar.com/v1
STREAM_FIXTURE_ID=your_fixture_id
STREAM_VENUE_ID=your_venue_id
```

`STREAM_TOKEN_BASE_URL` is the token service base for the streaming API. If you already have `AUTH_URL` set to `/oauth2/rest/token`, the streaming client can derive the base URL from it automatically.

## Usage

### Basic Example
Expand Down Expand Up @@ -114,11 +127,87 @@ if response.status_code == 200:
print(data.data[0].name_local)
```

## Streaming API

The streaming API is intentionally exposed via separate classes so the REST and MQTT clients stay independent.

### Connect To A Fixture Stream

```python
import os

from sportradar_datacore_api.streaming import HandballStreamingAPI


def handle_message(message) -> None:
print(message.topic)
print(message.message_type)
print(message.payload)


stream_api = HandballStreamingAPI(
client_id=os.getenv("CLIENT_ID", ""),
client_secret=os.getenv("CLIENT_SECRET", ""),
sport="handball",
token_base_url=os.getenv("STREAM_TOKEN_BASE_URL"),
auth_url=os.getenv("AUTH_URL"),
)

client = stream_api.create_fixture_stream(
fixture_id=os.getenv("STREAM_FIXTURE_ID", ""),
scopes=[
"read:stream_events",
"read:stream_status",
"read:stream_statistics",
"read:stream_play_by_play",
"read:stream_persons",
],
on_message=handle_message,
)

with client:
input("Press Enter to stop listening...\n")
```

### Publish To A Granted Topic

```python
stream_api = HandballStreamingAPI(
client_id=os.getenv("CLIENT_ID", ""),
client_secret=os.getenv("CLIENT_SECRET", ""),
sport="handball",
token_base_url=os.getenv("STREAM_TOKEN_BASE_URL"),
)

client = stream_api.create_fixture_stream(
fixture_id=os.getenv("STREAM_FIXTURE_ID", ""),
scopes=["write:stream_events", "read:response"],
)

event_message = {
"type": "event",
"fixtureId": os.getenv("STREAM_FIXTURE_ID", ""),
"clientType": "MyApp:1.0.0",
"data": {
"eventId": "11111111-1111-1111-1111-111111111111",
"class": "heartbeat",
"eventType": "client",
},
}

with client:
result = client.publish_to_scope("write:stream_events", event_message)
print(result)
```

Messages containing `compressedData` are decoded automatically and exposed as `decodedCompressedData` in the parsed payload.

## Architecture

This project uses a **Wrapper Pattern** around a generated OpenAPI client.

- **`src/sportradar_datacore_api/`**: The public-facing code. Contains the `HandballAPI` class, authentication logic, and user-friendly helpers.
- **`src/sportradar_datacore_api/streaming.py`**: Separate streaming access and MQTT client.
- **`src/_vendor/datacore_client/`**: The low-level client code generated from the Sportradar OpenAPI specification.
- *Note*: This directory allows us to ship the generated code without external dependencies or versioning conflicts.
- **Do not edit files in `_vendor` manually.** They are overwritten during code generation.
Expand All @@ -132,8 +221,7 @@ This project uses a **Wrapper Pattern** around a generated OpenAPI client.

## AI Assistance

If you are using GitHub Copilot in this repo, see the project-specific guidance in
`.github/copilot-instructions.md`.
AI coding assistants should read [AGENTS.md](AGENTS.md) for project-specific guidance and rules.

## Development

Expand Down
Loading
Loading