Python API¶
The reasonable Python package exposes the Rust reasoner through
PyO3 bindings. Pre-built wheels are on PyPI for
Python 3.9+ on macOS, Linux, and Windows.
pip install reasonable
The main class is PyReasoner. Create one instance per reasoning
session; it accumulates base triples and manages incremental state.
import reasonable
r = reasonable.PyReasoner()
PyReasoner¶
PyReasoner()Create a new reasoner instance with an empty triple store.
load_file(path: str) -> NoneAppend all triples from a Turtle or N3 file at path to the base graph. Raises
OSErrorif the file is missing or cannot be parsed. Call multiple times to load several files.from_graph(graph_or_iterable) -> NoneAppend triples from an
rdflib.Graphor any iterable of(subject, predicate, object)3-tuples. Useupdate_graph()instead when you need retraction support.update_graph(graph_or_iterable) -> boolReplace the base triples with the contents of the given graph. Computes a diff against the current base:
If only additions are found, the next
reason()uses incremental materialisation.If any removals are detected, the next
reason()performs a full re-materialisation.
Returns
Truewhen removals were detected.reason() -> list[tuple[Node, Node, Node]]Run OWL 2 RL materialisation and return all known triples (base plus inferred) as rdflib nodes. After the first call, subsequent calls are incremental unless removals were detected via
update_graph().reason_full() -> list[tuple[Node, Node, Node]]Force a full re-materialisation from base triples, ignoring any incremental state. Equivalent to
clear()followed byreason().clear() -> NoneReset all inferred state while keeping base triples. The next
reason()call will perform a full re-materialisation.get_base_triples() -> list[tuple[Node, Node, Node]]Return the current base (non-inferred) triples as rdflib nodes. Useful for debugging.
Examples¶
Collect inferred triples into a new graph:
import rdflib
import reasonable
g = rdflib.Graph()
g.parse("ontology.ttl")
g.parse("data.ttl")
r = reasonable.PyReasoner()
r.from_graph(g)
result = rdflib.Graph()
for triple in r.reason():
result.add(triple)
print(f"{len(result)} triples after materialisation")
Incremental update with retraction:
import rdflib
import reasonable
from rdflib import URIRef, RDF
ontology = rdflib.Graph()
ontology.parse("ontology.ttl")
data = rdflib.Graph()
data.add((URIRef("urn:sensor1"), RDF.type, URIRef("urn:TemperatureSensor")))
r = reasonable.PyReasoner()
r.from_graph(ontology + data)
r.reason()
# data changes…
data.remove((URIRef("urn:sensor1"), RDF.type, URIRef("urn:TemperatureSensor")))
data.add((URIRef("urn:sensor2"), RDF.type, URIRef("urn:HumiditySensor")))
r.update_graph(ontology + data)
triples = r.reason()
Note
from_graph() appends to existing base triples. If you want
to replace the base entirely, use update_graph() instead.
Building from source¶
cd python
uv sync --group dev
uv run maturin develop -b pyo3 --release
# verify
uv run python -c "import reasonable; print(reasonable.__version__)"
Run the test suite:
cd python
uv run pytest -q