Metadata-Version: 2.4
Name: valthos-sdk
Version: 0.2.0
Summary: Python SDK for Valthos API
Author-email: Valthos <support@valthos.com>
License-Expression: MIT
Project-URL: Homepage, https://valthos.com
Project-URL: Documentation, https://docs.valthos.com
Project-URL: Repository, https://github.com/valthos/valthos-sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
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
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.25.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: requests-mock>=1.11; extra == "dev"
Dynamic: license-file

# Valthos Python SDK

Python SDK for the Valthos bioinformatics platform.

## Installation

```bash
pip install valthos-sdk
```

## Authentication

The SDK supports two credential kinds — pick whichever matches the caller:

### Browser-callback login (interactive)

For human users on a workstation. Opens Grove in a browser, captures
the resulting TokenPair on a loopback HTTP listener, and persists it
to `~/.valthos/credentials` (mode 0600).

```python
import valthos_sdk

session = valthos_sdk.login(root_domain="valthos.com")
workspaces = session.list_workspaces()
```

The login flow opens `https://grove.<root_domain>/cli-login` with a
loopback `redirect_uri` and a 128-bit `state` parameter. The page
asks the auth API to mint a fresh refresh-token family for the
authenticated user and POSTs the resulting TokenPair back; the SDK
verifies `state` and writes the credentials. Grove's own session is
unaffected — it keeps its own refresh family, and the SDK gets an
independent one. Either client can refresh without invalidating the
other.

The auth_v2 user session is subject to the platform's 24h idle cap on
refresh tokens — if a session has been idle longer than that, the
next refresh fails and you'll need to re-login. For long-lived
unattended use, prefer service accounts (below).

### Service-account credentials (headless)

For Prefect flows, CI runners, and other unattended workloads.
A service-account secret is non-rotating, scoped to a single owner
user, and minted by an operator on a bastion (the auth server does
not expose a self-service issuance endpoint).

Operator workflow (run on the bastion as `valthos_operator`):

```bash
uv run python scripts/manage_service_accounts.py create \
  --user-rid user_xxxx --name prefect-mgs --ttl-days 365
```

The plaintext secret is printed to stdout exactly once. Transmit it
through a secure channel (e.g. AWS Secrets Manager / Prefect block
secret) — there is no way to recover it after that.

SDK use:

```python
import valthos_sdk

session = valthos_sdk.login_service_account(
    root_domain="valthos.com",
    secret="<plaintext from operator>",
)
workspaces = session.list_workspaces()
```

Or, equivalently, construct a Session directly:

```python
session = valthos_sdk.Session(
    root_domain="valthos.com",
    service_account_secret="<plaintext from operator>",
)
```

To revoke a credential:

```bash
uv run python scripts/manage_service_accounts.py revoke --rid svcacct_xxxx
```

Revocation takes effect on the next access-token mint — at most 10
minutes after the operator runs the command (the access-token TTL).

### Scripted use

If you've already minted a TokenPair through the auth API directly,
persist it without going through the browser:

```python
session = valthos_sdk.login_with_token_pair(
    root_domain="valthos.com",
    access_token="...",
    refresh_token="...",
    access_expires_at=...,
)
```

## Working with workspaces

```python
workspaces = session.list_workspaces()
for ws in workspaces:
    print(f"{ws.name}: {ws.status}")

workspace = session.workspace(name="My Project")
workspace = session.create_workspace(name="New Analysis", description="My analysis")
```

## File operations

```python
workspace.add('/local/path/data.csv', 'data/input.csv')
files = workspace.list()
workspace.create_folder('results')
workspace.copy('data.csv', 'backup/data.csv')
workspace.rename('old_name.txt', 'new_name.txt')
workspace.delete('temporary_file.txt')
```

## Analysis tools

```python
fitness = workspace.tools.Fitness(
    input_path='proteins.parquet',
    output_path='results/fitness.parquet',
)
validation = fitness.validate()
if validation['status'] == 'valid':
    estimate = fitness.estimate()
    print(f"Estimated time: {estimate['estimated_time_minutes']} minutes")
    result = fitness.run()
    print(f"Job ID: {result['job_id']}")
```

## Error handling

```python
from valthos_sdk import (
    ValthosError,
    AuthenticationError,
    RefreshFamilyRevokedError,
    ServiceAccountInvalidError,
    APIError,
    WorkspaceNotFoundError,
)

try:
    workspaces = session.list_workspaces()
except RefreshFamilyRevokedError:
    # Refresh-token reuse detected; another caller raced this session.
    # Re-login interactively.
    session = valthos_sdk.login(root_domain="valthos.com")
except ServiceAccountInvalidError:
    # Credential was revoked or expired. Mint a new one on the bastion.
    raise
except WorkspaceNotFoundError:
    print("Workspace not found")
```

## Requirements

- Python 3.9+
- requests
- pydantic >= 2.0

## License

MIT License — see [LICENSE](LICENSE).
