Metadata-Version: 2.4
Name: barbara-api-sdk
Version: 0.2.1
Summary: Official Python SDK for the Barbara Edge AI platform API
Project-URL: Homepage, https://github.com/Barbaraedge/barbara-api-sdk-python
Project-URL: Documentation, https://barbaraedge.github.io/barbara-api-sdk-python/
Project-URL: Issues, https://github.com/Barbaraedge/barbara-api-sdk-python/issues
Project-URL: Changelog, https://github.com/Barbaraedge/barbara-api-sdk-python/blob/main/CHANGELOG.md
Author-email: Barbara <support@barbara.tech>
License-Expression: MIT
License-File: LICENSE
Keywords: api,barbara,edge-ai,iot,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
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: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: bandit>=1.7; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pip-audit>=2.7; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs>=1.6; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
Requires-Dist: ruff>=0.6; extra == 'docs'
Description-Content-Type: text/markdown

<div align="center">

# 🛰️ Barbara API SDK for Python

**Official Python SDK for the [Barbara](https://barbara.tech) Edge AI platform API.**

Typed, synchronous and asynchronous clients for managing nodes, clusters, applications, models, and related resources.

📖 **[Full documentation](https://barbaraedge.github.io/barbara-api-sdk-python/)**

[![CI](https://github.com/Barbaraedge/barbara-api-sdk-python/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Barbaraedge/barbara-api-sdk-python/actions/workflows/ci.yml)
[![Security](https://github.com/Barbaraedge/barbara-api-sdk-python/actions/workflows/security.yml/badge.svg?branch=main)](https://github.com/Barbaraedge/barbara-api-sdk-python/actions/workflows/security.yml)
[![Docs](https://github.com/Barbaraedge/barbara-api-sdk-python/actions/workflows/docs.yml/badge.svg?branch=main)](https://barbaraedge.github.io/barbara-api-sdk-python/)
[![PyPI version](https://img.shields.io/pypi/v/barbara-api-sdk?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/barbara-api-sdk/)
[![Python versions](https://img.shields.io/pypi/pyversions/barbara-api-sdk?logo=python&logoColor=white)](https://pypi.org/project/barbara-api-sdk/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
[![Typed](https://img.shields.io/badge/typing-typed-blue.svg)](https://peps.python.org/pep-0561/)

</div>

---

## ✅ Requirements

- Python **3.9** or later

## 📦 Installation

```bash
pip install barbara-api-sdk
```

## 🔐 Authentication

The SDK authenticates against the Barbara API using OAuth2 password grant. You need four credentials, referred to as **Barbara API Credentials**:

| Credential | Description |
|---|---|
| `BBR_API_USERNAME` | Your Barbara Panel username |
| `BBR_API_PASSWORD` | Your Barbara Panel password |
| `BBR_API_CLIENT_ID` | OAuth2 client ID, provided by Barbara |
| `BBR_API_CLIENT_SECRET` | OAuth2 client secret, provided by Barbara |

> **Note**
> Panel credentials can be created at [onboarding.barbara.tech](https://onboarding.barbara.tech). Client credentials are issued by [Barbara support](mailto:support@barbara.tech).

By default, the client reads credentials from environment variables:

```bash
export BBR_API_USERNAME="..."
export BBR_API_PASSWORD="..."
export BBR_API_CLIENT_ID="..."
export BBR_API_CLIENT_SECRET="..."
```

```python
from barbara import BarbaraClient

client = BarbaraClient.from_env()
```

Additional optional environment variables:

| Variable | Description | Default |
|---|---|---|
| `BBR_API_URL` | Barbara API base URL | `https://prod.bap.barbara.tech` |
| `BBR_AUTH_URL` | Barbara auth server base URL | `https://prod.auth.barbara.tech/auth` |
| `BBR_REALM` | Authentication realm | `bbr_prod` |

Credentials can also be supplied explicitly instead of through environment variables:

```python
from barbara import BarbaraClient, BarbaraConfig

config = BarbaraConfig(
    client_id="...",
    client_secret="...",
    username="...",
    password="...",
)
client = BarbaraClient(config)
```

## 🚀 Quick start

```python
from barbara import BarbaraClient

with BarbaraClient.from_env() as client:
    for node in client.nodes.list():
        print(node.node_name, node.status)

    node = client.nodes.resolve("my-node-01")
```

### Async usage

`AsyncBarbaraClient` mirrors `BarbaraClient` method for method — only `await` differs.

```python
import asyncio
from barbara import AsyncBarbaraClient

async def main():
    async with AsyncBarbaraClient.from_env() as client:
        nodes = await client.nodes.list()

asyncio.run(main())
```

## 📚 Usage

### Nodes

See [Node management](https://academy.barbara.tech/platform/node-lifecycle/node-management/) in Academy.

```python
nodes = client.nodes.list(search="sensor")
node = client.nodes.get("<node-id>")
node = client.nodes.resolve("my-node-01")  # look up by node name

client.nodes.reboot("<node-id>")
client.nodes.poweroff("<node-id>")
```

#### Node secrets

See [Secrets](https://academy.barbara.tech/platform/workload-config/secrets/) in Academy.

```python
client.nodes.create_secrets("<node-id>", {"wifi-psk": "s3cr3t"})
secrets = client.nodes.list_secrets("<node-id>")
client.nodes.delete_secret("<node-id>", "<secret-id>")
```

#### Node app configuration

Corresponds to the Panel's [Global Config](https://academy.barbara.tech/platform/workload-config/global-config/) (node-scoped; see [Application configuration types](https://academy.barbara.tech/platform/workload-config/app-config-types/) for the full picture).

```python
client.nodes.set_appconfig("<node-id>", config={"threshold": 5})
config = client.nodes.get_appconfig("<node-id>")
```

#### Docker credentials

```python
client.nodes.create_docker_credentials(
    "<node-id>", [{"user": "bob", "password": "s3cr3t", "server": "docker.io"}]
)
```

#### Node identity: name, tags, location, safety config

See [General info](https://academy.barbara.tech/platform/node-lifecycle/general-info/) in Academy.

```python
client.nodes.update_name("<node-id>", "floor-2-sensor-01")
client.nodes.add_tag("<node-id>", "production")

client.nodes.set_location("<node-id>", lat=40.4168, lng=-3.7038, city="Madrid")
location = client.nodes.get_location("<node-id>")

client.nodes.update_safety_config(
    "<node-id>", trigger_threshold=90, stop_apps=True, prune_volumes=True
)
```

> **Note**
> `get_location` is returned as a raw dict rather than a typed object, since node location payloads vary in shape.

> **Warning**
> `set_location` currently returns a `500 Internal Server Error` regardless of the payload sent. Use the Panel to update a node's location until this is resolved.

#### OTA (Barbara Core version updates)

See [Firmware updates](https://academy.barbara.tech/platform/node-lifecycle/barbara-core-updates/) in Academy.

```python
client.nodes.send_ota_update("<node-id>", "update")
client.nodes.send_ota_update(
    "<node-id>", "schedule", schedule_timestamp="2026-01-01T03:00:00Z"
)
client.nodes.cancel_ota_update("<node-id>")
```

#### Docker maintenance and volumes

```python
client.nodes.prune_docker("<node-id>", "prunevolumes")
client.nodes.prune_docker_all("<node-id>")
client.nodes.restart_docker_daemon("<node-id>")

client.nodes.create_docker_volume("<node-id>", "shared-cache")

volumes = client.nodes.list_docker_volumes("<node-id>")
client.nodes.delete_docker_volume("<node-id>", volumes[0]["_id"])
```

See [Volumes](https://academy.barbara.tech/platform/workload-config/volumes/) in Academy.

> **Note**
> `create_docker_volume` doesn't return an id. Use `list_docker_volumes` to look one up before calling `delete_docker_volume`.

#### Telemetry

See [Telemetry](https://academy.barbara.tech/platform/node-lifecycle/telemetry/) in Academy.

```python
latency = client.nodes.get_telemetry_latency("<node-id>")
client.nodes.set_telemetry_latency("<node-id>", 30)

telemetry = client.nodes.get_last_telemetry("<node-id>")
print(telemetry["disk"], telemetry["alive"])
```

### Node workloads

Deploy and manage applications running on a node. See [Docker apps](https://academy.barbara.tech/platform/apps-and-models/docker-apps/) and [Marketplace apps](https://academy.barbara.tech/platform/apps-and-models/marketplace-apps/) in Academy.

```python
client.nodes.workloads.create_user_workload(
    "<node-id>",
    app_version_id="<app-version-id>",
    application_id="<application-id>",
)

client.nodes.workloads.create_market_workload(
    "<node-id>",
    app_version_id="<app-version-id>",
    application_id="<application-id>",
    name="my-workload",
    services=[{"name": "modelservice", "ports": {"PORT_NUMBER": "9083"}}],
)

client.nodes.workloads.start("<node-id>", "<workload-id>")
client.nodes.workloads.stop("<node-id>", "<workload-id>")
logs = client.nodes.workloads.get_logs("<node-id>", "<workload-id>")
```

> **Note**
> Creation and update calls do not return the resulting workload state. Call `client.nodes.workloads.get(...)` afterwards if you need it.

#### Model workloads

Same body shape as market workloads, deploying a model application version instead — `services` must exactly match the service template declared by that model version. See [Models](https://academy.barbara.tech/platform/apps-and-models/models/) in Academy.

```python
client.nodes.workloads.create_model_workload(
    "<node-id>",
    app_version_id="<model-app-version-id>",
    application_id="<model-application-id>",
    name="my-model-workload",
    services=[{"name": "modelservice", "ports": {"PORT_NUMBER": "8501"}}],
)
```

### Clusters

See [Clusters](https://academy.barbara.tech/platform/high-availability/clusters/) in Academy.

```python
clusters = client.clusters.list()
cluster = client.clusters.get("<cluster-id>")
client.clusters.update("<cluster-id>", "new-name")

client.clusters.create_secrets("<cluster-id>", {"db-password": "s3cr3t"})
client.clusters.set_appconfig("<cluster-id>", config={"threshold": 5})
```

#### Creating a cluster and managing membership

```python
client.clusters.create(
    "floor-2-cluster",
    primary_node={
        "nodeId": "<node-id>",
        "labels": "eyJ6b25lIjogImZsb29yLTIifQ==",  # base64 JSON: {"zone": "floor-2"}
        "restrictSwarmTrafficToInterface": False,
        "advertiseAddr": "10.0.0.5",
    },
    enable_cluster_volumes=True,
)

client.clusters.join_node(
    "<cluster-id>",
    "<node-id>",
    labels={"zone": "floor-2"},
    restrict_swarm_traffic_to_interface=False,
    advertise_addr="10.0.0.6",
)

client.clusters.pause_node("<cluster-id>", "<node-id>")
client.clusters.drain_node("<cluster-id>", "<node-id>")
client.clusters.set_node_active("<cluster-id>", "<node-id>")
client.clusters.leave_node("<cluster-id>", "<node-id>")
```

> **Note**
> `primary_node` and `secondary_nodes` take the cluster networking configuration as dicts matching the API schema.

### Cluster stacks

The cluster-level equivalent of node workloads — deploy an application across every node in a cluster. See [Add applications](https://academy.barbara.tech/platform/high-availability/add-applications/) in Academy.

```python
client.clusters.stacks.create_user_stack(
    "<cluster-id>",
    app_version_id="<app-version-id>",
    application_id="<application-id>",
)

client.clusters.stacks.delete("<cluster-id>", "<stack-id>")
```

Model stacks work the same way, using `create_model_stack`:

```python
client.clusters.stacks.create_model_stack(
    "<cluster-id>",
    app_version_id="<model-app-version-id>",
    application_id="<model-application-id>",
    name="my-model-stack",
    services=[{"name": "modelservice", "ports": {"PORT_NUMBER": "8501"}}],
)
```

### Applications

See [App Library](https://academy.barbara.tech/platform/apps-and-models/app-library/) in Academy.

```python
apps = client.applications.list()

client.applications.create(
    "edge-app", "Long description", "Barbara", docker=True, icon_path="./icon.png"
)

client.applications.create_version(
    "<application-id>", "./app-v1.tar", "1.0.0", ["amd64"], ["Initial release"]
)
```

> **Tip**
> `create` and `create_version` upload files (icon, installable artifact) as `multipart/form-data`. Pass a local file path — the SDK reads the file and builds the request for you.

### Models

See [Models](https://academy.barbara.tech/platform/apps-and-models/models/) in Academy.

```python
models = client.models.list()

client.models.create(
    "anomaly-detector", "Long description", "Barbara", model_type=0, engine=0
)

client.models.create_version("<model-id>", "./model.onnx", "1.0.0", ["Initial release"])
```

> **Tip**
> `sha256` and `size` for a model version are computed automatically from the artifact — you don't need to pass them yourself.

### App configurations

Reusable, named application configurations that can be referenced by ID elsewhere in the API. See [Application configuration types](https://academy.barbara.tech/platform/workload-config/app-config-types/) in Academy.

```python
app_config = client.appconfig.create(
    name="sensor-thresholds",
    description="Per-node alert thresholds",
    config={"temperature_max": 80},
)
```

### Groups

See [Nodes list](https://academy.barbara.tech/platform/node-lifecycle/nodes-list/) in Academy for group management in the Panel.

```python
group = client.groups.create(
    name="floor-2-sensors",
    description="All floor 2 nodes",
    node_ids=["<node-id-1>", "<node-id-2>"],
)
```

### Users

See [Organization](https://academy.barbara.tech/platform/users/organization/) in Academy.

```python
users = client.users.list()
page = client.users.paginate(offset=0, size=50)
```

### Alerts

See the [Alert Manager](https://academy.barbara.tech/developers/alert-manager/) app in Academy.

```python
alerts = client.alerts.list()
client.alerts.ack("<alert-id>")
events = client.alerts.list_events(node_id="<node-id>")
```

## ⚠️ Error handling

All API errors raise a subclass of `BarbaraApiError`:

```python
from barbara import BarbaraApiError, BarbaraAuthError, BarbaraNotFoundError, BarbaraPermissionError

try:
    client.nodes.resolve("unknown-node")
except BarbaraNotFoundError:
    ...
except BarbaraPermissionError:
    ...
except BarbaraApiError as e:
    print(e.status, e.body)
```

## 🧱 Architecture

- **One client, one resource tree.** `BarbaraClient` and `AsyncBarbaraClient` expose the same resources (`.nodes`, `.clusters`, `.applications`, ...) with identical method signatures.
- **Automatic token refresh.** A request that receives a `401` is retried once with a freshly fetched token.
- **Typed models.** Response entities are plain dataclasses. Every entity keeps the original API payload in `.raw`.
- **Typed exceptions.** `BarbaraNotFoundError`, `BarbaraAuthError`, and `BarbaraPermissionError` subclass `BarbaraApiError` so callers can handle specific failure modes.
- **An escape hatch for everything else.** Every resource method calls `client.request(method, path, ...)` internally — the same authenticated, token-refreshing request method is available directly for any endpoint not yet wrapped by a typed resource. See the [examples](#-examples). When building `path` yourself, percent-encode any value that isn't a fixed literal (`urllib.parse.quote(value, safe="")`) — every typed resource method does this for its own id parameters, but `client.request(...)` takes `path` as-is.

## 📖 API reference

| Resource | Description |
|---|---|
| `client.nodes` | Node lifecycle, secrets, app configuration, docker credentials, and actions (reboot, provision, ...) |
| `client.nodes.workloads` | Applications deployed on a node |
| `client.clusters` | Cluster lifecycle, secrets, app configuration, and docker credentials |
| `client.clusters.stacks` | Applications deployed across a cluster |
| `client.applications` | Application catalog and versions |
| `client.models` | Model catalog and versions |
| `client.appconfig` | Reusable application configurations |
| `client.groups` | Node groups |
| `client.users` | Company users (read-only) |
| `client.alerts` | Alerts and alert events |

Full generated API reference (every method, parameter, and return type): **[barbaraedge.github.io/barbara-api-sdk-python](https://barbaraedge.github.io/barbara-api-sdk-python/)**. For the underlying HTTP API itself, see the [Barbara API documentation](https://prod.bap.barbara.tech/documentation/).

## 🧪 Examples

The [`examples/`](examples/) directory has complete, runnable scripts for common use cases:

| Script | Description |
|---|---|
| [`quickstart.py`](examples/quickstart.py) | List nodes and look one up by name |
| [`node_info.py`](examples/node_info.py) | Read a node's configuration and latest telemetry |
| [`check_firmware_updates.py`](examples/check_firmware_updates.py) | Check nodes for outdated firmware, optionally update them |
| [`clone_node.py`](examples/clone_node.py) | Clone a node's workloads and app configuration onto another node |

Each script also demonstrates calling an endpoint through `client.request(...)` directly — the same low-level method every typed resource is built on — for functionality this SDK doesn't wrap yet.

See **[`examples/README.md`](examples/README.md)** for what each one covers, how to configure and run it, and ideas for extending it.

## 🗺️ Roadmap

The following areas of the Barbara API are **not yet covered** by this SDK:

- Node network configuration (interfaces, NTP, proxy, VPN, iptables)
- Node standalone mode and VPN peer management

## 📄 License

Distributed under the **MIT License**. See [`LICENSE`](LICENSE) for details.
