Graph algorithms and RDFS
TripleModel adds thin wrappers over pyoxigraph-backed graph operations for testing, subgraph extraction, RDFS-aware dispatch, and batch reference hydration. These helpers are not a reasoner or ORM — they complement from_graph / graph_to_model_dispatch.
When to use which helper
Goal |
Helper |
|---|---|
Assert two graphs match in tests |
|
Compare two model instances after sync |
|
Extract a resource’s bounded description |
|
Pick |
|
Batch-load shared ref targets (e.g. countries) |
|
Walk |
|
Central prefix + type registry |
|
Subclass / inverse hints from OWL TTL or static maps |
|
Graph comparison
from triplemodel import graphs_equal, graph_diff, model_diff
assert graphs_equal(g1, g2)
diff = graph_diff(g1, g2)
assert not diff.only_in_a and not diff.only_in_b
changes = model_diff(alice, bob, graph=g) # optional predicate-level diff
graphs_equal(..., normalize_bnodes=True) skolemizes blank nodes before isomorphism checks — useful when graphs were built separately. For graphs parsed in two parse() calls, blank-node identity will not match; see Working with graphs and Safe graph merge below.
Concise bounded description (CBD)
CBD returns the predicate closure around a subject. TripleModel exposes it for import:
from triplemodel import TripleModel, cbd_model
person = cbd_model(Person, graph, subject_uri)
# or
person = Person.cbd(graph, subject_uri)
CBD returns a subgraph, not a fully flattened tree. Nested TripleModel fields still hydrate via embed / ref_field when you call from_graph on the CBD graph. For a Python object graph, prefer Rdf.embed = "bnode" (see Nested models).
Run examples/exit_criteria_07.py for CBD + subclass dispatch.
RDFS subclass dispatch
Register both superclass and subclass models. When a subject is typed with a subclass IRI, dispatch picks the most specific registered class using rdfs:subClassOf in the graph:
from triplemodel import graph_to_model_dispatch, register_rdf_resource
register_rdf_resource(Person)
register_rdf_resource(Agent)
agent = graph_to_model_dispatch(graph, alice_uri)
Rdf.resolve_subclass (default True on RdfConfig) controls this behavior when resolve_model_class() is called without use_subclass=. Disable per class with class Rdf: resolve_subclass = False, or pass use_subclass=False to resolve_model_class_with_rdfs for exact rdf:type matching only.
Bulk loading via all_from_graph_dispatch() uses the same resolution rules as single-subject graph_to_model_dispatch().
Ontology hints (OntologyRegistry)
For SparqlModel-style subclass and inverse metadata without a full reasoner, load a small OWL/RDFS file or register hints statically:
from triplemodel import OntologyRegistry, apply_hints_to_model
reg = OntologyRegistry.from_ttl("ontology.ttl")
assert reg.subtypes_of("http://example.org/Animal") # includes Dog, etc.
assert reg.inverse_of("http://example.org/hasPart") == "http://example.org/partOf"
reg.register_subclasses("http://example.org/Animal", ["http://example.org/Dog"])
reg.register_inverse("http://example.org/hasPart", "http://example.org/partOf")
apply_hints_to_model(MyModel, reg, mutate=True) # sets inverse= on fields when unambiguous
API |
Direction on |
|---|---|
|
Descendants (subtypes / subclasses of |
|
Ancestors (superclasses of |
Use graph-backed subclass_uris when the instance graph carries rdfs:subClassOf axioms; use OntologyRegistry when you ship a separate ontology file or maintain a static map.
Batch reference hydration
ref_field hydrates one nested model per instance per reference — correct but slow when many rows share the same country URI. After a lightweight import:
from triplemodel import hydrate_refs
cities = City.all_from_graph(graph) # partial country refs
cities = hydrate_refs(cities, graph, "country")
For ResourceRef fields, pass spec={"country": Country}. model_join(cities, graph, {"country": Country}) is an alias.
See examples/realworld/wikidata_capitals.py for the Wikidata capitals pattern.
Transitive import (optional)
Mark a set field as transitive to expand multi-hop object URIs on import only (export still writes direct edges):
from triplemodel import rdf_field, Transitive
from typing import Annotated
parts: set[str] = rdf_field("ex:partOf", default_factory=set, transitive=True)
# or Annotated[set[str], Transitive()]
Vocabulary registry
from triplemodel import VocabularyRegistry
reg = VocabularyRegistry()
reg.register(Person)
reg.register(Organization)
reg.bind_vocab(graph)
cls = reg.model_for_subject(graph, subject)
This wraps register_rdf_resource and the internal type_uri index for documented multi-class setups.
Safe graph merge
merge_graphs combines triples as-is. Blank nodes from separate parse() calls are different nodes even when structurally identical. Prefer:
One
parse()/ oneload_graphfor combined data, orSkolemization / stable blank-node policy via
Rdf.blank_node_policy, orgraphs_equal(..., normalize_bnodes=True)only for testing.
Details: Working with graphs.
Catalog patterns (cookbook)
No new APIs — reuse existing examples/realworld/:
Pattern |
Example |
|---|---|
DCAT portal graphs |
|
|
|
Nobel / biographical LOD |
|
See Real-world patterns (0.4.1) for load_models, ref_field, and Wikidata typing.
OWL/RDFS codegen (0.8, experimental)
Experimental codegen (OWL/RDFS → stub TripleModel classes) shipped in 0.8 — see Stores, scale, and strict import and Codegen (experimental).