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

# Blobs and Revisions

> The generic layer: blobs, revisions, their lifecycle, the data channel and operations

Everything here works the same for every blob type. The [typed modules](/sdk/onnx) are built on it.

## Blobs

```python theme={null}
import blobhub

hub = blobhub.connect()
blob = hub.blob("acme", "checkout")     # organization, then blob; each an alias or an id
blob.id, blob.alias, blob.type, blob.visibility, blob.default_revision_id
```

`hub.blob(org, blob)` sends one [`GET /blobs/{org}/{blob}`](/rest-api/blobs/get-blob) and returns a `Blob` holding
the record. From then on the blob is addressed by its canonical ids, so renaming the organization's or the blob's
alias does not break a `Blob` you hold.

* **A missing blob raises `PermissionDenied`**, not `NotFound`. The platform answers the same 403 for a blob that
  does not exist and for one your credential cannot see, so the two cannot be told apart.
* **The properties are a snapshot** of the last read: `id`, `alias`, `org_id`, `type`, `visibility`, `status`,
  `default_revision_id`, and the whole `record`. `blob.refresh()` reads it again.
* **`type` is the blob's type string**, such as `blobhub.model.onnx`. See [Blob Types](/blob-types/introduction).

## Revisions

```python theme={null}
revision = blob.revision()                      # the default revision
revision = blob.revision("<revision id>")       # a specific one
for r in blob.revisions():                      # every revision, newest first
    print(r.id, r.phase, r.status, r.comment)
```

* `blob.revision(ref="default")` reads one revision through
  [Resolve Revision](/rest-api/blobs/resolve-revision). `ref` is `default` or a revision id.
* `blob.revisions(page_size=50)` yields every revision, newest first. It follows the listing's cursor to the end and
  never stops early on a short page. See [Pagination](/rest-api/conventions#pagination).
* A `Revision` holds `id`, `status`, `phase`, `comment`, `parent_id`, `updated_at` and the whole `record`, and
  `revision.refresh()` reads it again.

| Field | Values |
| :- | :- |
| `phase` | `draft`, `commit` or `managed` |
| `status` | `creating`, `ready`, `committing`, `deleting` or `failed` |

A blob has one default revision and at most one draft. Writes go to the draft; a committed revision is read-only.
The lifecycle is under [Concepts → Revisions](/general/concepts#revisions-and-the-revision-lifecycle).

### Creating and committing

```python theme={null}
draft = blob.create_revision("retrain on new data")   # waits until it is ready
# ... write to the draft ...
draft.commit("v2")                                    # waits until it is committed
```

`blob.create_revision(comment, *, timeout=600.0)` sends [Create Revision](/rest-api/blobs/create-revision), then
polls until the new revision is `ready`. The draft starts with a copy of the default revision's data and becomes the
blob's default.

* The default revision must be committed first, or the call raises `Conflict` with `error`
  `default_revision_not_committed`. A blob with a draft as its default already has the one draft it may hold.
* A blob at its revisions limit raises `CommandError` with `error` `limit_exceeded`.

`revision.commit(comment=None, *, timeout=600.0)` sends [Commit Revision](/rest-api/revisions/commit-revision), then
polls until the revision is `ready` in phase `commit`. A `comment` replaces the revision's comment. A revision that
is not a `ready` draft raises `Conflict`, with `error` `revision_not_ready` or `revision_not_writable`.

Both calls wait. A revision that ends `failed` raises `OperationFailed` (`revision_failed`), and one still working
when `timeout` runs out raises `WaitTimeout`, leaving the work running on the platform.

## The data channel

Every blob type's commands go through two routes on a revision. The SDK sends them for you:

```python theme={null}
revision.query("describe")                                 # POST /revisions/{id}/data/query
revision.command("create_session", alias="nightly-runs")   # POST /revisions/{id}/data/command
body, data = revision.query_bytes("download", path="model.onnx")
revision.command_bytes("upload", data, path="model.tar.gz", size=len(data))
```

The first argument is the command name, and every keyword becomes a field of the request body. A keyword set to
`None` is left out, because the platform's schemas refuse an explicit `null`. Nested values are sent as given. Each
call returns the response's JSON envelope as a `dict`; `query_bytes` also returns the binary payload.

| Channel | Methods | The revision must be |
| :- | :- | :- |
| query, a read | `query`, `query_bytes` | `ready`, in phase `draft`, `commit` or `managed` |
| command, a write | `command`, `command_bytes` | `ready`, in phase `draft` or `managed`, with write access |

A committed revision answers a command with `Conflict` (`revision_not_writable`). The SDK never checks a phase or a
status itself; the platform does, and its refusal arrives as an error.

The commands each type accepts are in the [Blob Types](/blob-types/introduction) reference, such as the workflow
type's [Create Session](/blob-types/workflow/operations/create-session).

## Typed modules

`revision.typed()` returns the typed module for the revision's blob type, or the revision itself when the SDK has
none:

| Type string | Typed module |
| :- | :- |
| `blobhub.model.onnx` | [`blobhub.types.onnx.Onnx`](/sdk/onnx) |
| `blobhub.graph.orientdb` | [`blobhub.types.orientdb.Graph`](/sdk/graph) |
| `blobhub.compute.workflow` | [`blobhub.types.workflow.Workflow`](/sdk/workflow) |

You can also construct a module directly, `Onnx(revision)`. It raises `TypeMismatch` when the revision's blob is of
another type.

## Operations

Some work finishes after the request that started it. The platform tracks it as an
[operation](/rest-api/operations/get-operation); today, that is the processing of an uploaded ONNX model.

```python theme={null}
operation = hub.operation("<operation id>")
operation.wait(timeout=600.0)        # polls every 2 s until completed; returns the operation
operation.id, operation.status, operation.type, operation.revision_id
```

`status` is `in_progress`, `completed` or `failed`. `wait()` returns once it is `completed`, raises
`OperationFailed` when it is `failed`, and raises `WaitTimeout` when `timeout` runs out, leaving the operation
running. An id that names no operation you can read raises `PermissionDenied`. [`Onnx.upload()`](/sdk/onnx) starts
and waits on one for you.

## Raw requests

`hub.request()` sends any request relative to the API URL, with the same authentication, retries and error mapping
as everything else, and returns the JSON envelope:

```python theme={null}
readme = hub.request("GET", f"revisions/{revision.id}/metadata/readme")
blobs = hub.request("GET", f"orgs/{blob.org_id}/blobs")["blobs"]
```

The first reads a revision's `readme` through [Resolve Metadata](/rest-api/revisions/resolve-metadata); the second
lists an organization's blobs through [List Blobs](/rest-api/orgs/list-blobs). `json=` is sent exactly as given, and
`params=` drops its `None` values. The path must be relative; an absolute URL raises `ValueError`. This is how you
reach routes the SDK has no method for, such as [creating a blob](/rest-api/orgs/create-blob) or
[minting an API key](/rest-api/shared/create-api-key).

`hub.whoami()` returns the calling user's record from [`GET /users/me`](/rest-api/users/get-user), or `None` for an
anonymous client.

## The client

A `Client` holds two HTTP connection pools, one for the API and one for presigned transfers. It is not thread-safe:
use one per thread. Close it when you are done, or let a `with` block do it:

```python theme={null}
with blobhub.connect() as hub:
    print(hub.blob("acme", "checkout").default_revision_id)
```

## See also

* [Errors and retries](/sdk/errors) — every error above, and which requests the SDK repeats.
* [Concepts](/general/concepts) — organizations, blobs and revisions.
* [REST API conventions](/rest-api/conventions) — the envelope, identifiers and pagination.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.