Metadata-Version: 2.4
Name: omnidapter-sdk
Version: 0.5.1
Summary: Omnidapter API client SDK
Project-URL: Homepage, https://github.com/heckerlabs/omnidapter
Project-URL: Repository, https://github.com/heckerlabs/omnidapter/tree/main/omnidapter-sdk
License: MIT
Requires-Python: >=3.10
Requires-Dist: aiohttp-retry>=2.8.3
Requires-Dist: aiohttp>=3.9
Requires-Dist: pydantic>=2.0
Requires-Dist: python-dateutil>=2.8.2
Provides-Extra: test
Requires-Dist: omnidapter-server; extra == 'test'
Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
Requires-Dist: pytest>=9.0.3; extra == 'test'
Requires-Dist: testcontainers[postgres]>=4.0; extra == 'test'
Requires-Dist: uvicorn>=0.30; extra == 'test'
Description-Content-Type: text/markdown

# omnidapter-sdk

Python client for the [Omnidapter](https://omnidapter.heckerlabs.ai) API.

Omnidapter lets you connect to your users' calendars (and other services) through a single unified API. You create a link token, send the user through the Connect UI to authorise their account, and then read their data using the connection that comes back.

## Installation

```bash
pip install omnidapter-sdk
```

Requires Python 3.10+.

## Getting started

The SDK is **async-native** — every operation method returns a coroutine that must be awaited. `OmnidapterClient` is an async context manager; use `async with` to guarantee the underlying HTTP session is closed:

```python
import asyncio
from omnidapter_sdk import OmnidapterClient


async def main() -> None:
    async with OmnidapterClient(
        base_url="https://api.example.com",
        api_key="omni_live_...",
    ) as client:
        providers = await client.providers.list_providers()
        for provider in providers.data:
            print(provider.key, provider.display_name)


asyncio.run(main())
```

For long-lived clients (e.g. one per FastAPI application), construct directly and call `await client.close()` at shutdown.

The actual payload is always on `.data`.

> **Breaking change in 0.5.0.** Prior versions (≤ 0.4.x) returned plain values from synchronous methods. The 0.5.0 release flips every method to `async def`; existing call sites must add `await` and wrap construction in `async with` (or call `await client.close()` explicitly).

---

## Providers

Providers are the services Omnidapter can connect to (e.g. Google, Microsoft). You typically list them once to populate a picker in your UI.

```python
providers = await client.providers.list_providers()
for provider in providers.data:
    print(provider.key, provider.display_name)

# Fetch a single provider by its key
google = await client.providers.get_provider(provider_key="google")
print(google.data.display_name)
```

---

## Connections

A connection represents an authorised link between one of your end users and a provider. Once a user completes the Connect flow, their connection appears here.

```python
# List all connections, optionally filtering by status or provider
connections = await client.connections.list_connections(status="active", provider="google")
for conn in connections.data:
    print(conn.id, conn.provider_key, conn.status)

# Fetch a single connection
conn = await client.connections.get_connection(connection_id="conn_...")
print(conn.data.status)

# Revoke a connection — this also invalidates any stored credentials
await client.connections.delete_connection(connection_id="conn_...")
```

---

## Link tokens

A link token is a short-lived, single-use token that grants an end user access to the Connect UI. Generate one server-side, pass it to your frontend, and redirect the user to `connect_url`. Omnidapter will handle the OAuth flow and create a connection on success.

```python
from omnidapter_sdk.models import CreateLinkTokenRequest

result = await client.link_tokens.create_link_token(
    create_link_token_request=CreateLinkTokenRequest(
        end_user_id="user_123",           # your internal user ID
        allowed_providers=["google", "microsoft"],  # restrict which providers are shown
    )
)

token = result.data
print(token.token)       # lt_... — pass this to your frontend
print(token.connect_url) # redirect the user here to start the Connect flow
print(token.expires_at)  # datetime — tokens are short-lived, generate them on demand
```

---

## Calendar

Once a user has a connection, you can read their calendar data. All calendar operations require a `connection_id`.

```python
from datetime import datetime, timezone

# List the calendars available on a connection
calendars = await client.calendar.list_calendars(connection_id="conn_...")
for cal in calendars.data:
    print(cal.id, cal.name)

# List events within a time range across a specific calendar
events = await client.calendar.list_events(
    connection_id="conn_...",
    calendar_id="cal_...",
    start=datetime(2026, 4, 1, tzinfo=timezone.utc),
    end=datetime(2026, 4, 30, tzinfo=timezone.utc),
)
for event in events.data:
    print(event.id, event.title, event.start)
```

---

## Error handling

The SDK raises `ApiException` for any non-2xx response. You can inspect the HTTP status code and the raw response body to handle errors appropriately.

```python
from omnidapter_sdk.exceptions import ApiException

try:
    conn = await client.connections.get_connection(connection_id="conn_unknown")
except ApiException as e:
    print(e.status)  # e.g. 404
    print(e.body)    # JSON error body from the server
```

---

## Notes

- The SDK is generated from the server's OpenAPI spec via `scripts/generate_sdks.sh`.
  Run that script after pulling changes to regenerate the client code.
- The transport is `aiohttp` (via openapi-generator's `library=asyncio` template).
  An `aiohttp.ClientSession` is opened lazily on the first request and closed when
  the `OmnidapterClient` is closed (via `async with` or `await client.close()`).
  Skipping the close emits a `ResourceWarning` from aiohttp at process exit.
