File I/O (parse and serialize)
TripleModel maps model classes to pyoxigraph parse and serialize so you can load and save RDF documents directly on a Store or Dataset.
Serialize to a string or file
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"
prefixes = {"foaf": FOAF}
slug: str
name: str = rdf_field("foaf:name")
person = Person(slug="alice", name="Alice")
ttl = person.serialize(format="turtle")
person.serialize(destination="alice.ttl")
Parse from a string, file, or URL
people = Person.parse(data=ttl, format="turtle")
people = Person.parse_file("alice.ttl")
people = Person.parse_url("https://example.org/data.ttl")
Format is inferred from the file suffix when omitted (.ttl → Turtle, .trig → TriG, and so on). When the suffix is unknown to TripleModel’s map, infer_format falls back to pyoxigraph RdfFormat.from_extension / from_media_type.
Parse flags
parse_into_graph, parse_url_into_graph, dataset/model parse accept:
lenient=True— tolerate certain syntax issues (pyoxigraph parser)without_named_graphs=True— drop named-graph structure on importrename_blank_nodes=True— rename blank nodes during parse
You can still pass additional pyoxigraph options via **format_kwargs.
Canonical N-Triples
graph.serialize(format="nt") uses pyoxigraph’s canonical N-Triples writer — useful for stable file diffs of the same graph shape.
URL fetch security
parse_url and parse_url_into_graph use Python’s urllib to fetch remote RDF. Do not pass untrusted URLs without your own allowlist or proxy controls — a malicious URL could target internal networks (SSRF). Load trusted content into a local Store first when data comes from users or external systems.
Base URI for relative IRIs
Set Rdf.base_uri (or pass base= to parse) so relative IRIs in Turtle resolve correctly (base_iri on low-level Store.parse):
class Person(TripleModel):
class Rdf:
namespace = "http://example.org/people/"
base_uri = "http://example.org/people/"
...
JSON-LD context
Rdf.jsonld_context is kept for API compatibility but does not affect parse or serialize on the pyoxigraph backend (TripleModel 0.10). Setting it emits a UserWarning; JSON-LD uses only context embedded in the document.
Subclass dispatch
When a document contains several rdf:type values and you have registered subclasses, use dispatch=True:
instances = TripleModel.parse(data=ttl, format="turtle", dispatch=True)
Each subject is loaded as the most specific registered model class. Note: Person.parse(..., dispatch=True) loads every registered rdf:type in the graph, not only Person; type_uri= is ignored when dispatch=True.
Low-level helpers: graph_to_model_dispatch and all_from_graph_dispatch (accept resolver=, registry=, de_skolemize=).
Import options on class methods
from_graph, all_from_graph, and parse / parse_file / parse_url accept resolver= and registry= for custom predicate resolution and literal conversion. all_from_graph and graph_to_models also accept de_skolemize=.
Inverse predicates
Map owl:inverseOf-style data on import with inverse= on rdf_field or InverseOf metadata (not on list / set fields). Export writes only the forward predicate. On sync_to_graph(..., mode="replace") or mode="patch", all incoming inverse triples for inverse fields are cleared before re-export (including reassignment and dropped nested IRI/bnode children). If both forward and inverse triples exist for the same field, import uses the forward objects and warns (or raises with on_duplicate="error").
Paired fields (back_populates)
For bidirectional navigation metadata across two model classes (SparqlModel Relationship(..., back_populates=...) parity), link the inverse-side field with back_populates= on rdf_field. This does not change import/export: one side still uses inverse= for reading inverse triples; the peer field declares the matching forward predicate.
class Person(TripleModel):
employer: str | None = rdf_field(
"ex:employer",
inverse="ex:employee",
back_populates=inverse_pair("Organization", "linked_person"),
)
class Organization(TripleModel):
linked_person: str | None = rdf_field(
"ex:employee",
back_populates=inverse_pair("Person", "employer"),
)
When both model classes are defined, TripleModel validates that each side’s back_populates points back to the other field and that inverse= predicates match the peer’s forward predicate. If the peer class exists but its field does not declare reciprocal back_populates, class creation raises ValueError. Links whose peer class is not imported yet stay pending until that class is defined (typical for split modules). Optional Rdf.ontology_registry = OntologyRegistry(...) checks owl:inverseOf in the ontology file.
Read-only graph navigation (no ORM session):
uris = subjects_via_back_populates(person, "employer", graph)
orgs = models_via_back_populates(person, "employer", graph)
Use a string model name in inverse_pair("Organization", ...) when the peer class is declared later in the same module. Single-field inverse= without a peer model remains valid for one-sided mappings.
Multi-class load (one parse)
When one Turtle file contains several rdf:types (Nobel laureates and prizes, DCAT catalog and datasets):
from triplemodel import load_graph, load_models, load_models_from_graph
bundles = load_models("catalog.ttl", DataCatalog, Dataset, Distribution)
catalogs = bundles[DataCatalog]
graph = load_graph("catalog.ttl", bind_prefixes=DataCatalog.Rdf.prefixes)
bundles = load_models_from_graph(graph, DataCatalog, Dataset)
For a single heterogeneous list by registered rdf:type, use parse_file(..., dispatch=True) instead.
See Real-world patterns (0.4.1) for Wikidata typing, ref_field, and XSD gYear.
Module helpers
from triplemodel import load_models, dump_model
people = load_models("people.ttl", Person)
dump_model(person, "out.ttl", format="turtle")