Metadata-Version: 2.4
Name: thrustlab
Version: 0.1.1
Summary: Official Python SDK for the ThrustLab API
Project-URL: Homepage, https://thrustlab.com
Project-URL: Documentation, https://thrustlab.com/docs
Project-URL: Repository, https://github.com/<owner>/thrustlab
Project-URL: Changelog, https://thrustlab.com/docs/changelog
Author-email: ThrustLab <support@thrustlab.com>
License: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
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
Requires-Python: >=3.10
Requires-Dist: attrs>=23.0
Requires-Dist: httpx<1.0,>=0.27
Requires-Dist: pydantic<3.0,>=2.7
Requires-Dist: python-dateutil>=2.8
Provides-Extra: dev
Requires-Dist: openapi-python-client>=0.21; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# thrustlab — official Python SDK for the ThrustLab API

[![PyPI](https://img.shields.io/pypi/v/thrustlab.svg)](https://pypi.org/project/thrustlab/)
[![Python](https://img.shields.io/pypi/pyversions/thrustlab.svg)](https://pypi.org/project/thrustlab/)
[![License](https://img.shields.io/pypi/l/thrustlab.svg)](https://github.com/kylebedrich/thrustlab/blob/master/backend/sdks/python/LICENSE)

## Install

```bash
pip install thrustlab
```

Requires Python 3.10+.

## Quickstart

```python
from thrustlab import Client

client = Client(api_key="key_...")  # or set $THRUSTLAB_API_KEY

# Create a project
project = client.projects.create(name="my project")

# Run a simulation and wait for the result
sim = client.simulations.create(
    project_id=project["id"],
    motor="comp_motor_xxx",
    propeller="comp_prop_xxx",
    battery="comp_batt_xxx",
    throttle=0.7,
)
result = client.simulations.wait(sim["id"], timeout=300)
print(result)
```

## Configuration

| Setting      | Constructor arg  | Env var               | Default                  |
|--------------|------------------|-----------------------|--------------------------|
| API key      | `api_key=`       | `THRUSTLAB_API_KEY`   | (required)               |
| Base URL     | `base_url=`      | `THRUSTLAB_BASE_URL`  | `https://thrustlab.com`  |
| Timeout      | `timeout=`       | —                     | `30` (seconds)           |
| Max retries  | `max_retries=`   | —                     | `3`                      |

## Resources

Every `/v1/` route family is exposed as an attribute on the client:

```python
client.api_keys
client.users
client.projects
client.simulations
client.sweeps
client.components
client.submissions
client.starred_components
client.geometry
client.credits
client.subscriptions
client.webhook_endpoints
```

## Error handling

```python
from thrustlab import Client
from thrustlab.exceptions import (
    AuthenticationError,
    ValidationError,
    RateLimitError,
    NotFoundError,
)

client = Client()
try:
    client.simulations.create(project_id="proj_xxx", motor="comp_xxx", ...)
except ValidationError as exc:
    print(f"bad request: {exc.code} ({exc.param}): {exc.message}")
except RateLimitError as exc:
    print(f"rate limited; retry after {exc.retry_after}s")
except NotFoundError as exc:
    print(f"not found: {exc.message}")
except AuthenticationError as exc:
    print(f"auth failed: {exc.message}")
```

Every error carries `.code`, `.type`, `.request_id`, `.http_status`, and
`.message`. `ValidationError` additionally exposes `.param`. See
[thrustlab.com/docs/guides/errors](https://thrustlab.com/docs/guides/errors)
for the full code reference.

## Pagination

List endpoints return a `CursorPager` — a lazy iterator that fetches the
next page only when needed:

```python
# Iterate all pages automatically
for project in client.projects.list():
    print(project["id"])

# Materialise the first page only
first_page = list(client.projects.list(limit=20))
```

## Async polling

`simulations.wait()` and `sweeps.wait()` block until the resource reaches a
terminal state (`succeeded`, `failed`, or `cancelled`) or the timeout fires:

```python
sweep = client.sweeps.create(...)
result = client.sweeps.wait(sweep["id"], timeout=600, poll_interval=2.0)
if result["status"] == "succeeded":
    print(result["outputs"])
```

## Webhooks

```python
from thrustlab import Webhook
from thrustlab.exceptions import SignatureVerificationError

WEBHOOK_SECRET = "whsec_..."

@app.post("/webhooks/thrustlab")
async def handle(request):
    payload = await request.body()
    sig    = request.headers["Thrustlab-Signature"]
    try:
        event = Webhook.verify(payload, sig, WEBHOOK_SECRET)
    except SignatureVerificationError:
        return Response(status_code=400)
    if event.type == "simulation.succeeded":
        print(event.data)
```

## Retries

The client automatically retries on 429 (rate limit) and 5xx responses using
exponential backoff with jitter. Set `max_retries=0` to disable:

```python
client = Client(max_retries=0)
```

## Links

- Documentation: https://thrustlab.com/docs
- API reference: https://thrustlab.com/docs/reference
- SDK guide: https://thrustlab.com/docs/sdk/python
- Changelog: https://thrustlab.com/docs/changelog
- Issue tracker: https://github.com/kylebedrich/thrustlab/issues

## License

MIT
