Metadata-Version: 2.4
Name: lagclient
Version: 0.1.0
Summary: Official Python SDK for the Lag API
Project-URL: Homepage, https://trylag.com
Project-URL: Repository, https://github.com/lag-app/sdk-python
Project-URL: Issues, https://github.com/lag-app/sdk-python/issues
Author: Lag
License: MIT License
        
        Copyright (c) 2026 Lag
        
        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: api,client,lag,rest,sdk,trylag
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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: httpx<1,>=0.27
Requires-Dist: pydantic<3,>=2.6
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# lagclient

The official Python SDK for the [Lag](https://trylag.com) API.

`lagclient` is a hand-written REST client covering the public Lag API: users,
friends, DMs, servers, rooms, room messages, events, and image uploads. It
does **not** include the WebSocket protocol or the voice client - those are
out of scope for this package.

Ships both a synchronous `Client` (backed by `httpx.Client`) and an
asynchronous `AsyncClient` (backed by `httpx.AsyncClient`), sharing a single
Pydantic v2 model layer.

- Typed response models with autocomplete and validation.
- Typed exception hierarchy with automatic retries on transient failures.
- Cursor pagination helpers (sync and async iterators).
- Multipart image upload from path, bytes, or file-like.
- Zero required dependencies beyond `httpx` and `pydantic`.

## Install

```bash
pip install lagclient
```

Requires Python 3.9+.

## Quickstart

### Synchronous

```python
import os
from lagclient import Client

with Client(token=os.environ["LAG_TOKEN"]) as client:
    me = client.users.me()
    print(f"Hello, {me.display_name or me.username}")

    for server in client.servers.list():
        print(f"- {server.name} ({server.member_count} members)")
```

### Asynchronous

```python
import asyncio
import os
from lagclient import AsyncClient

async def main() -> None:
    async with AsyncClient(token=os.environ["LAG_TOKEN"]) as client:
        me = await client.users.me()
        print(f"Hello, {me.display_name or me.username}")

        for server in await client.servers.list():
            print(f"- {server.name}")

asyncio.run(main())
```

## Authentication

The SDK accepts any Bearer token the API would accept:

- A **Personal Access Token** (`lag_pat_*`) - the recommended option for
  scripts, bots, and CI. Create one in the Lag web app under settings, or
  via the `lag` CLI with `lag auth login`.
- A **Supabase JWT** - useful when you already have an authenticated session
  (e.g. inside a web backend).

The token is sent as `Authorization: Bearer <token>` on every request. The
SDK does not implement OAuth flows, refresh tokens, or browser-based login -
obtain a token elsewhere and pass it in.

## Configuration

```python
client = Client(
    token="lag_pat_...",
    base_url="https://api.trylag.com",  # default
    timeout_seconds=30.0,               # default
    max_retries=2,                       # default
    user_agent="my-app/1.0",             # optional override
    extra_headers={"X-My-Header": "v"}, # optional extra headers
    # http_client=httpx.Client(...)     # bring your own
)
```

`max_retries` controls how many times the client retries on a transient
failure (5xx, 429, network error). Backoff is exponential with jitter, capped
at ~8s. The server's `Retry-After` header is honored on 429.

Both clients are also usable without a context manager:

```python
client = Client(token="lag_pat_...")
try:
    client.users.me()
finally:
    client.close()
```

## Resources

Every resource hangs off the client instance. The async client has the exact
same tree, just with `await` in front of each call.

| Attribute | What it covers |
|---|---|
| `client.system` | `/health`, `/version`, `/system-status`, `/config` |
| `client.users` | `/users/me`, `/users/me/avatar`, `/users/:id`, `/users/search`, Steam helpers |
| `client.friends` | list, requests, send/accept/decline, remove, block |
| `client.dms` | conversations + messages with cursor pagination |
| `client.servers` | servers CRUD, icon upload, leave |
| `client.servers.invites` | create / list / revoke / preview / join |
| `client.servers.members` | kick, ban, mute (and lists of active bans/mutes) |
| `client.servers.roles` | role CRUD and assignment |
| `client.servers.rooms` | voice rooms |
| `client.servers.rooms.messages` | room chat: list/send/edit/delete with cursor pagination |
| `client.events` | server events: list/create/get/update/cancel/RSVP |
| `client.events.guests` | host-side guest moderation |
| `client.events.templates` | recurring event templates |
| `client.images` | multipart upload, metadata, status, delete |

### Pagination

DM and room message endpoints return `{messages, hasMore, nextCursor}`. You
can walk pages yourself:

```python
cursor = None
while True:
    page = client.dms.list_messages("c1", limit=50, cursor=cursor)
    for msg in page["messages"]:
        handle(msg)
    if not page["hasMore"] or page["nextCursor"] is None:
        break
    cursor = page["nextCursor"]
```

Or use the built-in iterator helpers:

```python
# Sync
for page in client.dms.iter_messages("c1", limit=50):
    for msg in page.items:
        handle(msg)

# Async
async for page in async_client.dms.iter_messages("c1", limit=50):
    for msg in page.items:
        handle(msg)
```

The same pattern works for room messages via
`client.servers.rooms.messages.iter(server_id, room_id)`.

### Image upload

```python
# From a file path:
client.images.upload("./avatar.png", purpose="avatar")

# From raw bytes:
with open("./avatar.png", "rb") as f:
    client.images.upload(
        f.read(),
        purpose="avatar",
        filename="avatar.png",
        content_type="image/png",
    )

# From a file-like:
with open("./cover.jpg", "rb") as f:
    client.images.upload(f, purpose="event_cover", filename="cover.jpg")
```

The maximum upload size is 25 MiB.

## Error handling

Every non-2xx response becomes a typed exception you can catch precisely:

```python
from lagclient import (
    LagAPIError,
    LagAuthError,
    LagPermissionError,
    LagNotFoundError,
    LagConflictError,
    LagRateLimitError,
    LagServerError,
    LagConnectionError,
)

try:
    client.servers.get("does-not-exist")
except LagNotFoundError:
    print("not there")
except LagRateLimitError as err:
    print(f"slow down - retry after {err.retry_after_seconds}s")
except LagAPIError as err:
    print(f"{err.status}: {err}")
```

Network failures (DNS, refused, timeouts before any response) are raised as
`LagConnectionError`. Everything else is a subclass of `LagAPIError`.

## Local development

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest
mypy src/lagclient
ruff check src tests
python -m build   # produces wheel + sdist
```

Tests use `respx` to mock `httpx` transport - no real API is required. Every
resource has its own test file in `tests/`.

## Related

- The Lag API itself - `product/apps/api/` in the Lag monorepo.
- `@lag/sdk` for TypeScript / Node - sibling package in `sdks/node/`.
- The `lag` CLI in `cli/` - also MIT licensed.

## License

MIT. See [`LICENSE`](./LICENSE).
