Skip to main content

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

Project description

TripleModel

CI PyPI Python 3.10+ License: MIT Documentation

Typed Pydantic models for RDF. Declare fields once, get correct triples in and out of rdflib Graph objects — no manual graph.add for every property.

Install pip install triplemodel
Import from triplemodel import TripleModel, rdf_field
Docs triplemodel.readthedocs.io
Person(slug="alice", name="Alice")  →  (ex:alice, foaf:name, "Alice")  →  Person(...)

TripleModel is the mapping layer between Pydantic-shaped domain models and RDF triples: subject IRIs, XSD literals, nested resources, rdf:List, language tags, and graph sync. It is stateless and in-memory today; SparqlModel (sessions, SPARQL, ORM) is planned to build on top — see the ecosystem guide.

0.3.0 is alpha. APIs may change before 1.0. See the changelog and roadmap.

Install

pip install triplemodel

Requirements: Python 3.10+, Pydantic 2, rdflib 7.

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()

assert Person.from_graph(graph, alice.subject_uri()) == alice
print(alice.subject_uri())
http://example.org/people/alice

Unmapped fields are ignored on export/import — useful for computed or application-only data.

Features

Area Capability
Mapping Nested class Rdf + rdf_field() or Annotated[..., Predicate(...)]
Identity Subject IRIs from namespace + id_field (percent-encoded ids)
Scalars str, int, float, bool, date, datetime; IRI-like strURIRef
Collections set[T] → multiple objects per predicate; list[T] → ordered rdf:List
Literals LangString, Lang(), OpaqueLiteral, ResourceRef
Nesting Child TripleModel with Rdf.embed "iri" or "bnode"
Graph writes to_graph / sync_to_graph with add, replace, or patch
Namespaces Rdf.prefixes, CURIE predicates ("foaf:name"), bind_namespaces
Typing PEP 561 py.typed

Coming later (roadmap): file parse / serialize (0.4), named graphs (0.5), SPARQL helpers (0.6).

list vs set

Annotation RDF shape
set[str] Multiple objects on one predicate (unordered)
list[str] One ordered rdf:List (rdf:first / rdf:rest)

Use set for tags or duplicate predicates; use list when the graph should contain a real RDF list.

Full example

Language tags, RDF lists, and nested blank-node embeds:

from typing import Annotated

from triplemodel import TripleModel, rdf_field, sync_to_graph
from triplemodel.terms.lang import Lang, LangString
from triplemodel.vocab import DC, FOAF

class Address(TripleModel):
    class Rdf:
        namespace = "http://example.org/address/"
        type_uri = "http://example.org/Address"
        id_field = "slug"

    slug: str = "home"
    street: str = rdf_field("http://example.org/street")


class Person(TripleModel):
    class Rdf:
        namespace = "http://example.org/people/"
        type_uri = f"{FOAF}Person"
        id_field = "slug"
        embed = "bnode"
        prefixes = {"foaf": str(FOAF), "dc": str(DC)}

    slug: str
    title: LangString = rdf_field(f"{DC}title")
    nick: list[str] = rdf_field("foaf:nick", default_factory=list)
    address: Address | None = rdf_field("http://example.org/home", default=None)


person = Person(
    slug="alice",
    title=LangString("Alice's profile", "en"),
    nick=["Al", "Alice"],
    address=Address(street="1 Main St"),
)
graph = person.to_graph()

person.nick = ["Alice"]
sync_to_graph(person, graph, mode="replace")
again = Person.from_graph(graph, person.subject_uri())
print(again.nick)
['Alice']

Runnable version: examples/exit_criteria_03.py (same models; see also examples/doc/snippets/).

How mapping works

class Rdf — resource metadata

Attribute Role
namespace Base IRI; subject = namespace + encoded id_field value
type_uri rdf:type on export; filter for all_from_graph()
id_field Python field whose value becomes the subject id segment
embed "iri" (default) or "bnode" for nested models
prefixes CURIE map → Graph.bind on new graphs
graph_mode Default to_graph mode when mode= is omitted
blank_node_policy "fresh" or "stable" nested bnodes
skolemize_export / skolemize_import Blank-node skolemization defaults

Override the subject per call with uri= when the IRI still lives under namespace:

graph = alice.to_graph(uri="http://example.org/people/alice")

Helpers: subject_base(), id_from_subject_uri().

Fields → predicates

name: str = rdf_field("foaf:name")  # with Rdf.prefixes

from typing import Annotated
from triplemodel import Predicate

title: Annotated[str, Predicate("http://purl.org/dc/terms/title")]
title_fr: Annotated[str, Predicate(f"{DC}title"), Lang("fr")]

Subclasses inherit a parent Rdf when the child does not define one. A child class Rdf: replaces the parent config entirely — never use an empty nested Rdf on a subclass.

Scalar → RDF (export)

Python RDF
str (plain) xsd:string
str with URI scheme (http:, urn:, …) URIRef
int, float, bool, date, datetime XSD literal
LangString, ResourceRef, OpaqueLiteral matching literal / IRI

Register custom types with register_literal_type and LiteralRegistry.

Updating an existing graph

Default to_graph() uses mode="add" — it only appends. To remove triples when fields are cleared, use sync modes:

from triplemodel import sync_to_graph

sync_to_graph(person, graph, mode="replace")  # replace all owned triples for this subject
sync_to_graph(person, graph, mode="patch")    # per-predicate replace; lighter touch

replace clears nested IRI children and list heads before re-export; patch updates predicates present in the model and clears empty fields (including on nested resources). See the updating graphs guide.

API overview

Instance & class methods

Method Description
subject_uri(uri=None) Subject IRI for this instance
to_triples(uri=None) (subject, predicate, object) rows
to_graph(graph=None, uri=None, mode="add", skolemize=None) Serialize into a Graph
sync_to_graph(graph, uri=None, mode="replace", skolemize=None) Sync owned triples in-place
from_graph(graph, uri, ...) Load one resource
all_from_graph(graph, type_uri=None, ...) Load all resources of this type
rdf_config() Resolved RdfConfig

Common imports

from triplemodel import (
    TripleModel,
    rdf_field,
    Predicate,
    GraphMode,
    sync_to_graph,
    models_to_graph,
    merge_graphs,
    expand_curie,
    bind_namespaces,
    LangString,
    Lang,
    ResourceRef,
    OpaqueLiteral,
    RDF,
    XSD,
)

Full API: Read the Docs API reference.

More examples

Batch export

from rdflib import Graph
from triplemodel import models_to_graph

graph = models_to_graph([alice, bob])
models_to_graph([alice, bob], Graph())  # merge into existing graph

Encoded subject ids

bob = Person(slug="bob jones", name="Bob")
print(bob.subject_uri())
http://example.org/people/bob%20jones

Multiple tags on one predicate — use set[str] = rdf_field("foaf:topic", default_factory=set).

Runnable scripts: examples/exit_criteria_03.py, examples/readme_examples.py, and examples/doc/snippets/.

TripleModel vs SparqlModel

You need Package
Pydantic ↔ triples on an in-memory Graph triplemodel
File I/O, datasets, SPARQL sessions, cascade put SparqlModel (planned TripleModel dependency)

Known limitations

  • In-memory only until 0.4 (parse / serialize on the roadmap).
  • BNode embed is experimental; prefer embed="iri" for stable linking.
  • Collectionslist[T] / set[T] require scalar T; list[TripleModel] is not supported.
  • BNode subjects are skipped by all_from_graph().
  • Default add mode does not remove stale triples — use sync_to_graph or mode="replace".

Details: user guides · RDF lists & lang.

Development

git clone https://github.com/eddiethedean/triplemodel.git && cd triplemodel
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
ruff format src tests && ruff check src tests
ty check src tests
PYTHONPATH=src python examples/exit_criteria_03.py
PYTHONPATH=src:. python examples/doc/regenerate_outputs.py  # refresh doc output files

CI: Python 3.10–3.13; tests/test_doc_examples.py runs every snippet under examples/doc/snippets/. Release process: RELEASING.md.

Documentation

Resource Link
User guides guides index
API reference api
Changelog CHANGELOG.md
Roadmap docs/ROADMAP.md
Ecosystem docs/ECOSYSTEM.md

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.3.0.tar.gz (79.5 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.3.0-py3-none-any.whl (42.7 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for triplemodel-0.3.0.tar.gz
Algorithm Hash digest
SHA256 0ae83f7c474a46e50ea2042fc5e0e637ea711c1580a76c057d9c858128a9c0ec
MD5 de4d6c4c1ed4269cf046acbbe1bdfbe9
BLAKE2b-256 09f51201d5e21b902cff8ee4967517b09de4059b1459a9b9f36bb82ff17d89a6

See more details on using hashes here.

File details

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

File metadata

  • Download URL: triplemodel-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 42.7 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ee769094aa91f541b1cef411a89047cbf8cf9d4068dfa737d43999af778ccd24
MD5 00f9d0a35b30d50e2bf298eba3cae859
BLAKE2b-256 43263bcd72c68ec8e7dc1fb896821aa45148ca91a4f91212a1000878a4c9c2d6

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