SparqlModel ORM guide
SparqlModel — the SQLModel of SPARQL: typed models, a persistent session, and Python queries that compile to SPARQL.
This guide is for application developers. SparqlModel is the ORM; TripleModel (triplemodel>=0.12.0 for SparqlModel 0.13+; >=0.10.0 for older releases), installed automatically, is the mapping engine underneath. In-process graphs use pyoxigraph via triplemodel.Store.
Related: Guides · SparqlModel Technical Specification · SparqlModel (ORM) + TripleModel (mapping engine) · SparqlModel Roadmap · Troubleshooting
Two packages, one stack
SparqlModel |
TripleModel |
|
|---|---|---|
Role |
ORM — run apps on graphs |
Mapping — correct triples from Pydantic |
Metaphor |
SQLModel / SQLAlchemy ORM |
SQLAlchemy Core / serde |
Entry point |
|
|
Dependency |
Requires |
Standalone library |
Your app
→ SPARQLModel(TripleModel) — one class (Option A; 0.4+)
→ SPARQLSession.put / .query / .get
→ sync_to_graph / from_graph + graph.py (cascade policy)
→ triplemodel.Store in a Store
0.4.0+: session I/O uses sparqlmodel.rdf_bridge on unified SPARQLModel(TripleModel) instances.
Rule of thumb: if your code never constructs a SPARQLSession, you probably want TripleModel only.
Pydantic integration
SPARQLModel is a Pydantic v2 model that subclasses TripleModel (Option A, target 0.4) with a merged metaclass for the query DSL. That is the same positioning as SQLModel relative to SQLAlchemy: one class for mapping and ORM ergonomics.
Layer |
Validation |
|---|---|
Construct |
|
Persist |
|
Load |
|
Config |
|
Field("schema:name", ...) and Relationship(...) wrap pydantic.Field and forward keyword arguments (min_length, ge, description, defaults, and so on) for scalar and relationship annotations.
FastAPI: use the same SPARQLModel classes as request/response models; OpenAPI schema comes from Pydantic. See FastAPI integration.
Errors: Pydantic ValidationError on load is wrapped as HydrationError for a single ORM-facing exception; configuration problems (for example hydration cycles) stay as ConfigurationError.
Task-oriented examples: Models and Pydantic validation. Normative detail: SparqlModel Technical Specification (validation architecture).
Choose your package
I need… |
Use |
|---|---|
CRUD, queries, cascade in a backend or API |
SparqlModel |
Python filters → SPARQL ( |
SparqlModel |
HTTP SPARQL store ( |
SparqlModel |
Load a Turtle file into models without a session |
TripleModel |
ETL, tests, libraries, one-off |
TripleModel |
What you import from SparqlModel
from sparqlmodel import (
SPARQLSession, # sync — start here
AsyncSPARQLSession, # asyncio / FastAPI async routes (0.6+)
SPARQLModel,
Field,
Relationship,
IRI,
HttpStore, # optional: sparqlmodel[http]
)
SPARQLSession— unit of work over a storeSPARQLModel— entity class (SQLModel-style); subclassesTripleModel(0.4+)Field/Relationship— ORM sugar overrdf_field/Predicate
You generally do not import triplemodel in application code unless you are mixing stateless file I/O with session usage — e.g. bulk TripleModel.parse() then session.put() per row.
ORM lifecycle
Method |
Semantics |
|---|---|
|
Insert triples only; does not remove existing triples for the subject. |
|
Upsert: remove owned triples (root, embedded tree, orphans), then write current state. Uses mapping layer + SparqlModel cascade policy. |
|
Remove owned triples for root and embedded composition targets. |
|
Load one entity; |
|
Find entities; filters compile to SPARQL. |
|
Raw SPARQL SELECT. |
|
Apply or discard queued |
|
Close the backing store when it implements |
|
Evict cached instances for an IRI. |
|
Detach one instance from the identity map (store unchanged). |
|
Clear identity map and hydration cache (pending queue unchanged). |
|
Reload from the store; update cached instance when present. |
|
Return the session’s canonical instance for that identity key (no store write). |
with SPARQLSession() as session:
session.put(person)
session.put(other, flush=False)
# pending queue flushed on clean exit
with SPARQLSession(store=HttpStore("https://example.org/sparql")) as remote:
hits = remote.query(Person).where(Person.name == "Alice").all()
On exception, the context manager calls rollback_pending() (discard the queue only — already-flushed writes stay). Set rollback_on_error=False to keep pending across errors, or close_on_exit=False when the store is managed elsewhere.
Async session (0.6+)
For FastAPI and other asyncio apps, use AsyncSPARQLSession with AsyncMemoryStore or AsyncHttpStore (sparqlmodel[http] — same httpx extra as sync). The sync session API is unchanged.
from sparqlmodel import AsyncSPARQLSession, IRI
from sparqlmodel.stores.async_http import AsyncHttpStore
async with AsyncSPARQLSession(store=AsyncHttpStore("https://example.org/sparql")) as session:
await session.put(person)
results = await session.query(Person).where(Person.name == "Odos").all()
one = await session.get(Person, person.id, depth=1)
Topic |
Guidance |
|---|---|
When to use async |
Remote SPARQL over the network in async routes; avoid blocking |
When sync is fine |
Scripts, tests, |
Concurrency |
One |
FastAPI |
|
Compiler / hydration |
Same rules as sync; only store I/O and session entry points are |
Composition and cascade
SparqlModel-only persistence policy (TripleModel does not define multi-resource cascade):
Value on a relationship |
On |
|---|---|
Nested |
Composition — cascade owned triples; orphan cleanup when links change |
Nested with |
Reference — link only; nested triples not written or removed by root |
|
Reference — update link; do not delete the target resource |
TripleModel’s sync_to_graph syncs one resource’s owned triples. SparqlModel’s put decides which subjects to remove across the composition tree before calling into the mapper.
Query DSL
with SPARQLSession() as session:
session.query(Person).where(Person.name == "Odos").all()
session.query(Person).where(Person.works_for.name == "Acme Corp").all()
==— triple pattern match!=— defaultNOT EXISTS(0.5.2+); optional.use_inequality_for_ne()for legacy inequality&/|—AndExpr/OrExpr<,>,<=,>=,.in_(tuple)— ordering and membership filtersNone→QueryErrorMulti-hop paths (
Person.works_for.located_in.name) require relatedrdf:typein the graph
Compiler detail: SPECS.md.
Hydration
with SPARQLSession() as session:
session.get(Person, iri, depth=0) # scalars
session.get(Person, iri, depth=1) # one relationship hop
ORM eager-load. Query .all(depth=1) and .first(depth=1) accept the same parameter.
Loading scalars and relationships uses hydration.py over TripleModel from_graph on SPARQLModel instances (0.4+; 0.3 via interim _triple.py).
HttpStore and the local mirror
HttpStore (sparqlmodel[http]) sends updates to a SPARQL 1.1 endpoint and keeps a local triplemodel.Store mirror for session.graph, get, cascade, and orphan logic.
Operation |
Reads / writes |
|---|---|
|
Remote + mirror |
|
Remote only |
|
Mirror only |
If another process changes the remote dataset, execute may return IRIs that get cannot load until this store instance has applied the same triples to its mirror. Use MemoryStore for single-process apps; treat each HttpStore as the single writer for its endpoint.
Export and file I/O
Preferred for files without a session (0.7+):
# persons = Person.parse("data.ttl")
print(person.serialize(format="turtle"))
Backward-compatible wrappers:
from sparqlmodel.serializers import export_model, import_graph
export_model(person, format="turtle")
New RDF formats are added in TripleModel only. model_dump_jsonld() is for API dicts (cascade-aware); file JSON-LD uses serialize(format="json-ld").
Current integration status
Capability |
Owner today |
Direction |
|---|---|---|
Literals, XSD, subject IRIs |
TripleModel (package dep) |
Via |
|
SparqlModel cascade + TripleModel |
Direct on instances (0.4) |
|
SparqlModel depth + |
Unified subclass (0.4) |
Interim |
Shipped 0.3 |
Remove 0.4 |
Turtle / JSON-LD export |
Thin |
TripleModel (0.7 shipped) |
Query compiler |
SparqlModel only |
Stays in SparqlModel |
See ROADMAP.md for milestones.
Production readiness
Today (0.2.0): Suitable for prototypes, tests, single-process apps (MemoryStore), and early FastAPI services. Remote HttpStore requires understanding the mirror model (query vs get).
Target (0.15): Production-grade SPARQL ORM with SQLModel-parity sessions and queries. Track progress:
Production checklist — normative P0 / P1 / P2 gates
Roadmap — Forward roadmap — versions 0.7–0.15
SQLModel parity checklist — quick mapping from SQL habits
Production guide — deployment and HttpStore operations
Shipped in 0.13.0: Multi-valued fields (set/list scalars and refs), LangString / MultiLangString, polymorphic query, property paths, not_, VALUES, IRI string filters, SchemaRegistry, back_populates. Production HttpStore (mirror semantics, HTTP resilience, GSP sync_mirror) shipped through 0.12.0 — see PRODUCTION. See SPARQLMojo parity backlog. Session cache control (merge, refresh, expunge, expunge_all) shipped in 0.9.0. Query lists (offset, order_by, count, nullable OPTIONAL) shipped in 0.8.0. Async session and stores shipped in 0.6.0.
When not to use SparqlModel
Use TripleModel alone when:
There is no long-lived session
You only need correct triples or file round-trip
You are building a library that should not depend on ORM semantics
Use SparqlModel when:
You are building an application or API over a triple store
You need
put/deletecascade and Pythonicwhere()filters
Further reading
TripleModel docs — mapping, terms, files
ECOSYSTEM.md — maintainer boundaries and module retirement
ROADMAP.md — ORM features and wiring schedule
PRODUCTION.md — deployment and HttpStore operations
PLAN.md — product vision and parity tiers