Metadata-Version: 2.4
Name: terminusdb-pydantic
Version: 0.1.2
Summary: Pydantic v2 adapter and schema generator for TerminusDB
License-Expression: MIT
Project-URL: Source, https://gitlab.com/ed-tech6370840/terminusdb-pydantic
Project-URL: Issues, https://gitlab.com/ed-tech6370840/terminusdb-pydantic/-/issues
Project-URL: PyPI, https://pypi.org/project/terminusdb-pydantic/
Keywords: terminusdb,pydantic,json-ld,schema,graph-database
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic<3,>=2.7
Dynamic: license-file

# terminusdb-pydantic

[![PyPI](https://img.shields.io/pypi/v/terminusdb-pydantic.svg)](https://pypi.org/project/terminusdb-pydantic/)
[![Python](https://img.shields.io/pypi/pyversions/terminusdb-pydantic.svg)](https://pypi.org/project/terminusdb-pydantic/)

Pydantic v2 models for TerminusDB JSON-LD documents and TerminusDB schema
generation.

The package lets application code keep ordinary Pydantic models as its Python
data model while providing a small adapter layer for TerminusDB persistence:

```text
Pydantic model
    ↕
TerminusDB JSON-LD document
    ↓
TerminusDB schema document
```

> **Status:** early-stage / pre-1.0. The supported type mapping is intentionally
> small and explicit.
>
> **Project status:** this is a community package and is not an official
> TerminusDB package.

## Installation

With uv:

```bash
uv add terminusdb-pydantic
```

With pip:

```bash
python -m pip install terminusdb-pydantic
```

Python 3.11+ and Pydantic v2 are supported.

## Quick start

Define a model:

```python
from enum import Enum

from terminusdb_pydantic import LexicalKey, TerminusModel


class Level(str, Enum):
    bachelor = "bachelor"
    master = "master"


class Discipline(TerminusModel):
    __terminus_key__ = LexicalKey("code")

    code: str
    name: str
    description: str | None = None
    level: Level
```

Serialize it to a TerminusDB JSON-LD document:

```python
from terminusdb_pydantic import to_document


discipline = Discipline(
    code="MATH-01",
    name="Mathematical Analysis",
    level=Level.bachelor,
)

document = to_document(
    discipline,
    document_id="Discipline/MATH-01",
)

assert document == {
    "@type": "Discipline",
    "@id": "Discipline/MATH-01",
    "code": "MATH-01",
    "name": "Mathematical Analysis",
    "level": "bachelor",
}
```

Read a TerminusDB document back into Pydantic:

```python
from terminusdb_pydantic import from_document

discipline = from_document(Discipline, document)
```

Pydantic validation is applied when the model is reconstructed.

## Generate a TerminusDB schema

```python
from terminusdb_pydantic import models_to_schema

schema_documents = models_to_schema([Discipline])
```

The returned value is ordinary Python data and can be inspected, versioned,
tested, or sent to TerminusDB using
[`terminusdb-async`](https://pypi.org/project/terminusdb-async/).

## Key strategies

`TerminusModel` uses `RandomKey()` by default. The package also supports
lexical, hash, and value-hash keys:

```python
from terminusdb_pydantic import HashKey, LexicalKey, ValueHashKey

__terminus_key__ = LexicalKey("code")
__terminus_key__ = LexicalKey("program_code", "discipline_code")
__terminus_key__ = HashKey("source", "target")
__terminus_key__ = ValueHashKey()
```

## Supported type mapping

| Python / Pydantic annotation | TerminusDB schema |
| --- | --- |
| `str` | `xsd:string` |
| `int` | `xsd:integer` |
| `float` | `xsd:double` |
| `bool` | `xsd:boolean` |
| `T | None` | `Optional<T>` |
| `list[T]` / `tuple[T, ...]` | `List<T>` |
| `set[T]` | `Set<T>` |
| `Enum` | TerminusDB `Enum` document |
| another Pydantic model | reference to that class |

Example:

```python
class Course(TerminusModel):
    title: str
    tags: set[str]
    prerequisites: list[str]
    notes: str | None = None
```

## Enums

Python enums are emitted once per generated schema:

```python
from enum import Enum


class AssessmentType(str, Enum):
    exam = "exam"
    credit = "credit"
    project = "project"
```

A field annotated with `AssessmentType` references the generated TerminusDB
enum.

## JSON-LD conversion

`to_document()`:

- uses Pydantic's JSON serialization mode;
- omits fields whose value is `None`;
- adds `@type` from the model class name;
- optionally adds `@id`.

`from_document()`:

- removes top-level JSON-LD metadata keys beginning with `@`;
- validates the remaining data with `model_validate()`.

This keeps persistence metadata separate from the application model.

## Migration metadata

`terminus_field()` can attach TerminusDB-specific metadata to a Pydantic
field:

```python
from terminusdb_pydantic import terminus_field


class Discipline(TerminusModel):
    name: str = terminus_field(rename_from="title")
```

The `rename_from` value is stored in Pydantic field metadata for higher-level
migration tooling. It does not itself mutate a TerminusDB schema.

## Current boundaries

The library is intentionally conservative in `0.1.x`.

- It does not provide persistence or network access.
- It does not act as an ORM.
- It does not infer arbitrary Python types.
- Types outside the current mapping fall back to `xsd:string`; inspect generated
  schemas before applying them to production databases.
- Schema generation and data migration are separate concerns.

Use [`terminusdb-async`](https://pypi.org/project/terminusdb-async/) for
network access and
[`terminusdb-migrations`](https://pypi.org/project/terminusdb-migrations/) for
schema comparison and migration planning.

## End-to-end example

```python
from terminusdb_async import AsyncTerminusClient
from terminusdb_pydantic import (
    LexicalKey,
    TerminusModel,
    models_to_schema,
    to_document,
)


class Discipline(TerminusModel):
    __terminus_key__ = LexicalKey("code")

    code: str
    name: str


async def save() -> None:
    async with AsyncTerminusClient(
        "http://localhost:6363",
        organization="admin",
        database="edtech",
        username="admin",
        password="root",
    ) as db:
        await db.insert_schema_documents(
            models_to_schema([Discipline]),
            author="schema-bot",
            message="Add Discipline schema",
        )

        value = Discipline(code="MATH-01", name="Mathematical Analysis")
        await db.insert_documents(
            to_document(value, document_id="Discipline/MATH-01"),
            author="application",
            message="Add Discipline",
        )
```

## Package family

| Package | Purpose |
| --- | --- |
| [`terminusdb-async`](https://pypi.org/project/terminusdb-async/) | Async TerminusDB HTTP client |
| [`terminusdb-pydantic`](https://pypi.org/project/terminusdb-pydantic/) | Pydantic v2 ↔ JSON-LD and schema generation |
| [`terminusdb-migrations`](https://pypi.org/project/terminusdb-migrations/) | Schema diff and migration planning |

## Development

The project uses uv:

```bash
uv sync --group dev
uv run pytest -q
uv build --no-sources
```

CI tests Python 3.11, 3.12, and 3.13.

Development follows GitHub Flow on GitLab. Changes are made on short-lived
branches and merged into `main` through Merge Requests. See
`CONTRIBUTING.md`.

## Versioning

The package is pre-1.0. A bounded dependency is recommended for consumers that
want compatible updates:

```toml
terminusdb-pydantic = ">=0.1.1,<1.0.0"
```

Review release notes before upgrading across `0.x` minor versions.

## License

MIT License. See [LICENSE](LICENSE) for the full text.
