> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dataerai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Upload, download, and manage Dataerai metadata from Python. Blocking methods that work well in scripts and notebooks.

The Python SDK is a client for the `dataerai` transfer daemon. Its methods are **blocking**, so they're easy to use in a script or a Jupyter notebook. The daemon starts automatically if it isn't already running.

## Requirements

* Python 3.10+
* The `dataerai` binary on your `PATH` (see the [CLI overview](/cli/overview)), or pass `binary_path` explicitly.
* Signed in with `dataerai auth login` (see [Authenticate](/cli/authenticate)).

## Install

```bash theme={null}
pip install dataerai-sdk
```

The SDK drives the local `dataerai` binary. The quickest way to get it is to install the CLI, which bundles the binary for your platform:

```bash theme={null}
pip install dataerai-cli        # provides the `dataerai` command + binary
```

<Note>
  Working with scientific instrument files (AFM, electron microscopy, RHEED, X-ray)? The same package ships an optional metadata extractor — install it with `pip install "dataerai-sdk[metaextract]"` and see [Metadata extraction](/metaextract/overview).
</Note>

## Quickstart

```python theme={null}
from dataerai import DataeraiClient

with DataeraiClient(binary_path="/usr/local/bin/dataerai") as client:
    # Who am I?
    status = client.auth_status()
    print(f"Logged in as {status.user_email}")

    # Upload a file
    result = client.upload(
        "/path/to/data.csv",
        title="My dataset",
        owner_type="project",
        owner_id="proj-abc123",
        tags=["csv", "demo"],
        on_progress=lambda p: print(f"  {p.percent:.0f}%  {p.rate_mbps:.1f} MB/s"),
    )
    print(f"Uploaded asset_id={result.asset_id}")

    # Download it back
    dl = client.download(result.asset_id, dest_dir="/tmp/downloads")
    for f in dl.files:
        print(f"  {f.local_path} ({f.size:,} bytes)")

    # Read and update metadata
    meta = client.get_metadata(result.asset_id)
    client.set_metadata(result.asset_id, title="My dataset v2", tags=["csv", "demo"])

    # Link this derived asset back to a source asset
    relationship = client.create_relationship(
        result.asset_id,
        "source-asset-id",
        relationship_type="derived_from",
        analysis_mode="non_destructive",
        qualifiers={"tool": "pycroscopy"},
    )
    related_id = relationship.related_asset["id"] if relationship.related_asset else "source-asset-id"
    print(f"Linked through {relationship.type} to {related_id}")
```

Use the client as a context manager (`with ... as client:`) for automatic cleanup, or call `client.connect()` and `client.close()` yourself.

## Methods

| Method                                                                                           | Returns          | Description                                                                |
| ------------------------------------------------------------------------------------------------ | ---------------- | -------------------------------------------------------------------------- |
| `auth_status()`                                                                                  | `AuthStatus`     | Logged-in user and token expiry.                                           |
| `upload(local_path, *, title, owner_type, owner_id, ...)`                                        | `UploadResult`   | Upload a file; blocks until complete.                                      |
| `download(asset_id, dest_dir, ...)`                                                              | `DownloadResult` | Download the latest content; blocks until complete. Resumes automatically. |
| `get_metadata(asset_id)`                                                                         | `AssetMetadata`  | Retrieve metadata.                                                         |
| `set_metadata(asset_id, **fields)`                                                               | `AssetMetadata`  | Update metadata fields.                                                    |
| `create_relationship(from_asset_id, to_asset_id, rel_type=None, *, relationship_type=None, ...)` | `Relationship`   | Create a directed provenance link between two assets.                      |

### `upload()` arguments

`title`, `owner_type` (`"project"` or `"user"`), and `owner_id` are required. Optional: `description`, `alias`, `tags`, `metadata` (dict), `collection_id`, `chunk_size_mb`, `on_progress` (callback), and `transfer_timeout_s` (default 3600).

The `on_progress` callback receives a `ProgressEvent` with `bytes_done`, `bytes_total`, `file_name`, and convenience `percent` and `rate_mbps` properties.

### Provenance relationships

Link two assets to record how your data came to be — for example that an analysis was derived from a measurement, or that raw data was acquired with a configuration. See [Provenance & relationships](/organize/provenance) for the model.

```python theme={null}
# Link a processed result to the raw data it came from.
client.create_relationship(
    analysis.asset_id, raw.asset_id, "analysis_of",
    analysis_mode="non_destructive",
)
```

`create_relationship(from_asset_id, to_asset_id, rel_type, *, ...)` creates a directed edge **from** the derived asset **to** its origin; `rel_type` is a free-form verb describing the source's role (for example `"analysis_of"` or `"acquired_with"`). You need write access to the source and read access to the target. Optional keyword arguments: `analysis_mode` (one of `non_destructive`, `altering`, `destructive`, `in_situ`, `ex_situ`, `invasive`, `non_invasive`), `qualifier_note`, `qualifier_time`, and `qualifiers` (dict). It returns a `Relationship` and raises `DaemonError` with `ERR_RELATIONSHIP_EXISTS` if an identical link already exists.

For notebook integrations that prefer named relationship fields, pass the relationship verb with the keyword-only alias `relationship_type`, for example `relationship_type="derived_from"`.

<Note>
  Building an instrument-control workflow? The [QICK integration](/integrations/qick) uses `create_relationship` to capture experiment provenance automatically.
</Note>

## Errors

| Exception                    | When                                        |
| ---------------------------- | ------------------------------------------- |
| `DaemonError(code, message)` | The daemon returned a coded error.          |
| `DaemonTimeoutError`         | A request or transfer exceeded its timeout. |
| `ConnectionError`            | The daemon disconnected unexpectedly.       |

## Next steps

<CardGroup cols={2}>
  <Card title="CLI" icon="terminal" href="/cli/overview">
    The command-line client the SDK builds on.
  </Card>

  <Card title="Node SDK" icon="hexagon" href="/sdks/nodejs">
    The same operations from Node.js.
  </Card>

  <Card title="QICK integration" icon="atom" href="/integrations/qick">
    Capture quantum-experiment provenance automatically.
  </Card>

  <Card title="Provenance" icon="share-2" href="/organize/provenance">
    How relationships model your data's lineage.
  </Card>
</CardGroup>
