Metadata-Version: 2.4
Name: zydecodb
Version: 1.0.0rc1
Summary: Official Python driver for ZydecoDB.
Project-URL: Homepage, https://github.com/dataparade/zydecodb
Project-URL: Source, https://github.com/dataparade/zydecodb
Author: Dataparade
License: MIT
Keywords: database,document-store,nosql,zydecodb
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Database :: Front-Ends
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# ZydecoDB Python driver

Official Python client for [ZydecoDB](../../README.md). Pure standard library,
no runtime dependencies.

**Wire reference:** this client is the hand-maintained reference codec. Go and
TypeScript track the same bytes via [`../conformance/vectors.json`](../conformance/vectors.json)
(CI job `wire-conformance`).

## Install

```bash
pip install zydecodb
```

Requires Python 3.9+. (Working from a checkout of this repo:
`pip install -e clients/python`.)

## Quick start

```python
from zydecodb import Client

# Plain TCP (localhost). For TLS: Client(..., api_key="YOUR_KEY", tls=True)
with Client("127.0.0.1", 9470, api_key="YOUR_KEY") as db:
    users = db.collection("users")
    users.create_index(["email"], unique=True)

    uid = users.insert_one({"email": "ada@example.com", "name": "Ada", "age": 30})

    for u in users.find({"age": {"$gte": 18}}, sort=[("age", True)]):
        print(u["name"], u["age"])

    users.update_one({"_id": uid}, {"$inc": {"age": 1}})
    print(users.count_documents())
```

## What you get

- **Connection pooling.** `Client` owns a thread-safe pool (`pool_size`,
  default 8) and is safe to share across threads.
- **Automatic retries with backoff.** Transient transport failures and server
  `EngineBusy` responses are retried (full-jitter exponential backoff) for
  operations that are safe to repeat. Operator updates and deletes are never
  retried automatically.
- **Keepalive.** Idle pooled connections are validated with a `Ping` on
  checkout and transparently replaced if dead.
- **Typed error taxonomy.** Non-OK responses raise a specific subclass:
  `ConflictError` (unique-index violation), `AuthError`, `ServerBusyError`,
  `InvalidRequestError`, or the base `ServerError` — each carrying the wire
  `status` byte. Transport problems raise `ConnectionError`.
- **`Collection` API.** `insert_one/many`, `find`/`find_one`,
  `update_one/many`, `delete_one/many`, `count_documents`, `distinct`,
  `create_index`, with `$`-operators, sort, projection, and skip/limit.
  Pagination is repeatable-read across pages.
- **Raw KV with TTL.** Side-channel `put` (with `expires_at`), `get`, and `delete` methods on `Client` for session data that needs a time-to-live.
- **TLS.** Pass `tls=True` for system CA defaults, or an `ssl.SSLContext` for custom roots / verification.

## Optimistic concurrency

```python
got = users.get_with_revision(uid)
doc, rev = got
doc["age"] += 1
try:
    users.replace_one_if_match(uid, doc, if_match=rev)
except ConflictError:
    pass  # re-read and retry, or merge
```

Also: `find_with_revision`, `update_by_id_if_match`. Revisions are opaque
integers. Stale/missing documents raise `ConflictError`. Against an older
server these methods fail with a protocol error instead of silently becoming
unconditional writes.

## Bounded transactions

```python
with db.transaction() as tx:
    tx.put(b"session", b"active")
    tx.put_document("users", "u1", {"n": 1})
```

Pins one connection for the duration; no automatic retries. Collections must
already exist. Filter queries/updates and DDL are rejected inside a transaction.
Commit transport failure raises `UnknownCommitError` — reconcile by re-reading
keys. Older servers reject `Begin` with a protocol error.

## Durability

Writes are durable (fsync-on-commit) by default. For latency-sensitive,
loss-tolerant writes, pass `relaxed=True` to acknowledge before the fsync.
It is available on every write: `insert_one`, `replace_one`, `update_one`,
`update_many`, `delete_one`, and `delete_many`.

```python
users.insert_one(doc, relaxed=True)
users.update_one({"_id": "ada"}, {"$inc": {"hits": 1}}, relaxed=True)
users.delete_many({"stale": True}, relaxed=True)
```

Filtered positional `$set` (exactly one array match) uses the same update APIs
with a path like `items.$[skuId=ABC].qty` — no new client methods.

Directional indexes: pass `("field", False)` tuples in `create_index` for DESC
(e.g. `[("ownerId", True), ("updatedAt", False)]`).

## Running the tests

Unit + wire conformance (no server):

```bash
cd clients/python
pip install -e ".[dev]"
pytest tests/test_protocol.py tests/test_conformance.py
```

Integration tests run against a live server selected by environment variables
(skipped automatically if it is unreachable):

```bash
ZYDECODB_TEST_HOST=127.0.0.1 ZYDECODB_TEST_PORT=9470 pytest
```
