From 4bafce79cd535f8021400e89fec56a13dbcc3f49 Mon Sep 17 00:00:00 2001 From: kj-podonos Date: Wed, 17 Jun 2026 20:05:09 +0900 Subject: [PATCH] docs: add examples/, README badges + SDK usage, SUPPORT, editorconfig, pre-commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Developer-facing polish ahead of the public flip (pure additions, no behavior change): - examples/ — 5 runnable snippets verified against reference.md - README — PyPI/pyversions/CI/License badges; deepened SDK usage; fixed stale v0.2.0 -> v0.6.0 - SUPPORT.md, .editorconfig, .pre-commit-config.yaml Co-Authored-By: Claude Opus 4.8 (1M context) --- .editorconfig | 18 +++++++++++ .pre-commit-config.yaml | 20 ++++++++++++ README.md | 62 +++++++++++++++++++++++++++++++++++--- SUPPORT.md | 10 ++++++ examples/README.md | 23 ++++++++++++++ examples/async_client.py | 20 ++++++++++++ examples/error_handling.py | 27 +++++++++++++++++ examples/list_workflows.py | 23 ++++++++++++++ examples/provider_keys.py | 24 +++++++++++++++ examples/quickstart.py | 27 +++++++++++++++++ 10 files changed, 249 insertions(+), 5 deletions(-) create mode 100644 .editorconfig create mode 100644 .pre-commit-config.yaml create mode 100644 SUPPORT.md create mode 100644 examples/README.md create mode 100644 examples/async_client.py create mode 100644 examples/error_handling.py create mode 100644 examples/list_workflows.py create mode 100644 examples/provider_keys.py create mode 100644 examples/quickstart.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7969809 --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b40b2b6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -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 diff --git a/README.md b/README.md index 2fc3477..ef221cb 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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). diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..6d3d375 --- /dev/null +++ b/SUPPORT.md @@ -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). diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..201517f --- /dev/null +++ b/examples/README.md @@ -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). diff --git a/examples/async_client.py b/examples/async_client.py new file mode 100644 index 0000000..ea7c002 --- /dev/null +++ b/examples/async_client.py @@ -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()) diff --git a/examples/error_handling.py b/examples/error_handling.py new file mode 100644 index 0000000..90fb4ac --- /dev/null +++ b/examples/error_handling.py @@ -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() diff --git a/examples/list_workflows.py b/examples/list_workflows.py new file mode 100644 index 0000000..41d4b9b --- /dev/null +++ b/examples/list_workflows.py @@ -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() diff --git a/examples/provider_keys.py b/examples/provider_keys.py new file mode 100644 index 0000000..90b685c --- /dev/null +++ b/examples/provider_keys.py @@ -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() diff --git a/examples/quickstart.py b/examples/quickstart.py new file mode 100644 index 0000000..21d7f22 --- /dev/null +++ b/examples/quickstart.py @@ -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()