Skip to content

Pydantic native implementation in terminusdb client - #482

Open
hoijnet wants to merge 14 commits into
mainfrom
pydantic-native
Open

hoijnet wants to merge 14 commits into
mainfrom
pydantic-native

Conversation

@hoijnet

@hoijnet hoijnet commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

This adds a first-class pydantic interface for schema management and typed documents, so database schemas and Python domain models can evolve together without drift. TerminusModel subclasses declare the same schema DocumentTemplate declares and emit identical class documents, so both syntaxes coexist in one Schema and one commit. The legacy path is untouched in behaviour — it remains the fully supported compatibility interface.

AI-assisted PR writing.

What is included

terminusdb_client/schema/pydantic.py carries the whole integration: TerminusModel, TerminusUnion (tagged unions, including sys:Unit members via None annotations), TerminusEnum, PydanticSchema, and the Unfold/Array annotation markers. Class-level flags cover the schema constructs the legacy client never mapped — _shared, _foreign, _metadata, _unfoldable, _abstract, _base — and Field(min_length/max_length) maps to @cardinality/@min_cardinality/@max_cardinality on Set properties.

Instances serialise through the existing _obj_to_dict/_embedded_rep/@capture contract, so insert_document, update_document, replace_document and capture/reference graphs work unchanged. Documents parse back into validated models via PydanticSchema.parse_document(client.get_document(...)); folded links materialise as id-carrying stubs with a private _backend_id.

PydanticSchema.from_db rebuilds models from the schema graph in dependency order (inheritance included), resolves forward references with model_rebuild, and restores class/property documentation as docstrings and Field(description=...). tdbpy sync --format pydantic generates a schema.py of TerminusModel classes whose emitted schema round-trips against the database; model_backend in config.json selects the emitter. tdbpy check diffs schema.py against the database schema and exits 1 on drift (with --ignore-docs for documentation-only differences), giving CI a real drift gate.

Compatibility changes

The only edits to existing code are four hasattr → getattr(..., None) is not None guards in Client.py — needed because pydantic PrivateAttrs and ClassVar defaults are always present even when unset (_backend_id stamp-back, _capture, _subdocument). schema.py gains UuidV7Key (engine-supported, previously missing), an optional metadata context argument, and Schema.diff. woqlschema/__init__.py no longer leaks the schema module name, which shadowed the schema package. Pydantic is an optional extra (terminusdb[pydantic], pydantic>=2.7,<3); the client imports and works unchanged without it.

Verified behaviour

Unit suite: 1410 tests pass including 50 new pydantic tests covering golden parity with DocumentTemplate twins, all annotation mappings, stub/capture behaviour, from_db round-trips, generated-module imports, and diff semantics. Integration suite against a live server: schema push, legacy reload, insert→parse→replace, folded-link stubs, @shared/@unfoldable/@unfold, mixed DocumentTemplate+TerminusModel commits, and diff in both directions — all passing.

Engine-verified semantics worth noting: Optional[Set]/Optional[List] raise TypeError at declaration (the engine rejects them), @key is repeated by the server on subclasses so models always emit the effective key, and @inherits scalar-vs-list shape is normalised inside diff comparisons.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant