Metadata-Version: 2.4
Name: paperclip-sdk
Version: 0.6.1
Summary: Public Python SDK for calling remote functions, with sync and async clients.
Keywords: leasekey,sdk,rpc,paperclip
Requires-Python: >=3.10,<4.0
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.14
Requires-Dist: httpx (>=0.27,<1.0)
Requires-Dist: load-dotenv (>=0.1.0,<0.2.0)
Requires-Dist: sentry-sdk (>=2.56.0,<3.0.0)
Project-URL: Homepage, https://leasekey.org
Description-Content-Type: text/markdown

# paperclip-sdk

Python SDK for calling server-hosted Paperclip tools from scripts, workers, web apps, and async services.

Use it when you want to keep business logic, pricing rules, private workflows, or customer-specific functionality on the server while calling it from Python through a small client.

## Install

```bash
poetry add paperclip-sdk
```

or

```bash
pip install paperclip-sdk
```

## Authentication

The SDK reads the API key from `LEASEKEY_API_KEY` by default:

```bash
export LEASEKEY_API_KEY="your-api-key"
```

The default base URL is `https://leasekey.org`. For self-hosted or local servers, pass a custom `base_url`.

## Quickstart

### Sync

```python
from paperclip_sdk import Paperclip

with Paperclip() as paperclip:
    result = paperclip.tools.core.hello_world(name="Ada")
    print(result)
```

### Async

```python
import asyncio

from paperclip_sdk import AsyncPaperclip


async def main() -> None:
    async with AsyncPaperclip() as paperclip:
        result = await paperclip.tools.core.hello_world(name="Ada")
        print(result)


asyncio.run(main())
```

## Tool access styles

The SDK supports three equivalent ways to call the same tool.

### Sync

```python
from paperclip_sdk import Paperclip

with Paperclip() as paperclip:
    print(paperclip.tools.core.hello_world(name="Ada"))
    print(paperclip.core.hello_world(name="Ada"))
    print(paperclip["core.hello_world"](name="Ada"))
```

### Async

```python
import asyncio

from paperclip_sdk import AsyncPaperclip


async def main() -> None:
    async with AsyncPaperclip() as paperclip:
        print(await paperclip.tools.core.hello_world(name="Ada"))
        print(await paperclip.core.hello_world(name="Ada"))
        print(await paperclip["core.hello_world"](name="Ada"))


asyncio.run(main())
```

## Use a specific server

### Sync

```python
from paperclip_sdk import Paperclip

with Paperclip(
    base_url="http://127.0.0.1:8889",
    api_key="your-api-key",
) as paperclip:
    result = paperclip.tools.core.hello_world(name="Ada")
    print(result)
```

### Async

```python
import asyncio

from paperclip_sdk import AsyncPaperclip


async def main() -> None:
    async with AsyncPaperclip(
        base_url="http://127.0.0.1:8889",
        api_key="your-api-key",
    ) as paperclip:
        result = await paperclip.tools.core.hello_world(name="Ada")
        print(result)


asyncio.run(main())
```

## Discover available tools

Use `paperclip.list_tools()` for the flat tool list or `paperclip.manifest()` for the full manifest payload.

### Sync

```python
from paperclip_sdk import Paperclip

with Paperclip() as paperclip:
    print(paperclip.list_tools())
```

### Async

```python
import asyncio

from paperclip_sdk import AsyncPaperclip


async def main() -> None:
    async with AsyncPaperclip() as paperclip:
        print(await paperclip.list_tools())


asyncio.run(main())
```

## Error handling

By default, the SDK uses `suppress_errors=True`.

That means failed calls can return `None` instead of raising immediately, and the captured failure is available in `last_error`.

### Sync

```python
from paperclip_sdk import Paperclip

with Paperclip(suppress_errors=True) as paperclip:
    result = paperclip.tools.core.hello_world(name="Ada")

    if result is None:
        print("Call failed:", paperclip.last_error)
```

### Async

```python
import asyncio

from paperclip_sdk import AsyncPaperclip


async def main() -> None:
    async with AsyncPaperclip(suppress_errors=True) as paperclip:
        result = await paperclip.tools.core.hello_world(name="Ada")

        if result is None:
            print("Call failed:", paperclip.last_error)


asyncio.run(main())
```

To raise failures immediately, disable suppressed errors:

### Sync

```python
from paperclip_sdk import Paperclip

with Paperclip(suppress_errors=False) as paperclip:
    result = paperclip.tools.core.hello_world(name="Ada")
    print(result)
```

### Async

```python
import asyncio

from paperclip_sdk import AsyncPaperclip


async def main() -> None:
    async with AsyncPaperclip(suppress_errors=False) as paperclip:
        result = await paperclip.tools.core.hello_world(name="Ada")
        print(result)


asyncio.run(main())
```

## Debug logging

```python
from paperclip_sdk import Paperclip

with Paperclip(
    base_url="http://127.0.0.1:8889",
    debug=True,
    log_server_meta=True,
) as paperclip:
    result = paperclip.tools.core.hello_world(name="Ada")
    print(result)
```

## Notes

- Tools can be accessed as `paperclip.tools.<namespace>.<name>(...)`
- Masked namespace access is also supported: `paperclip.<namespace>.<name>(...)`
- Flat concrete tool access is also supported: `paperclip["<namespace>.<name>"](...)`
- Registered tools can be discovered with `paperclip.list_tools()`
- The full manifest is available with `paperclip.manifest()`
- The default base URL is `https://leasekey.org`
- The SDK automatically sends `User-Agent: paperclip-sdk/<version>`
- The SDK also sends `X-Leasekey-SDK-Version: <version>`
- Built-in retry behavior covers retryable transport failures and these HTTP statuses: `408`, `425`, `429`, `502`, `503`, `504`

