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
2 changes: 1 addition & 1 deletion .spec-sha
Original file line number Diff line number Diff line change
@@ -1 +1 @@
33699d2e8a7c89bc778445e9ea3883ee985b20ad
6e8f07d1484ca4c4b976609f61c1d88b8288b3a3
5 changes: 2 additions & 3 deletions src/onepin/.fern/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
}
]
},
"originGitCommit": "c4e8a48ae3bc09abd9ae19460bf56c49a1fe71c6",
"originGitCommit": "3d712ffd87d0eab81a87aa8c0e603081a11ee5f1",
"originGitCommitIsDirty": true,
"invokedBy": "ci",
"ciProvider": "unknown"
"invokedBy": "manual"
}
30 changes: 27 additions & 3 deletions src/onepin/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# OnePin Python Library
# Onepin Python Library

[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=OnePin%2FPython)
[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Onepin%2FPython)
[![pypi](https://img.shields.io/pypi/v/onepin)](https://pypi.python.org/pypi/onepin)

The OnePin Python library provides convenient access to the OnePin APIs from Python.
The Onepin Python library provides convenient access to the Onepin APIs from Python.

## Table of Contents

Expand All @@ -13,6 +13,7 @@ The OnePin Python library provides convenient access to the OnePin APIs from Pyt
- [Environments](#environments)
- [Async Client](#async-client)
- [Exception Handling](#exception-handling)
- [Pagination](#pagination)
- [Advanced](#advanced)
- [Access Raw Response Data](#access-raw-response-data)
- [Retries](#retries)
Expand Down Expand Up @@ -101,6 +102,29 @@ except ApiError as e:
print(e.body)
```

## Pagination

Paginated requests will return a `SyncPager` or `AsyncPager`, which can be used as generators for the underlying object.

```python
from onepin import OnePinClient

client = OnePinClient(
token="<token>",
)

client.templates.list()
```

```python
# You can also iterate through pages and access the typed response per page
pager = client.templates.list(...)
for page in pager.iter_pages():
print(page.response) # access the typed response for each page
for item in page:
print(item)
```

## Advanced

### Access Raw Response Data
Expand Down
14 changes: 8 additions & 6 deletions src/onepin/_cli/_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,11 +320,12 @@ def _emit(cmd: Cmd, resp: Any, bound: dict[str, Any], json_on: bool, *, limit: i


def _emit_pager(cmd: Cmd, pager: Any, json_on: bool, *, limit: int, offset: int = 0) -> int:
# The SDK returns a SyncPager when generated with pagination enabled, and a
# list-envelope model (items under .data) otherwise. Iterating a pydantic
# envelope directly would yield (field, value) tuples — unwrap .data first.
# SyncPager has no .data attribute, so this is a no-op for real pagers.
items = list(islice(iter(getattr(pager, "data", pager)), limit))
# A generated SyncPager iterates across every API page. The CLI exposes explicit
# --offset paging, so render only the current page's .items; otherwise a single
# command silently fans out into repeated requests. Older SDK envelopes keep rows
# under .data, and iterating the envelope itself would yield (field, value) tuples.
source = (pager.items or []) if hasattr(pager, "items") else getattr(pager, "data", pager)
items = list(islice(iter(source), limit))
rows = [to_jsonable(item) for item in items]
if json_on:
render_json(rows)
Expand Down Expand Up @@ -354,7 +355,8 @@ def _echo_pager_footer(pager: Any, shown: int, *, limit: int, offset: int = 0) -
Human output only. The ``--json`` payload stays a bare array because that shape is the
agent contract pinned by the manifest snapshot; agents read the count by paging.
"""
total = getattr(getattr(pager, "pagination", None), "total", None)
envelope = getattr(pager, "response", pager)
total = getattr(getattr(envelope, "pagination", None), "total", None)
if isinstance(total, int):
remaining = max(total - (offset + shown), 0)
more = f" {remaining} more — page with --offset." if remaining > 0 else ""
Expand Down
22 changes: 8 additions & 14 deletions src/onepin/_cli/commands/composites.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ def workflow_run(
with api_errors(json_on):
client = get_client()
kwargs = _maybe_workspace(client.workflows.runs.start)
overrides = _run_scoped_body(script, source_language)
if overrides is not None:
kwargs["request_options"] = overrides
kwargs.update(_run_scoped_kwargs(script, source_language))
started = client.workflows.runs.start(workflow_id, **kwargs)
run = to_jsonable(getattr(started, "data", started))
run_id = run.get("id") if isinstance(run, dict) else None
Expand Down Expand Up @@ -142,9 +140,7 @@ def workflow_preview_run(
with api_errors(json_on):
client = get_client()
kwargs = _maybe_workspace(client.workflows.preview_run)
overrides = _run_scoped_body(script, source_language)
if overrides is not None:
kwargs["request_options"] = overrides
kwargs.update(_run_scoped_kwargs(script, source_language))
resp = client.workflows.preview_run(workflow_id, **kwargs)
payload = to_jsonable(getattr(resp, "data", resp))
if json_on:
Expand Down Expand Up @@ -870,24 +866,22 @@ def definition_schema(
# === Shared helpers ======================================================================


def _run_scoped_body(script: Optional[str], source_language: Optional[str]) -> Optional[dict[str, Any]]:
"""Build ``request_options`` carrying the run-scoped overrides, or ``None`` for neither.
def _run_scoped_kwargs(script: Optional[str], source_language: Optional[str]) -> dict[str, str]:
"""Typed run-scoped overrides for ``runs.start`` / ``preview_run``, omitting what wasn't passed.

``workflows run`` and ``workflows preview-run`` share this so an estimate prices the exact
body the run will send — two builders could drift, and a cost quoted from a different body
than the one charged is precisely the failure these overrides exist to prevent.

Sent as additional body parameters rather than a ``WorkflowRunStartIn``: the generated model
defaults both fields to ``None``, so Fern serializes the unset one as an explicit
``"source_language": null`` instead of omitting it. Building the dict keeps only the keys the
user actually passed, which is the wire format this endpoint has always been called with.
The SDK now accepts ``script_text`` / ``source_language`` as ``OMIT``-defaulted keyword args,
so passing only the keys the user supplied yields the same wire body without the explicit
``"source_language": null`` the old wrapped-model path would have serialized.
"""
overrides = {
return {
key: value
for key, value in (("script_text", script), ("source_language", source_language))
if value is not None
}
return {"additional_body_parameters": overrides} if overrides else None


def _maybe_workspace(method: Any) -> dict[str, Any]:
Expand Down
110 changes: 94 additions & 16 deletions src/onepin/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -4651,16 +4651,16 @@ client.workspaces.get_workspace(
<dl>
<dd>

**workspace_id:** `str`
**workspace_id:** `str`

</dd>
</dl>

<dl>
<dd>

**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.

</dd>
</dl>
</dd>
Expand Down Expand Up @@ -5033,6 +5033,88 @@ client.workspaces.get_workspace_subscription(
</dl>


</dd>
</dl>
</details>

<details><summary><code>client.workspaces.<a href="src/onepin/workspaces/client.py">get_workspace_credits</a>(...) -> ApiResponseBalanceResponse</code></summary>
<dl>
<dd>

#### 📝 Description

<dl>
<dd>

<dl>
<dd>

Return the credit balance governing this workspace — the source the Usage/credit card reads.

Same shape as ``/users/me/credits``. For an **organization** workspace this is the organization's
shared credit pool — the balance a member's runs debit; for a **personal** workspace it is the
workspace owner's balance. Readable by any workspace member (the sensitive billing menu — payment
methods and subscription lifecycle — is gated separately). ``balance``/``remaining`` are the
spendable pool, ``used`` is consumption in the current billing period, ``plan_grant`` is the plan's
monthly allowance, and ``period_end`` is the next expected reset boundary (or null when none is
promised). For an annual subscriber this GET may perform idempotent period maintenance before
returning; retries remain safe. Membership is existence-hidden (404); the org-admin fallback applies.
</dd>
</dl>
</dd>
</dl>

#### 🔌 Usage

<dl>
<dd>

<dl>
<dd>

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

client = OnePinClient(
token="<token>",
environment=OnePinClientEnvironment.PROD,
)

client.workspaces.get_workspace_credits(
workspace_id="workspace_id",
)

```
</dd>
</dl>
</dd>
</dl>

#### ⚙️ Parameters

<dl>
<dd>

<dl>
<dd>

**workspace_id:** `str`

</dd>
</dl>

<dl>
<dd>

**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.

</dd>
</dl>
</dd>
</dl>


</dd>
</dl>
</details>
Expand Down Expand Up @@ -7289,7 +7371,7 @@ lifecycle.
<dd>

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

client = OnePinClient(
Expand All @@ -7299,7 +7381,6 @@ client = OnePinClient(

client.workflows.estimate_workflow(
workflow_id="workflow_id",
request=WorkflowRunStartIn(),
)

```
Expand All @@ -7324,15 +7405,15 @@ client.workflows.estimate_workflow(
<dl>
<dd>

**workspace_id:** `typing.Optional[str]`
**request:** `WorkflowRunStartIn`

</dd>
</dl>

<dl>
<dd>

**request:** `typing.Optional[WorkflowRunStartIn]`
**workspace_id:** `typing.Optional[str]`

</dd>
</dl>
Expand Down Expand Up @@ -7388,7 +7469,7 @@ confirm the expected cost. Equivalent to `POST /estimate`.
<dd>

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

client = OnePinClient(
Expand All @@ -7398,7 +7479,6 @@ client = OnePinClient(

client.workflows.preview_run(
workflow_id="workflow_id",
request=WorkflowRunStartIn(),
)

```
Expand All @@ -7423,15 +7503,15 @@ client.workflows.preview_run(
<dl>
<dd>

**workspace_id:** `typing.Optional[str]`
**request:** `WorkflowRunStartIn`

</dd>
</dl>

<dl>
<dd>

**request:** `typing.Optional[WorkflowRunStartIn]`
**workspace_id:** `typing.Optional[str]`

</dd>
</dl>
Expand Down Expand Up @@ -8951,7 +9031,7 @@ Requires at least `editor` role in the workspace; viewers cannot run workflows.
<dd>

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

client = OnePinClient(
Expand All @@ -8961,7 +9041,6 @@ client = OnePinClient(

client.workflows.runs.start(
workflow_id="workflow_id",
request=WorkflowRunStartIn(),
)

```
Expand All @@ -8986,15 +9065,15 @@ client.workflows.runs.start(
<dl>
<dd>

**workspace_id:** `typing.Optional[str]`
**request:** `WorkflowRunStartIn`

</dd>
</dl>

<dl>
<dd>

**request:** `typing.Optional[WorkflowRunStartIn]`
**workspace_id:** `typing.Optional[str]`

</dd>
</dl>
Expand Down Expand Up @@ -9456,4 +9535,3 @@ client.workflows.runs.cancel(
</dd>
</dl>
</details>

Loading
Loading