TripleModel and SparqlModel — separation of responsibilities
Both projects wrap Pydantic and pyoxigraph (TripleModel 0.10+). They share a maintainer and a long-term direction: SparqlModel depends on triplemodel>=0.9,<2 today; adopt >=0.10,<2 when ready (SM-7). SparqlModel 0.4 (Option A) makes SPARQLModel a TripleModel subclass — one class, one mapping path. This document is the contract for what each package owns.
Naming: PyPI/install name triplemodel; base class TripleModel; project title TripleModel.
Doc |
Purpose |
|---|---|
Strategy, priorities, integration gates |
|
Releases, pyoxigraph matrix, SM-* milestones |
|
SparqlModel maintainer guide (copy into SparqlModel repo) |
SparqlModel maintainers: start with ECOSYSTEM_SPARQLMODEL.md for module retirement and PR boundaries.
┌──────────────────────────────────────────┐
│ SparqlModel (sparqlmodel) │
│ ORM · session · queries · stores │
└────────────────────┬─────────────────────┘
│ depends on (shipped >=0.9)
┌────────────────────▼─────────────────────┐
│ triplemodel (PyPI) │
│ TripleModel · Pydantic ↔ RDF · I/O │
└────────────────────┬─────────────────────┘
│
┌────────────────────▼─────────────────────┐
│ pyoxigraph · pydantic │
└──────────────────────────────────────────┘
Rule: dependency flows downward only. triplemodel must never import sparqlmodel.
triplemodel — the mapping layer
Tagline: Typed Pydantic models ↔ RDF graphs (terms, triples, files).
User question it answers: “How do I turn this Python object into correct triples (and back) without hand-writing every predicate?”
Owns
Area |
Examples |
|---|---|
Model metadata |
Nested |
Subject identity |
|
Term conversion |
Python scalars ↔ |
Graph serialization |
|
Field ↔ predicate |
Single- and multi-valued fields, nested embedded models (roadmap) |
Namespaces |
Prefixes, CURIE expansion, |
Document I/O |
|
Named graphs |
|
RDF engine coverage |
Matrix in ROADMAP.md for features that map to typed models |
Does not own
Sessions, identity maps, or unit-of-work lifecycle
Python query expressions or SPARQL compilation
put/deletecascade or orphan cleanup policiesHTTP SPARQL endpoints or store plugins (except thin passthrough where noted in the matrix)
FastAPI or web framework integration
Application-level “repository” patterns
Typical callers
ETL and data pipelines
Tests and fixtures (round-trip assertions)
Libraries (e.g. SparqlModel) that need stable graph mapping
Scripts that load a file → Pydantic → transform → serialize
API shape
Stateless and explicit: you pass a Graph (or get one back). No hidden global graph.
person = Person(slug="alice", name="Alice")
g = person.to_graph()
restored = Person.from_graph(g, person.subject_uri())
SparqlModel — the ORM layer
Tagline: SPARQL-native object graph mapper for triple stores.
Repo: github.com/eddiethedean/sqarqlmodel · PyPI: sparqlmodel
User question it answers: “How do I build an app that CRUDs and queries RDF data like a small database?”
Owns
Area |
Examples |
|---|---|
Session |
|
Store abstraction |
|
Query DSL |
|
SPARQL compiler |
Python comparisons → SPARQL WHERE (and extensions: OR, joins, …) |
Hydration |
Load by IRI with relationship |
Relationship semantics |
Embedded models vs |
Persistence policy |
Owned triples, orphan cleanup, |
App integration |
FastAPI extras, remote SPARQL (roadmap) |
Raw SPARQL |
|
Does not own (delegates to TripleModel, once integrated)
Canonical
python_to_term/term_to_pythonPredicate metadata resolution and duplicate-predicate rules
File format registry and
Graph.parse/serializewrappersSubject IRI rules shared across projects
Named-graph quad I/O primitives
Typical callers
Web APIs and services
Interactive apps with filters and updates
Prototypes against in-memory or remote SPARQL endpoints
API shape
Stateful: a session holds a store/graph; queries and writes go through the session.
session = SPARQLSession()
session.put(person)
found = session.query(Person).where(Person.name == "Odos").first()
Decision guide
When choosing a package (or deciding where a feature belongs):
If you need… |
Package |
|---|---|
Round-trip a model from an existing |
TripleModel |
Load/save Turtle, JSON-LD, Trig files |
TripleModel |
Shared vocabulary / term conversion bugs fixed once |
TripleModel |
|
SparqlModel |
|
SparqlModel |
SPARQL endpoint over HTTP |
SparqlModel |
FastAPI RDF responses |
SparqlModel |
Raw |
TripleModel passthrough on |
Triple ownership (0.2+)
TripleModel owns mapped predicates for a subject: sync_to_graph / mode="replace" removes prior (subject, predicate, ?) triples for predicates declared on the model (plus rdf:type when configured), then writes the current field values.
SparqlModel owns session policy beyond that: cascade to related resources, orphan cleanup when a parent is deleted, and which related IRIs are included in a put.
SparqlModel should call triplemodel.sync_to_graph (or equivalent) for the resource’s owned triples, then apply its own rules for linked resources.
When implementing a feature:
Touching… |
Belongs in |
|---|---|
“This |
TripleModel |
“Re-export dropped a triple on update” |
TripleModel (sync/merge) + SparqlModel policy |
“ |
SparqlModel compiler |
“Two parents deleted the same embedded IRI” |
SparqlModel cascade rules (may call TripleModel for triple sets) |
“TriG named graph round-trip” |
TripleModel; SparqlModel uses it via session/store |
Public API convergence (target)
Today the two libraries use different surface names; convergence is intentional, not required to be identical.
Concept |
TripleModel |
SparqlModel (current) |
Notes |
|---|---|---|---|
Base model |
|
|
Option A target SparqlModel 0.4; 0.3 uses interim adapter |
RDF type |
|
|
Unify via prefixes + expansion in TripleModel |
Predicates |
|
|
Same metadata; different constructors |
Subject id |
|
|
TripleModel may add explicit |
Prefixes |
|
|
Single implementation in TripleModel |
Export graph |
|
via internal graph + |
SparqlModel calls TripleModel |
Import graph |
|
|
SparqlModel adds depth and relationships |
SparqlModel integration status
Shipped: sparqlmodel requires triplemodel>=0.9,<2. Session I/O uses TripleModel sync_to_graph / from_graph (0.3 via interim _triple.py adapter).
Next (SM-6 / SparqlModel 0.4): SPARQLModel(TripleModel) — delete dynamic adapter; Field / Relationship as sugar over rdf_field / Predicate.
SparqlModel-specific behaviour (cascade, query compiler, session, async stores) stays in SparqlModel.
Optional extras (unchanged split)
Extra |
Package |
|---|---|
|
SparqlModel |
|
SparqlModel dev / optional extra |
Summary
TripleModel = what the data is in RDF (mapping + files + pyoxigraph-backed graphs).
SparqlModel = how an application uses that data (session, queries, updates, stores).
Keep TripleModel thin, library-friendly, and stateless. Keep SparqlModel opinionated about persistence and querying. Share one mapping implementation; do not share one public API.