Metadata-Version: 2.4
Name: serva
Version: 0.6.1
Summary: Official Python client for the Serva encode/decode API
Author: Servamind
License: Proprietary - All Rights Reserved
Project-URL: Homepage, https://serva.servamind.com
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27
Requires-Dist: numpy>=1.24
Requires-Dist: huggingface_hub>=0.20
Requires-Dist: torch>=2.0
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Dynamic: license-file

# serva

Python client for the Serva encode/decode API. Encode files and rows of numbers
into the `.serva` format, decode them back, push datasets to the Hugging Face
Hub, and load them into PyTorch.

## Install

```bash
pip install serva
```

Grab an API key from [serva.servamind.com](https://serva.servamind.com) and pass
it to the client or set `SERVA_API_KEY`. Both encoding and decoding need one.

## Encode and decode

```python
from serva import Serva

client = Serva(api_key="sk_live_...")   # or set SERVA_API_KEY

result = client.encode("photo.raw")
print(result.output_path, result.savings_percent)

client.decode("photo.serva", output="photo.raw")
```

`encode` takes one thing, what you want encoded. A file is written beside its
input with a `.serva` suffix, so `pictures/photo.raw` becomes
`pictures/photo.serva`.

Rows of numbers work the same way. They are staged as a float32 `.npy` and
encoded as a file, so both go through one endpoint and come back as the same
result. Nothing about an array suggests a filename, so rows get a generated one
unless you say otherwise:

```python
client.encode([[0.1, 0.2, 0.3]])                        # 4f99dcaf-....serva
client.encode([[0.1, 0.2, 0.3]], output="rows.serva")   # rows.serva
```

`output` names the result for a file too:

```python
client.encode("photo.raw", output="archive/photo.serva")
```

Worth naming your rows: a `.serva` does not record where it came from, so a
generated name is all you will have to go on later.

Decoding takes `output` the same way. A `.serva` does not carry the original
filename, so without one you get the container's own name with a `.out` suffix,
beside the `.serva`.

```python
client.decode("photo.serva", output="photo.raw")     # photo.raw
client.decode("photo.serva")                         # photo.out

rows = client.decode("rows.serva", output="rows.npy")
numpy.load(rows.output_path)                         # the array, exactly
```

The contents are unchanged either way, only the name differs, and `numpy.load`
reads the rows back whatever the file is called.

Encoding does not lock anything, so there is no password to choose and none to
remember. `decode` still takes one, for files encoded back when it did:

```python
client.decode("old.serva", password="my-secret", output="old.raw")
```

## Read blocks locally

A `.serva` you already have can be sliced on disk. `get_blocks` does not call
the API and does not need a key. It returns hypervectors.


```python
from serva import get_blocks, get_block

blocks = get_blocks("photo.serva")
print(len(blocks), "blocks")
print(blocks[0].shape, blocks[0].dtype)   
print(get_block("photo.serva", 0).shape)  
```

## Hugging Face

Push encoded datasets to the Hub and pull them back. Set `HF_TOKEN` or pass
`hf_token=...`; nothing extra to install.

```python
result = client.encode("photo.raw")
client.hub.push(result, repo_id="your-name/my-dataset")

path = client.hub.pull("your-name/my-dataset", "photo.serva")
client.decode(path, output="photo.raw")
```

## PyTorch

`ServaDataset` reads a folder of `.serva` files and returns each file's raw bytes
as a tensor, the model trains on the encoded bytes. Point it at a
directory, like `torchvision`'s `ImageFolder`.

```python
from serva.torch import ServaDataset
from torch.utils.data import DataLoader

ds = ServaDataset("data/train")
loader = DataLoader(ds, batch_size=32, shuffle=True)

for batch in loader:
    ...   # your model, your training step
```

The folder layout decides what you get:

- **Class subfolders** (`ants/*.serva`, `bees/*.serva`) → `(tensor, label)`.
- **A flat folder** → just the tensor, for text or other unlabeled data.

Use `label_fn` when labels aren't in folder names (a CSV lookup, a regression
target):

```python
ds = ServaDataset("data/train", label_fn=lambda path: scores[path.stem])
```

By default files are padded to a fixed length so batches stack. To keep every
byte and avoid padding side effects, use `length=None` with `pad_collate` it
pads each batch only to its longest file and returns a mask marking real bytes,
so the model ignores the padding:

```python
from serva.torch import ServaDataset, pad_collate

ds = ServaDataset("data/train", length=None)
loader = DataLoader(ds, batch_size=32, collate_fn=pad_collate)

for bytes, mask, labels in loader:
    ...   # pass mask to your model so padded positions are ignored
```

`ServaDataset` only turns `.serva` files into tensors. Reshaping is plain PyTorch 
that's what `transform` and the DataLoader's `collate_fn` are for.

## Configuration

| Setting | How | Default |
|---|---|---|
| API key | `Serva(api_key=...)` or `SERVA_API_KEY` | — (required to encode or decode) |
| Base URL | `Serva(base_url=...)` | production API |
| Hugging Face token | `Serva(hf_token=...)` or `HF_TOKEN` | — |

## Progress

Both calls draw a bar when stderr is a terminal, one line per phase.

```
  upload        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% 0:00:32
  encode/decode ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% 0:00:16
  download      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% 0:00:01
```

The upload and download lines track bytes moved. The middle line is named after
whichever call you made and tracks the work on the server, from the time it
reports as still to go.

## Errors

Everything raises a subclass of `ServaError`, connection failures and timeouts
included, so a single `except ServaError` covers all of them.

| Error | Raised when |
|---|---|
| `AuthError` | the API key is missing, invalid, or expired |
| `PasswordError` | the password does not decrypt this file, or one is needed and none was given |
| `PaymentRequiredError` | a payment method is needed to continue |
| `AccessDeniedError` | the account may not touch this resource |
| `NotFoundError` | the task, job, or file is gone from the server |
| `ConflictError` | the request clashes with work already done |
| `ValidationError` | the input or the request was rejected before any work started |
| `FileTooLargeError` | the file is over the service maximum |
| `RateLimitError` | too many requests in too short a window |
| `ServiceError` | the service failed while handling the request |
| `NetworkError` | the service could not be reached at all |
| `RequestTimeoutError` | the service did not answer in time |
| `HubError` | a Hugging Face operation failed |

Each one carries the message the service wrote, plus `status_code` and `code`,
so you can branch on the cause without matching on message text.

```python
try:
    client.decode("old.serva", password="wrong")
except ServaError as exc:
    print(exc)          # Password is incorrect or file is corrupted
    print(exc.code)     # incorrect_password
```

## Versioning

Semantic versioning. Read the installed version from `serva.__version__`.

