Merge pull request '1.1.0' (#9) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 30s
All checks were successful
Build And Publish Package / publish (push) Successful in 30s
Reviewed-on: #9
This commit is contained in:
@ -1,18 +1,18 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/crate-ci/typos
|
- repo: https://github.com/crate-ci/typos
|
||||||
rev: v1.36.3
|
rev: v1.42.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: typos
|
- id: typos
|
||||||
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.13.2
|
rev: v0.14.13
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [ --fix ]
|
args: [ --fix ]
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
|
|
||||||
- repo: https://github.com/RobertCraigie/pyright-python
|
- repo: https://github.com/RobertCraigie/pyright-python
|
||||||
rev: v1.1.405
|
rev: v1.1.408
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyright
|
- id: pyright
|
||||||
|
|
||||||
|
|||||||
170
README.md
170
README.md
@ -0,0 +1,170 @@
|
|||||||
|
# Twitch Client (Async)
|
||||||
|
|
||||||
|
An internal **async Twitch API client** built on top of **AioHTTPX**, providing typed access to the Twitch **Helix API** and **OAuth2** endpoints with built-in rate limiting and optional caching.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What it provides
|
||||||
|
|
||||||
|
* `TwitchAPIClient` — async client for Twitch **Helix API**
|
||||||
|
* `TwitchAuthClient` — async client for Twitch **OAuth2**
|
||||||
|
* Built on **AioHTTPX** (aiohttp transport, Redis rate limiting & caching)
|
||||||
|
* Strongly typed responses using **Pydantic models**
|
||||||
|
* Automatic request parameter cleanup (`None` values removed)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation (internal)
|
||||||
|
|
||||||
|
Configure your private package index, then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install twitchclient
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
### OAuth helper client
|
||||||
|
|
||||||
|
```python
|
||||||
|
from twitchclient.auth import TwitchAuthClient
|
||||||
|
from twitchclient import scopes
|
||||||
|
|
||||||
|
auth = TwitchAuthClient(
|
||||||
|
client_id="CLIENT_ID",
|
||||||
|
client_secret="CLIENT_SECRET",
|
||||||
|
redirect_uri="https://your.app/callback",
|
||||||
|
)
|
||||||
|
|
||||||
|
url = await auth.create_authorization_code_grant_flow_url(
|
||||||
|
scope=[scopes.USER_READ_EMAIL, scopes.CHAT_READ],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### App access token
|
||||||
|
|
||||||
|
```python
|
||||||
|
token = await auth.app_access_token()
|
||||||
|
print(token.access_token)
|
||||||
|
```
|
||||||
|
|
||||||
|
### User access token
|
||||||
|
|
||||||
|
```python
|
||||||
|
token = await auth.user_access_token(code="AUTH_CODE")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Client
|
||||||
|
|
||||||
|
```python
|
||||||
|
from twitchclient.api import TwitchAPIClient
|
||||||
|
|
||||||
|
client = TwitchAPIClient(
|
||||||
|
client_id="CLIENT_ID",
|
||||||
|
client_secret="CLIENT_SECRET",
|
||||||
|
redirect_uri="https://your.app/callback",
|
||||||
|
redis_url="redis://localhost:6379", # optional
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
* Base URL: `https://api.twitch.tv/helix`
|
||||||
|
* `Client-Id` header is set automatically
|
||||||
|
* Rate limit defaults to **700 req/min** when Redis is enabled
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example usage
|
||||||
|
|
||||||
|
### Get channel information
|
||||||
|
|
||||||
|
```python
|
||||||
|
channels = await client.get_channel_information(
|
||||||
|
access_token=token.access_token,
|
||||||
|
broadcaster_id=123456,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start a commercial
|
||||||
|
|
||||||
|
```python
|
||||||
|
await client.start_commercial(
|
||||||
|
access_token=token.access_token,
|
||||||
|
broadcaster_id=123456,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cached request
|
||||||
|
|
||||||
|
Caching is controlled via the `X-Cache-TTL` header:
|
||||||
|
|
||||||
|
```python
|
||||||
|
streams = await client.get_streams(
|
||||||
|
access_token=token.access_token,
|
||||||
|
game_id=509658,
|
||||||
|
cache_time=30,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rate limiting & caching
|
||||||
|
|
||||||
|
* **Redis-backed**
|
||||||
|
* Shared key: `twitch`
|
||||||
|
* Enabled automatically when `redis_url` is provided
|
||||||
|
* Cache TTL is per-request (`X-Cache-TTL` header)
|
||||||
|
|
||||||
|
Implementation is inherited from **AioHTTPX transports** .
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
All API methods raise typed exceptions:
|
||||||
|
|
||||||
|
* `ClientError(status_code, message)`
|
||||||
|
* `InternalError(status_code, message)`
|
||||||
|
|
||||||
|
Defined in schema module .
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scopes
|
||||||
|
|
||||||
|
OAuth scopes are defined as constants and typed literals:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from twitchclient import scopes
|
||||||
|
|
||||||
|
scopes.CHANNEL_READ_SUBSCRIPTIONS
|
||||||
|
scopes.CHAT_READ
|
||||||
|
```
|
||||||
|
|
||||||
|
See `scopes.py` for the full list .
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typed responses
|
||||||
|
|
||||||
|
All successful responses return **Pydantic models**, e.g.:
|
||||||
|
|
||||||
|
* `ChannelsInformation`
|
||||||
|
* `Streams`
|
||||||
|
* `Users`
|
||||||
|
* `EventsubSubscriptions`
|
||||||
|
|
||||||
|
Models live in `schema.py` .
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes & limitations
|
||||||
|
|
||||||
|
* Async-only (no sync client)
|
||||||
|
* Internal API, no stability guarantees
|
||||||
|
* Some endpoints still marked TODO (e.g. Guest Star)
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "twitchclient"
|
name = "twitchclient"
|
||||||
version = "1.0.7"
|
version = "1.1.0"
|
||||||
description = "Client for Twitch API"
|
description = "Client for Twitch API"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [{ name = "Miwory", email = "miwory.uwu@gmail.com" }]
|
||||||
{ name = "Miwory", email = "miwory.uwu@gmail.com" }
|
|
||||||
]
|
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = ["aiohttpx>=1.3,<=2.0", "pydantic>=2.12,<=2.13"]
|
||||||
"aiohttpx>=1.3,<=2.0",
|
|
||||||
"pydantic>=2.12,<=2.13",
|
|
||||||
]
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["uv_build>=0.9.2,<0.10.0"]
|
requires = ["uv_build>=0.9.2,<0.10.0"]
|
||||||
@ -18,10 +13,10 @@ build-backend = "uv_build"
|
|||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"ruff==0.14.9",
|
"ruff==0.14.13",
|
||||||
"pyright==1.1.407",
|
"pyright==1.1.408",
|
||||||
"poethepoet==0.38.0",
|
"poethepoet==0.40.0",
|
||||||
"pre-commit==4.5.0",
|
"pre-commit==4.5.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[tool.uv.index]]
|
[[tool.uv.index]]
|
||||||
|
|||||||
@ -13,10 +13,10 @@ from .eventsub import types as sub_type
|
|||||||
class TwitchAPIClient(AioHTTPXClient):
|
class TwitchAPIClient(AioHTTPXClient):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
redis_url: str,
|
|
||||||
client_id: str,
|
client_id: str,
|
||||||
client_secret: str,
|
client_secret: str,
|
||||||
redirect_uri: str,
|
redirect_uri: str,
|
||||||
|
redis_url: str | None = None,
|
||||||
):
|
):
|
||||||
self.base_uri = 'https://api.twitch.tv/helix'
|
self.base_uri = 'https://api.twitch.tv/helix'
|
||||||
self.client_id = client_id
|
self.client_id = client_id
|
||||||
@ -27,8 +27,8 @@ class TwitchAPIClient(AioHTTPXClient):
|
|||||||
base_url=self.base_uri,
|
base_url=self.base_uri,
|
||||||
headers={'Client-Id': self.client_id},
|
headers={'Client-Id': self.client_id},
|
||||||
redis_url=redis_url,
|
redis_url=redis_url,
|
||||||
key='twitch',
|
key='twitch' if redis_url else None,
|
||||||
limit=700,
|
limit=700 if redis_url else None,
|
||||||
logger='Twitch API',
|
logger='Twitch API',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -10,10 +10,10 @@ from . import scopes
|
|||||||
class TwitchAuthClient(AioHTTPXClient):
|
class TwitchAuthClient(AioHTTPXClient):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
redis_url: str,
|
|
||||||
client_id: str,
|
client_id: str,
|
||||||
client_secret: str,
|
client_secret: str,
|
||||||
redirect_uri: str,
|
redirect_uri: str,
|
||||||
|
redis_url: str | None = None,
|
||||||
):
|
):
|
||||||
self.base_uri = 'https://id.twitch.tv/oauth2'
|
self.base_uri = 'https://id.twitch.tv/oauth2'
|
||||||
self.client_id = client_id
|
self.client_id = client_id
|
||||||
@ -23,8 +23,8 @@ class TwitchAuthClient(AioHTTPXClient):
|
|||||||
super().__init__(
|
super().__init__(
|
||||||
base_url=self.base_uri,
|
base_url=self.base_uri,
|
||||||
redis_url=redis_url,
|
redis_url=redis_url,
|
||||||
key='twitch',
|
key='twitch' if redis_url else None,
|
||||||
limit=700,
|
limit=700 if redis_url else None,
|
||||||
logger='Twitch Auth',
|
logger='Twitch Auth',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user