Metadata-Version: 2.5
Name: blobhub
Version: 0.3.0
Summary: BlobHub Python SDK: blobs, revisions and typed data access for the BlobHub platform
Project-URL: Homepage, https://blobhub.io/
Project-URL: Documentation, https://docs.blobhub.io/
Project-URL: Source, https://github.com/blobhubio/blobhub-sdk-python
Project-URL: Issues, https://github.com/blobhubio/blobhub-sdk-python/issues
Author-email: BlobHub <developers@blobhub.io>
License: MIT License
        
        Copyright (c) 2020 BlobHub. All rights reserved.
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE.txt
Keywords: blobhub,blobs,models,onnx,sdk,versioning
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.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: hatchling>=1.25; extra == 'dev'
Requires-Dist: numpy>=1.20; extra == 'dev'
Requires-Dist: onnx>=1.16; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Description-Content-Type: text/markdown

# BlobHub Python SDK

`blobhub` is the Python SDK for [BlobHub](https://blobhub.io/), a platform for versioned blobs: ONNX models, graphs,
workflows and more. It reads and writes blobs and their revisions, runs any blob type's data commands, and has
typed modules for ONNX models, OrientDB graphs and workflows.

## Install

```bash
pip install blobhub
```

Python 3.10 or newer. The only dependencies are `httpx` and `PyYAML`.

## Connect

```python
import blobhub

hub = blobhub.connect()
print(hub.identity)        # Identity(source='profile:work', api_url='https://api.blobhub.io/v1')
```

`connect()` makes no network call. It resolves each value from the first source that has it:

| Value | 1 | 2 | 3 | 4 |
| --- | --- | --- | --- | --- |
| profile name | `profile=` | `BLOBHUB_PROFILE` | the file's `default:` | none |
| API key | `api_key=` | `BLOBHUB_API_KEY` | the profile's `key` | an anonymous token |
| API URL | `api_url=` | `BLOBHUB_API_URL` | the profile's `url` | `https://api.blobhub.io/v1` |

- **Profiles** are the ones [blobhub-cli](https://pypi.org/project/blobhub-cli/)'s `blobhub login` stores in
  `~/.blobhub/credentials.yaml`. The SDK reads that file and never writes it. A file readable by group or other is
  refused: `chmod 600 ~/.blobhub/credentials.yaml`.
- `blobhub.connect(profile="work")` picks a profile; `blobhub.connect(api_key="…")` passes a key directly.
- `blobhub.connect(anonymous=True)` reads public content with no credential at all, and ignores the profile file.
  Combined with `api_key=` or `profile=` it raises `ValueError`.
- `hub.identity` says where the key came from. The key itself is never printed, logged or put in an error.

## A short tour

```python
blob = hub.blob("onnx-models", "super-resolution")    # org and blob, each an alias or an id
blob.type, blob.visibility, blob.default_revision_id

rev = blob.revision()                    # the default revision; blob.revision("<id>") for another
for r in blob.revisions():               # every revision, newest first
    print(r.id, r.phase, r.status)

rev.query("describe")                    # any read the blob's type supports
rev.command("<command>", key="value")    # any write; arguments set to None are left out
```

### Revisions

```python
draft = blob.create_revision("retrain")  # needs a committed default revision; waits until ready
draft.commit("v2")                       # waits until committed
```

### ONNX models

```python
from blobhub.types.onnx import Onnx

model = Onnx(rev)                        # or rev.typed()
model.artifacts()                        # [Artifact(path='model.onnx', size=…), …]
path = model.download()                  # model.onnx, cached under ~/.cache/blobhub
model.download("model.tar.gz", dest="./model.tar.gz")
model.topology()                         # the graph without its weights

operation = Onnx(draft).upload("super_resolution.onnx")   # packs, uploads, waits for processing
```

A committed revision's download is cached and reused; a draft's is fetched again each time. Large files move in
parts over presigned URLs, and an upload that fails part-way is cancelled.

### Graphs

```python
from blobhub.types.orientdb import Graph

graph = Graph(rev)                       # or rev.typed() for a blobhub.graph.orientdb blob
result = graph.execute("SELECT FROM V WHERE name = :name", {"name": "fox"})
result.vertices, result.edges            # OrientDB records, each with its "@rid" and "@class"
graph.execute("CREATE VERTEX V SET name = 'fox'", write=True)
```

A read runs one `SELECT`, `TRAVERSE` or `MATCH` statement; anything else needs `write=True`, which a committed
revision refuses with `Conflict`. Parameters are a dict for `:name` placeholders or a list for `?`. A statement
returns at most 50 records. When OrientDB rejects a statement, the `CommandError` message gives its reason.

### Workflows

```python
from blobhub.types.workflow import Workflow

question = {"type": "message", "message": {"role": "user", "content": [{"type": "text", "text": "Why?"}]}}
run = Workflow(rev).execute(             # or rev.typed() for a blobhub.compute.workflow blob
    "summarize",                         # a definition alias or id, or "<org>/<blob>/<revision>/<definition>"
    input_data={"question": question},   # component alias to value
    session="sdk-demo",                  # an alias, created when absent, or an id; None starts a fresh session
)
print(run.id, run.session_id, run.url)   # url opens the execution in blobhub.io's debugger
# run.stop()                            # while it runs, instead of waiting: only a running execution stops
status = run.wait(timeout=900, on_event=print)   # "completed", "failed" or "stopped"
for event in run.events():               # the whole event log, oldest first
    print(event["type"], event.get("message"))
```

`execute` returns as soon as the execution exists. An input value reaches the workflow's `data` component whose
`alias` port names it and whose persistence is `execution`. A session that is not open is refused with `Conflict`
before anything is created. `wait` gives up with `WaitTimeout` and leaves the execution running. Writing and
deploying definitions is [blobhub-cli](https://pypi.org/project/blobhub-cli/)'s job.

### Errors

Every error is a `blobhub.BlobHubError` carrying the server's `error` code, a `message` and the HTTP `status`:
`AuthError` (401), `PermissionDenied` (403, also answered for many missing resources), `NotFound` (404), `Conflict`
(409, e.g. `revision_not_writable`), `RateLimited` (429), `ServerError` (5xx), `NetworkError` and its subclass
`RequestNotSent`, `CommandError`, `ConfigError`, `TypeMismatch`, `OperationFailed` and `WaitTimeout`. When the
client found no key and fell back to an anonymous token, an `AuthError` or `PermissionDenied` message says so. When
the server's failure carries a `details` list, the error keeps it in `details`.

Reads are retried on throttling, server errors and network failures. Writes are retried only when the failure proves
the request never ran, so a retry can never repeat a write.

### Raw requests

```python
hub.request("GET", f"revisions/{rev.id}/metadata/readme")
```

## Migrating from 0.1.x

0.1.x cannot talk to the current API, so there is no compatibility layer.

| 0.1.x | 0.2 |
| --- | --- |
| `Blob(org_id, blob_id, api_key=…)` from `blobhub.blob` | `blobhub.connect(api_key=…).blob(org, blob)` |
| `blob.revisions.latest()` | `blob.revision()` (the default revision) |
| `revision["status"]`, `revision[Revision.FIELD_PHASE]` | `revision.status`, `revision.phase` |
| `from blobhub.presets.onnx import Onnx, Model` | `from blobhub.types.onnx import Onnx` |
| `onnx.upload(Model.from_local_file(onnx, path))` returns `True` | `onnx.upload(path)` returns an `Operation` |
| `onnx.download()` returns a `Model`; `model.path` | `onnx.download()` returns a `Path` |
| models stored under `~/.blobhub/` | models cached under `~/.cache/blobhub/revisions/<revision>/` |
| `CallFailure`, `BadRequest`, `Forbidden` | the classes in `blobhub.errors` |

## License

MIT
