Skip to main content

Pydantic TripleModel classes ↔ RDF triples via rdflib (alpha).

Project description

TripleModel

CI Python 3.10+ License: MIT Documentation

Pydantic models for RDF graphs. Map typed Python classes to rdflib triples and back — without hand-writing graph.add for every field.

PyPI / import triplemodel
Base class TripleModel
Person(slug="alice", name="Alice")  →  (ex:alice, foaf:name, "Alice")  →  Person(...)

TripleModel is the typed mapping layer in a small ecosystem: Pydantic models ↔ RDF triples via field types and predicates. SparqlModel (session, SPARQL queries, ORM) is planned to depend on TripleModel from 0.2 — see the ecosystem guide.

0.2.0 is alpha. The API may change until 1.0. See CHANGELOG and the roadmap.

Features

  • Pydantic v2 models with validate_assignment=True
  • Declarative mapping — nested Rdf config + rdf_field() or Annotated[..., Predicate(...)]
  • Subject IRIs — build from namespace + id_field, percent-encoded segments, safe import (no prefix collisions)
  • XSD round-tripstr, int, float, bool, date, datetime; IRI-like strings → URIRef
  • Stateless I/Oto_graph / from_graph / all_from_graph / models_to_graph on in-memory Graph
  • Multi-valued fieldslist[T] / set[T] round-trip multiple objects per predicate
  • Nested models — embed child TripleModel instances (Rdf.embed: "iri" or "bnode")
  • Sync modessync_to_graph / to_graph(..., mode="replace"|"patch") remove stale owned triples when fields are cleared
  • Prefixes & CURIEsRdf.prefixes, rdf_field("foaf:name"), bind_namespaces
  • Typed packagepy.typed for type checkers

Not in 0.2.0 (on the roadmap): file parse/serialize (0.4), RDF lists and full blank-node strategy (0.3), SPARQL helpers (0.6).

Requirements

Install

pip install triplemodel

Quick start

from triplemodel import TripleModel, rdf_field

FOAF = "http://xmlns.com/foaf/0.1/"

class Person(TripleModel):
    class Rdf:
        namespace = "http://example.org/people/"
        type_uri = f"{FOAF}Person"
        id_field = "slug"

    slug: str
    name: str = rdf_field(f"{FOAF}name")
    age: int | None = rdf_field(f"{FOAF}age", default=None)

alice = Person(slug="alice", name="Alice", age=30)

graph = alice.to_graph()
print(alice.subject_uri())  # http://example.org/people/alice

assert Person.from_graph(graph, alice.subject_uri()) == alice
assert len(Person.all_from_graph(graph)) == 1

Concepts

RDF metadata (class Rdf)

Attribute Role
namespace Base IRI for subject resources
type_uri Emitted as rdf:type; used to filter all_from_graph()
id_field Field value appended to namespace for the subject IRI

Subject IRIs use subject_base(namespace) + percent-encoded id (quote / unquote). Override the subject IRI per call with uri= (round-trip works when the URI still matches namespace):

alice = Person(slug="alice", name="Alice")
custom_uri = "http://example.org/people/alice"
graph = alice.to_graph(uri=custom_uri)
assert Person.from_graph(graph, custom_uri) == alice

Shared helpers (also on the package root):

from triplemodel import id_from_subject_uri, subject_base

base = subject_base("http://example.org/people")  # ensures trailing / or #
id_from_subject_uri("http://example.org/people", "http://example.org/people/alice")  # "alice"

Field → predicate

name: str = rdf_field("http://xmlns.com/foaf/0.1/name")

Or with Annotated:

from typing import Annotated
from triplemodel import Predicate

title: Annotated[str, Predicate("http://purl.org/dc/terms/title")]

Fields without a predicate mapping are skipped on export and import (handy for computed or app-only fields).

Subclasses inherit a parent’s nested Rdf class when the child does not define Rdf. If the child declares class Rdf:, it replaces the parent’s config entirely — do not use an empty nested Rdf on a subclass.

Term conversion

Python RDF (export)
str (not IRI-like) xsd:string literal
str with an RFC 3986 scheme (http:, https:, urn:, mailto:, file:, …) URIRef
int, float, bool, date, datetime XSD-typed literal

Import uses each field’s type annotation. BNode objects cannot be coerced into str fields.

API reference

TripleModel methods

Method Description
Instance subject_uri(uri=None) Subject IRI
Instance to_triples(uri=None) (subject, predicate, object) tuples
Instance to_graph(graph=None, uri=None, mode="add") Serialize into a Graph (mode: add, replace, patch)
Instance sync_to_graph(graph, uri=None, mode="replace") Update owned triples in an existing graph
Class from_graph(graph, uri, validate_type=True, on_duplicate="warn") Load one resource
Class all_from_graph(graph, type_uri=None, validate_type=True, on_duplicate="warn") Load all resources of this type_uri
Class rdf_config() Resolved RdfConfig

Module-level API

Name Description
rdf_field, Predicate, OnDuplicate, GraphMode Field metadata, duplicate policy, sync modes
sync_to_graph, expand_curie, bind_namespaces, merge_graphs Sync, CURIEs, namespaces, graph merge
RdfConfig, TripleModel Config dataclass and base model
model_to_graph, model_to_triples, models_to_graph Export without subclassing
graph_to_model, graph_to_models Import into a model class
subject_base, id_from_subject_uri Subject IRI building and parsing
RDF, RDFS, XSD, RDF_TYPE Common namespace IRIs

Examples

Batch export into one graph

from rdflib import Graph
from triplemodel import TripleModel, models_to_graph, rdf_field

FOAF = "http://xmlns.com/foaf/0.1/"


class Person(TripleModel):
    class Rdf:
        namespace = "http://example.org/people/"
        type_uri = f"{FOAF}Person"
        id_field = "slug"

    slug: str
    name: str = rdf_field(f"{FOAF}name")


people = [
    Person(slug="alice", name="Alice"),
    Person(slug="bob", name="Bob"),
]
graph = models_to_graph(people)

# Or merge into an existing graph (rdflib Graph() is falsy when empty — pass explicitly)
existing = Graph()
models_to_graph(people, existing)

Encoded subject ids

from triplemodel import TripleModel, rdf_field

FOAF = "http://xmlns.com/foaf/0.1/"


class Person(TripleModel):
    class Rdf:
        namespace = "http://example.org/people/"
        type_uri = f"{FOAF}Person"
        id_field = "slug"

    slug: str
    name: str = rdf_field(f"{FOAF}name")


bob = Person(slug="bob jones", name="Bob")
uri = bob.subject_uri()  # .../bob%20jones
restored = Person.from_graph(bob.to_graph(), uri)
assert restored == bob

TripleModel vs SparqlModel

Need Use
Turn a model instance into triples / load from a Graph TripleModel (pip install triplemodel)
Turtle/JSON-LD files, namespaces, datasets (roadmap) TripleModel
session.put, queries, cascade delete, HTTP store SparqlModel

Details: project plan · ecosystem guide.

Limitations (0.2.x)

  • Scalar duplicates — multiple objects on a non-collection field still warn/error via on_duplicate (collections import all values).
  • BNode embedRdf.embed="bnode" is experimental; named IRI embed ("iri") is preferred. replace/patch may leave orphan blank-node subgraphs.
  • RDF lists — use list[T] for multiple objects per predicate, not rdf:List syntax (roadmap).
  • In-memory graphs only — no parse / serialize until 0.4.0.
  • Stale triples on re-export — use sync_to_graph or to_graph(..., mode="replace") to remove cleared fields; default mode="add" only appends.
  • from_graph type check — when Rdf.type_uri is set, import requires that triple unless validate_type=False.
  • uri= overridefrom_graph can only derive id_field when the subject URI is under Rdf.namespace; off-namespace URIs fail validation unless you add triples another way.
  • Empty child class Rdf: — shadows the parent and clears namespace / type_uri / id_field; omit Rdf on the child to inherit.
  • type_uri="" or other falsy config — treated as unset (no rdf:type on export, no type filter on import).
  • id_from_subject_uri — returns the URI suffix after the namespace base (may include extra / segments); not a single-segment validator.
  • id_field values False or 0 — are valid ids (not treated as empty).
  • BNode subjects — skipped in all_from_graph().
  • Non-XSD boolean literalsbool fields without xsd:boolean use a loose truthiness heuristic on import.
  • Union field types (e.g. str | int) rely on rdflib toPython() when the annotation is not a single scalar type.
  • Rdf.graph_mode — used when mode= is omitted on to_graph() / model_to_graph(); sync_to_graph() uses it when not "add", otherwise defaults to "replace".
  • Multi-value collectionslist[T] / set[T] are for scalar T only; list[TripleModel] / set[TripleModel] are rejected until a future release.

Development

git clone https://github.com/eddiethedean/triplemodel.git
cd triplemodel
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pytest
ruff format src tests && ruff check src tests
ty check src tests
sphinx-build -b html docs docs/_build/html -W
PYTHONPATH=src python examples/readme_examples.py

CI runs on Python 3.10, 3.11, 3.12, and 3.13. Release steps: RELEASING.md.

Documentation

Read the Docs: triplemodel.readthedocs.io

Doc Description
User guides Step-by-step topics (mapping, sync, nested models, …)
API reference Generated from docstrings
Docs sources Sphinx / MyST source on GitHub
CHANGELOG Release notes
Roadmap Versions and rdflib parity
Plan Strategy and priorities
Ecosystem triplemodel ↔ SparqlModel boundaries

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

triplemodel-0.2.0.tar.gz (65.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

triplemodel-0.2.0-py3-none-any.whl (35.4 kB view details)

Uploaded Python 3

File details

Details for the file triplemodel-0.2.0.tar.gz.

File metadata

  • Download URL: triplemodel-0.2.0.tar.gz
  • Upload date:
  • Size: 65.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for triplemodel-0.2.0.tar.gz
Algorithm Hash digest
SHA256 4412a20ea67b9ede3c1a2d358fed560ddcf011368d9b5500082adb9375a86db7
MD5 088d91ca18e2edd66f22d4c8d2cebe95
BLAKE2b-256 85d38e0b26831bad399e1a330e6053d5f5050d778c26b2789c6f51cc1b94e7c6

See more details on using hashes here.

File details

Details for the file triplemodel-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: triplemodel-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 35.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for triplemodel-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c55e017be7c8fcf972d4252ba7bc3179d40435af5690591c2764386e914df28e
MD5 e0172f07e920fac8b08d5ed74a25cf48
BLAKE2b-256 c86235bae02b7302d52befdf59928fa534cfc8cca7a3d1960e55366c2f85a10f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page