Skip to content
Merged
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
18 changes: 18 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space

[*.py]
indent_size = 4
max_line_length = 120

[*.{yml,yaml,json,toml}]
indent_size = 2

[*.md]
trim_trailing_whitespace = false
20 changes: 20 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Optional local hooks. CI (.github/workflows/ci.yml) is the source of truth — it also
# runs `ruff check src/onepin/_cli` (the hand-rolled CLI lives under the Fern-excluded
# src/onepin/ prefix). Install: `pip install pre-commit && pre-commit install`.
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
exclude: '\.md$'
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-merge-conflict
- id: check-added-large-files
62 changes: 57 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

Python SDK + CLI for [OnePin](https://onepin.ai) — the AI-powered voice workflow platform.

[![PyPI version](https://img.shields.io/pypi/v/onepin)](https://pypi.org/project/onepin/)
[![Python versions](https://img.shields.io/pypi/pyversions/onepin)](https://pypi.org/project/onepin/)
[![CI](https://github.com/podonos/onepin-python/actions/workflows/ci.yml/badge.svg)](https://github.com/podonos/onepin-python/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

## Installation

```bash
Expand Down Expand Up @@ -423,23 +428,70 @@ onepin workflows create --name "My workflow" --definition @workflow.json
## SDK usage

The Python SDK is generated by [Fern](https://buildwithfern.com) from the OpenAPI spec.
Reference docs live at [docs.onepin.ai](https://docs.onepin.ai).
Full per-endpoint reference: [`src/onepin/reference.md`](src/onepin/reference.md) ·
hosted docs at [docs.onepin.ai](https://docs.onepin.ai). Runnable scripts in [`examples/`](examples/).

```python
from onepin import OnePinClient

client = OnePinClient(token="op_...") # or base_url=... for a non-prod environment
client = OnePinClient(token="op_...") # your API key, used as the bearer token

workflows = client.workflows.list()
workflows = client.workflows.list() # paginated — iterate items directly
voices = client.voices.list()
ready = client.health.readiness()
```

### Environments

```python
from onepin import OnePinClient
from onepin.environment import OnePinClientEnvironment

# Defaults to PROD (https://api.onepin.ai). Target the dev API instead:
client = OnePinClient(token="op_...", environment=OnePinClientEnvironment.DEV)
# ...or point at any host directly:
client = OnePinClient(token="op_...", base_url="https://dev-api.onepin.ai")
```

### Async

```python
import asyncio
from onepin import AsyncOnePinClient

client = AsyncOnePinClient(token="op_...")


async def main() -> None:
voices = await client.voices.list()


asyncio.run(main())
```

An `AsyncOnePinClient` with the same surface is also available for `asyncio` code.
### Errors, retries, timeouts

```python
from onepin import OnePinClient
from onepin.core.api_error import ApiError

client = OnePinClient(token="op_...", timeout=20.0) # client-wide timeout (default 60s)

try:
client.workflows.get(
workflow_id="wf_123",
request_options={"max_retries": 1, "timeout_in_seconds": 5},
)
except ApiError as e:
print(e.status_code, e.body)
```

## Repository structure

- `src/onepin/` — Fern-generated SDK (do not hand-edit; overwritten on each regen)
- `src/onepin/_cli/` — hand-rolled Typer CLI atop the generated client
- `src/onepin/reference.md` — full per-endpoint API reference
- `examples/` — runnable SDK snippets
- `scripts/post_fern.sh` — restores `py.typed` markers after Fern overwrites `src/onepin/`

## License
Expand All @@ -448,4 +500,4 @@ MIT — see [LICENSE](LICENSE).

## Status

Published on PyPI — latest: **v0.2.0**. See the [changelog](CHANGELOG.md).
Published on PyPI — latest: **v0.6.0**. See the [changelog](CHANGELOG.md).
10 changes: 10 additions & 0 deletions SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Support

Need help with the OnePin Python SDK or CLI?

- 📖 **Docs:** https://docs.onepin.ai
- 💬 **Questions & bugs:** open an [issue](https://github.com/podonos/onepin-python/issues) using the bug-report or feature-request template
- ✉️ **Email:** support@onepin.ai
- 🔑 **API keys, account & billing:** https://app.onepin.ai

For security vulnerabilities, **do not** open a public issue — see [SECURITY.md](SECURITY.md).
23 changes: 23 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Examples

Runnable snippets for the OnePin Python SDK. Each reads your API key from the
`ONEPIN_API_KEY` environment variable.

```sh
pip install onepin
export ONEPIN_API_KEY="op_..." # create one at https://app.onepin.ai/settings/api-keys
python examples/quickstart.py
```

| File | Shows |
|------|-------|
| `quickstart.py` | Construct a client, readiness probe, list workflows |
| `list_workflows.py` | Paginate workflows, fetch one by id |
| `async_client.py` | `AsyncOnePinClient` + async pagination |
| `error_handling.py` | `ApiError`, retries, timeouts |
| `provider_keys.py` | Bring-your-own provider keys (BYO-key) |

By default the client targets **PROD** (`https://api.onepin.ai`). Pass
`environment=OnePinClientEnvironment.DEV` or `base_url="https://dev-api.onepin.ai"` to
target another host. See the repo [README](../README.md#sdk-usage) and the full
per-endpoint reference in [`src/onepin/reference.md`](../src/onepin/reference.md).
20 changes: 20 additions & 0 deletions examples/async_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Async client usage with the AsyncOnePinClient."""

import asyncio
import os

from onepin import AsyncOnePinClient


async def main() -> None:
client = AsyncOnePinClient(token=os.environ["ONEPIN_API_KEY"])

# AsyncPager is async-iterable.
voices = await client.voices.list()
async for voice in voices:
print(voice)
break # just the first


if __name__ == "__main__":
asyncio.run(main())
27 changes: 27 additions & 0 deletions examples/error_handling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Error handling, retries, and timeouts.

Any non-2xx response raises a subclass of `ApiError` exposing `.status_code` and `.body`.
Per-request behaviour is tuned via `request_options`; client-wide timeout via `timeout=`.
"""

import os

from onepin import OnePinClient
from onepin.core.api_error import ApiError


def main() -> None:
client = OnePinClient(token=os.environ["ONEPIN_API_KEY"], timeout=20.0)

try:
client.workflows.get(
workflow_id="wf_does_not_exist",
request_options={"max_retries": 1, "timeout_in_seconds": 5},
)
except ApiError as error:
print("API error:", error.status_code)
print("body:", error.body)


if __name__ == "__main__":
main()
23 changes: 23 additions & 0 deletions examples/list_workflows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Paginate workflows and fetch a single one by id."""

import os

from onepin import OnePinClient


def main() -> None:
client = OnePinClient(token=os.environ["ONEPIN_API_KEY"])

# `list()` returns a SyncPager — iterate items directly, or use `.iter_pages()`.
for index, workflow in enumerate(client.workflows.list()):
print(workflow)
if index >= 4:
break

# Fetch one workflow by id:
# detail = client.workflows.get(workflow_id="wf_...")
# print(detail)


if __name__ == "__main__":
main()
24 changes: 24 additions & 0 deletions examples/provider_keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Bring-your-own provider keys (e.g. ElevenLabs).

Provider keys let the platform call third-party providers with *your* credentials.
"""

import os

from onepin import OnePinClient


def main() -> None:
client = OnePinClient(token=os.environ["ONEPIN_API_KEY"])

print("provider keys:", client.provider_keys.list_provider_keys())

# Store or replace a provider key (uncomment and supply a real key):
# client.provider_keys.put_provider_key(
# provider="elevenlabs",
# request={"key": os.environ["ELEVENLABS_API_KEY"]},
# )


if __name__ == "__main__":
main()
27 changes: 27 additions & 0 deletions examples/quickstart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Quickstart: construct a client and make a few read calls.

Run:
pip install onepin
export ONEPIN_API_KEY="op_..."
python examples/quickstart.py
"""

import os

from onepin import OnePinClient


def main() -> None:
client = OnePinClient(token=os.environ["ONEPIN_API_KEY"])

# Health probe — a cheap first call to confirm connectivity.
print("readiness:", client.health.readiness())

# Workflows in your workspace. `list()` returns a pager you can iterate directly.
for workflow in client.workflows.list():
print("workflow:", workflow)
break # just the first


if __name__ == "__main__":
main()
Loading