Metadata-Version: 2.4
Name: typra
Version: 0.11.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Rust
Requires-Dist: pytest>=8 ; extra == 'test'
Provides-Extra: test
Summary: Python bindings for Typra, a typed embedded database.
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# typra (Python)

[![CI](https://github.com/eddiethedean/typra/actions/workflows/ci.yml/badge.svg)](https://github.com/eddiethedean/typra/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/typra.svg)](https://pypi.org/project/typra/)

Official **CPython** bindings for **Typra** (PyO3 native extension): a typed, embedded database with a Rust core.

## Status

You get a durable **schema catalog**, **validation**, nested **row values** (record **v2** on insert; **v1** segments still replay), and **constraints** in a single **`.typra`** file, plus **in-memory** databases and **snapshot** bytes.

**Queries and secondary indexes (0.7+):** register optional **`indexes_json`** on **`register_collection`**, then use **`db.collection("name").where("field", value).and_where(...).limit(n).explain()`** and **`all()`** / **`all(fields=[...])`** for subset rows. A longer **on-disk + reopen** example lives in the [Python user guide — Realistic workflow](https://github.com/eddiethedean/typra/blob/main/docs/guide_python.md#realistic-workflow-indexed-queries-on-disk). Typra also ships an **experimental, read-only DB-API 2.0 adapter** (`typra.dbapi`) with a minimal `SELECT` subset—see the [Python guide](https://github.com/eddiethedean/typra/blob/main/docs/guide_python.md#db-api-20-pep-249-and-sqlalchemy).

| Resource | Link |
|----------|------|
| **Repository** | [github.com/eddiethedean/typra](https://github.com/eddiethedean/typra) |
| **Rust crates** | [`typra` on crates.io](https://crates.io/crates/typra) |
| **Full Python guide** | [docs/guide_python.md](https://github.com/eddiethedean/typra/blob/main/docs/guide_python.md) |
| **Getting started** | [docs/guide_getting_started.md](https://github.com/eddiethedean/typra/blob/main/docs/guide_getting_started.md) |
| **Migrating 0.4 → 0.5** | [docs/migration_0.4_to_0.5.md](https://github.com/eddiethedean/typra/blob/main/docs/migration_0.4_to_0.5.md) |
| **Migrating 0.5 → 0.6** | [docs/migration_0.5_to_0.6.md](https://github.com/eddiethedean/typra/blob/main/docs/migration_0.5_to_0.6.md) |
| **Migrating 0.6 → 0.7** | [docs/migration_0.6_to_0.7.md](https://github.com/eddiethedean/typra/blob/main/docs/migration_0.6_to_0.7.md) |
| **Rust module layout** | [docs/03_rust_crate_and_module_layout.md](https://github.com/eddiethedean/typra/blob/main/docs/03_rust_crate_and_module_layout.md) |
| **Changelog** | [CHANGELOG.md](https://github.com/eddiethedean/typra/blob/main/CHANGELOG.md) |

## Requirements

- **CPython 3.9+**
- Wheels use the stable ABI (**`cp39-abi3`**): one wheel per platform, compatible with Python 3.9+ on that platform.

## Install

```bash
pip install "typra>=0.11.0,<0.12"
```

Pin the minor range you test against; pre-1.0 releases may still change APIs or the on-disk format between minors.

## Quick start

```python
# Setup: module, in-memory DB, and `books` collection (PK `title`).
import typra

db = typra.Database.open_in_memory()
cid, ver = db.register_collection(
    "books",
    '[{"path": ["title"], "type": "string"}]',
    "title",
)
# Example: insert one row, read it back, print package version.
print("registered", cid, ver)
db.insert("books", {"title": "Typra"})
print(db.get("books", "Typra"))
print(typra.__version__)
```

Output (the version line matches the installed wheel):

```text
registered 1 1
{'title': 'Typra'}
0.11.0
```

On disk, use **`Database.open("app.typra")`** instead; registrations are **persisted** across process restarts for that path.

### Indexed query (sketch)

```python
# Setup: in-memory DB, indexed collection, one row.
import typra

db = typra.Database.open_in_memory()
fields = '[{"path": ["id"], "type": "int64"}, {"path": ["sku"], "type": "string"}]'
indexes = '[{"name": "sku_idx", "path": ["sku"], "kind": "index"}]'
db.register_collection("items", fields, "id", indexes)
db.insert("items", {"id": 1, "sku": "abc"})
# Example: equality query on indexed `sku`.
print(db.collection("items").where("sku", "abc").all())
```

Output:

```text
[{'id': 1, 'sku': 'abc'}]
```

See **[`docs/guide_python.md`](https://github.com/eddiethedean/typra/blob/main/docs/guide_python.md)** for `and_where`, `limit`, `explain`, and subset projections.

## API overview

| Member | Description |
|--------|-------------|
| `typra.__version__` | Package version (matches the Rust workspace release). |
| `Database.open(path: str)` | Create or open a database file. Raises `OSError` if the path cannot be opened (e.g. missing parent directory, path is a directory). |
| `db.path() -> str` | Path used to open the database. |
| `db.register_collection(name, fields_json, primary_field, indexes_json=None) -> tuple[int, int]` | Register a **new** collection (schema version **1**). Optional **`indexes_json`**: JSON array of `{"name", "path", "kind"}` objects (`"unique"` or `"index"` / `"non_unique"`). Returns **`(collection_id, schema_version)`**. Names are trimmed; duplicates or bad JSON raise `ValueError`. |
| `db.collection(name) -> Collection` | Query handle: **`where`**, **`and_where`**, **`limit`**, **`explain`**, **`all`** / **`all(fields=[...])`**. |
| `db.insert(collection_name, row: dict) -> None` | Insert or replace the latest row (required fields + optional keys per schema). |
| `db.get(collection_name, pk) -> dict \| None` | Latest row or missing. |
| `Database.open_in_memory()` / `Database.open_snapshot_bytes(data)` / `db.snapshot_bytes()` | In-memory DB and byte snapshots. |
| `db.collection_names() -> list[str]` | All registered names, **sorted** alphabetically. |

For behavior details (errors, edge cases, development), see the **[Python user guide](https://github.com/eddiethedean/typra/blob/main/docs/guide_python.md)**.

## `fields_json` (schema descriptor)

`register_collection` expects `fields_json` to be a JSON **array** of objects. Each object describes one field:

- **`path`**: JSON array of strings (path segments), e.g. `["profile", "name"]`.
- **`type`**: either a **primitive** name or a **composite** object.

**Primitives:** `"bool"`, `"int64"`, `"uint64"`, `"float64"`, `"string"`, `"bytes"`, `"uuid"`, `"timestamp"`.

**Composites:**

- Optional: `{"optional": <inner>}`
- List: `{"list": <inner>}`
- Object: `{"object": [ … same shape as top-level field objects … ]}`
- Enum: `{"enum": ["a", "b"]}` (variants must be strings)
- **`constraints`** (optional): JSON array of constraint objects, e.g. `{"min_i64": 0}`, `{"max_length": 100}`, `{"email": true}`, `{"regex": "^[a-z]+$"}`.

### Example (nested)

```python
# Setup: in-memory DB and a collection whose PK uses an optional int field.
import typra

db = typra.Database.open_in_memory()
db.register_collection(
    "items",
    '[{"path": ["x"], "type": {"optional": "int64"}}]',
    "x",
)
# Example: confirm registration.
print("nested:", db.collection_names())
```

Output:

```text
nested: ['items']
```

### Example (multiple fields)

```python
# Setup: in-memory DB and a multi-field `books` schema (PK `title`).
import typra

db = typra.Database.open_in_memory()
schema = """[
  {"path": ["title"], "type": "string"},
  {"path": ["year"], "type": "int64"},
  {"path": ["tags"], "type": {"list": "string"}}
]"""
db.register_collection("books", schema, "title")
# Example: confirm registration.
print("multi:", db.collection_names())
```

Output:

```text
multi: ['books']
```

## Exceptions

- **`ValueError`**: invalid JSON, wrong shape, unknown type, invalid collection name, duplicate collection name, validation failures, or format/schema errors from the engine when registering.
- **`OSError`**: I/O failures when opening the database file.
- **`RuntimeError`**: reserved for engine “not implemented” paths (unexpected for supported API paths).

## Building from source

You need **Rust**, **Python 3.9+**, and **[maturin](https://www.maturin.rs/)**. From the repo’s **`python/typra`** directory:

```bash
maturin develop --release
pytest -q
```

From the repository root, **`make check-full`** runs Rust + Python checks, tests, and **`make verify-doc-examples`** (validates documented command output). See also **[python/README.md](https://github.com/eddiethedean/typra/blob/main/python/README.md)** (workspace layout for contributors).

