Metadata-Version: 2.5
Name: valt0-client
Version: 1.0.0
Summary: Python client for Valt0: fetch secrets from a local Valt0 vault at runtime
Project-URL: Homepage, https://valt0.com
Project-URL: Issues, https://github.com/byte1620/valt0-issues/issues
Author: BYTE 1620
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: client,secrets,valt0,vault
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
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: Programming Language :: Python :: 3.14
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: cryptography>=42
Description-Content-Type: text/markdown

# valt0

Python client for **Valt0**. It fetches secrets from a local Valt0 vault at runtime, so you don't
have to keep them in `.env` files, config files, or CI variables.

- **License:** Apache-2.0
- **Requires:** Python >= 3.9, and the `valt0` service/CLI installed and running locally

---

## Installation

```bash
pip install valt0-client
```

The package is installed as `valt0-client` and imported as `valt0`.

---

## Usage

```python
import valt0

vault = valt0.open_vault(id="your-vault-id", name="your-vault-name")

# The with block zeroes the underlying buffer as soon as you are finished
with vault.secret("DATABASE_PASSWORD") as secret:
    # Preferred: the raw bytes, wiped on exit
    pw = secret.bytes()

    # Or, when a string is unavoidable
    db.connect(password=secret.reveal())
```

Without a `with` block, call `secret.destroy()` yourself, ideally in a `finally`.

### Secrets are redacted by default

A `Secret` won't leak its value into a log line, a traceback, an f-string, or a pickle. You only
get the plaintext when you ask for it explicitly:

```python
print(secret)                            # [REDACTED]
print(repr(secret))                      # Secret([REDACTED])
f"{secret}"                              # [REDACTED]
logging.info("connecting %s", secret)    # connecting [REDACTED]
json.dumps({"pw": secret}, default=str)  # {"pw": "[REDACTED]"}
pickle.dumps(secret)                     # TypeError: Secret objects cannot be pickled or copied

secret.reveal()  # the actual value
```

Redaction covers `str()`, `repr()` and `format()`. Pickling and copying raise `TypeError`, so a
secret can't slip out through `multiprocessing` or a cache.

### Errors

`open_vault` and `Vault.secret` raise two specific errors for the most common connection failures:

```python
try:
    vault = valt0.open_vault(id="your-vault-id", name="your-vault-name")
except valt0.CantConnectToServiceError:
    ...  # socket or session file not found: is the valt0 service running?
except valt0.NoServiceResponseError:
    ...  # the service did not answer within the transport timeout (1s)
except valt0.Valt0Error:
    ...  # any other error, e.g. the vault could not be loaded
```

### Threads and asyncio

The client is synchronous and thread-safe; every call uses its own connection. From async code,
run it in a worker thread:

```python
secret = await asyncio.to_thread(vault.secret, "DATABASE_PASSWORD")
```

---

## API

### `open_vault(*, id: str, name: str) -> Vault`

Opens the vault identified by `id`.
Raises an error if the service is unreachable or the vault cannot be loaded.

| Argument | Type  | Description                                                 |
| -------- | ----- | ----------------------------------------------------------- |
| `id`     | `str` | Vault identifier — the only value used to locate the vault  |
| `name`   | `str` | Display label for messages; `.valt0` is appended if missing |

> **Note:** `name` can be anything. Valt0 does not check that it matches the vault `id` — it is
> only used for messaging purposes, so pick whatever makes the vault easy to recognise.

### `Vault`

| Member         | Description                          |
| -------------- | ------------------------------------ |
| `id`           | The vault's id                       |
| `name`         | The vault's name                     |
| `secret(name)` | `Secret`: fetches one secret by name |

### `Secret`

| Member        | Description                                                                                                       |
| ------------- | ----------------------------------------------------------------------------------------------------------------- |
| `bytes()`     | The raw bytes as a `bytearray` (the underlying buffer, not a copy). This is the preferred accessor.               |
| `reveal()`    | The value as a `str`. Strings are immutable and can't be wiped, so use sparingly. `ValueError` if not UTF-8 text. |
| `destroy()`   | Zeroes the buffer. Buffers returned by `bytes()` are zeroed too, and later calls return empty values.             |
| `with secret` | Calls `destroy()` when the block exits.                                                                           |

### Errors

| Error                       | Raised when                                       |
| --------------------------- | ------------------------------------------------- |
| `Valt0Error`                | Base class of every error the client raises       |
| `CantConnectToServiceError` | The service socket or session file does not exist |
| `NoServiceResponseError`    | The service did not respond before the deadline   |

---

## Platform support

Linux, macOS and Windows, including inside containers.

On Linux and macOS the client reaches the service through its unix socket, or over TCP to
`host.docker.internal` when `VALT0_TRANSPORT_CHANNEL=tcp`. On Windows it always uses TCP to
`127.0.0.1` (CPython has no `AF_UNIX` support there), and `VALT0_TRANSPORT_CHANNEL` and
`VALT0_SOCKET_PATH` are ignored.

---

## Development

```bash
python -m venv .venv && . .venv/bin/activate
pip install -e . pytest
pytest
```

The tests run against a strict fake of the Valt0 service (`tests/fake_service.py`).
`tests/test_go_parity.py` also runs the Go client through the same scenarios and checks that both
clients send the same requests and return the same results; it runs when `go` is installed and the
Go client is checked out at `../../golang/client` (or `$VALT0_GO_CLIENT_DIR`). See
[RELEASING.md](RELEASING.md) for publishing.

---

## Links

- [Website](https://valt0.com)
- [Issue tracker](https://github.com/byte1620/valt0-issues/issues)

Maintained by BYTE 1620. Licensed under Apache-2.0.
