Skip to main content

TripleModel

CI PyPI Python 3.10+ License: MIT Documentation

Typed Pydantic models for RDF. Declare fields once, get correct triples in and out of pyoxigraph Store objects — no manual quad writes 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, graph sync, and file parse/serialize. It is stateless (no ORM session); SparqlModel (sessions, SPARQL, ORM) builds on top — see the ecosystem guide.

0.12.0 is beta. Public API is frozen from 0.9 until 1.0 — see API stability, changelog, migration 0.11 (and 0.10 pyoxigraph). 0.12.0 is additive (no migration guide). See roadmap.

Install

pip install triplemodel

Requirements: Python 3.10+, Pydantic 2, pyoxigraph 0.5+.

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(...)]; predicate validation at class definition
Identity Subject IRIs from namespace + id_field (percent-encoded ids); IriId for full-IRI ids
Scalars str, int, float, bool, date, datetime; IRI-like strURIRef; literal_datatype (e.g. xsd:gYear)
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"; ref_field for URI foreign keys
Typing Rdf.instance_of for Wikidata-style property classification (wdt:P31, etc.)
Graph writes to_graph / sync_to_graph with add, replace, or patch
Namespaces Rdf.prefixes, CURIE predicates ("foaf:name"), bind_namespaces
Named graphs Rdf.graph_iri, to_dataset / from_dataset, TriG / N-Quads via Dataset
File I/O parse / parse_file / parse_url, serialize, load_graph, load_dataset, load_models, load_models_from_graph, load_models_from_dataset, dump_model (Turtle, TriG, N-Triples, JSON-LD, …)
Dispatch parse(..., dispatch=True), graph_to_model_dispatch, all_from_graph_dispatch by rdf:type
Inverse predicates rdf_field(..., inverse=...) for import; forward predicate on export
Package typing PEP 561 py.typed
SPARQL ask, construct_models, select_models, load_sparql, apply_update, prepare_model_query
Graph algorithms graphs_equal, graph_diff, model_diff, cbd_graph / cbd_model, hydrate_refs, model_join
RDFS Subclass-aware dispatch, transitive_objects / transitive_subjects, VocabularyRegistry, Transitive import
Scale & stores iter_graph_to_models, load_models_streaming, parse_into_store_graph, open_graph (memory / disk)
Strict import Rdf.strict_import, Rdf.warn_unmapped_fields on graph_to_model
Plugins triplemodel.pluginsregister_predicate_resolver, literal/resource registration
Codegen Experimental triplemodel-codegen CLI (OWL/RDFS → stub models)

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
base_uri Default base IRI for resolving relative IRIs on parse
jsonld_context Reserved for API stability; not applied on pyoxigraph (issues UserWarning)

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", ...) Serialize into a Graph
sync_to_graph(graph, *, uri=None, mode="replace", ...) Sync owned triples in-place on this instance
from_graph(graph, uri, *, resolver=, registry=, ...) Load one resource
all_from_graph(graph, *, type_uri=None, resolver=, registry=, de_skolemize=, ...) Load all resources of this type
parse / parse_file / parse_url Parse RDF and return list[TripleModel] (class methods; optional dispatch=True)
serialize Write instance triples to a file or string
rdf_config() Resolved RdfConfig

Common imports

from triplemodel import (
    TripleModel,
    rdf_field,
    ref_field,
    Predicate,
    IriId,
    GraphMode,
    sync_to_graph,
    models_to_graph,
    load_graph,
    load_models,
    load_models_from_graph,
    load_models_from_dataset,
    load_dataset,
    parse_into_dataset,
    model_to_dataset,
    models_to_dataset,
    get_graph_context,
    graph_to_model_dispatch,
    all_from_graph_dispatch,
    ask,
    apply_update,
    construct_models,
    select_models,
    load_sparql,
    open_sparql_graph,
    prepare_model_query,
    run_sparql,
    init_bindings_from_model,
    init_ns_from_model,
    graph_from_construct_result,
    merge_graphs,
    expand_curie,
    bind_namespaces,
    LangString,
    Lang,
    ResourceRef,
    OpaqueLiteral,
    RDF,
    XSD,
)

Full API: Read the Docs API reference.

More examples

Batch export

from triplemodel import Store
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, examples/realworld/ (Nobel, DCAT, Wikidata, Schema.org), and examples/doc/snippets/.

TripleModel vs SparqlModel

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

Known limitations

  • Union queriesfrom_dataset reads one named graph only; use Dataset.query for union semantics (see the datasets guide).
  • BNode embed is experimental; prefer embed="iri" for stable linking.
  • Collectionslist[T] / set[T] require scalar T; list[TripleModel] and set[TripleModel] are not supported.
  • Inverse predicates — import uses forward or inverse triples (forward wins on conflict); sync_to_graph in add, replace, or patch clears incoming inverse triples before writing forward predicates (including reassignment and dropped nested IRI/bnode children). Export writes forward predicates only.
  • Discoveryall_from_graph() and dispatch discovery match subjects via forward owned predicates (and rdf:type / instance_of); resources reachable only through inverse triples are not discovered.
  • Dispatch parseparse(..., dispatch=True) loads every registered rdf:type (not only the class you call .parse on); type_uri= is ignored when dispatch=True.
  • Skolemizeskolemize / de_skolemize on import or export mutate the entire shared Store, not only the resource being loaded or synced. sync_to_graph(..., mode="patch") runs skolemize after cleanup and export; replace / add skolemize via write_model_add before appending new triples.
  • Remote SPARQL graphopen_sparql_graph and load_sparql raise NotImplementedError in 0.10.0; load remote data into a local Store (see SPARQL guide).
  • BNode embed + fresh policy — with default blank_node_policy="fresh", replace / patch remove and re-export nested blank nodes on every sync even when the nested value is unchanged; use embed="iri" or blank_node_policy="stable" for stable identities.
  • BNode subjects are skipped by all_from_graph() and by parse(..., dispatch=True) / all_from_graph_dispatch().
  • Subclass dispatchparse(..., dispatch=True) and all_from_graph_dispatch() resolve subjects via Rdf.resolve_subclass (RDFS closure when enabled); unregistered types are omitted without error.
  • Default add mode does not remove stale triples — use sync_to_graph or mode="replace".
  • sync_to_graph() defaults to replace when Rdf.graph_mode is "add" (unlike to_graph(), which defaults to add).

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,docs]"
make ci              # pytest, lint, docs (matches GitHub Actions)
make release-check   # before tagging: examples + twine check

CI: Python 3.10–3.13. 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.

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.12.0.tar.gz (222.1 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.12.0-py3-none-any.whl (113.4 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for triplemodel-0.12.0.tar.gz
Algorithm Hash digest
SHA256 4443fe48a45f09a7deae17f5a159ed325756516e686888413b6c456ef601610d
MD5 de6564c5f6c384c04f5b23175ba8b2ba
BLAKE2b-256 a3e8acbc46f6087ea622eff748cce4aef69ef2e47a2f5f392569dac98d69e996

See more details on using hashes here.

File details

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

File metadata

  • Download URL: triplemodel-0.12.0-py3-none-any.whl
  • Upload date:
  • Size: 113.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.12.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2a4a201e3bd361070c035fe33ba3257dd9bf99d8b398a1fc6865eb5c2273ae17
MD5 e2c5795366c7347bc8b3eca14917113d
BLAKE2b-256 dd502b878cf98dfb2bac9a24d2e9d13482d8166bbbad4023bb788bc96df8d905

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.12.0 This release

2 files

0.11.0

2 files

0.10.1

2 files

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

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