Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting Started: Python

The Python package wraps the same native Rust engine — not a reimplementation — so parsing, serialization, SPARQL, and validation behave identically to the Rust, JavaScript, and C surfaces.

pip install purrdf

Parsing

import purrdf

quads = purrdf.parse(
    '<https://example.org/alice> <http://xmlns.com/foaf/0.1/name> "Alice" .',
    purrdf.RdfFormat.TURTLE,
)

Validation: SHACL and ShEx

The native validation engines are exposed as top-level submodules mirroring the Rust purrdf umbrella crate — never through the internal purrdf_native extension module directly:

from purrdf import shapes, shex

report = shapes.validate(shapes_ttl=my_shapes, data_nt=my_data)
print(report["conforms"])

results = shex.validate(my_schema_shexc, my_data_ttl,
                        [("https://example.org/alice", "https://example.org/PersonShape")])
print(results[0]["conformant"])

SHACL result dicts keep the stable keys focus, path, value, severity, component, source_shape, and message. See SHACL and ShEx for what the engines cover.

Entailment

purrdf.entail closes a dataset under a SPARQL entailment regime. It is not purrdf.shapes.entail, which applies the SHACL-AF sh:rules a shapes graph declares; this one takes no shapes and uses the regime’s own specification rule table.

import purrdf
from purrdf import entail

dataset = purrdf.RdfDataset(my_turtle, purrdf.RdfFormat.TURTLE)
closure, report = entail.materialize(dataset, "rdfs", "")
print(closure.to_nquads())
print(report)          # what fired, what did not, boundaries, budget, contract hash

The report is the second return value and is never optional — the same discipline the Rust, WebAssembly, and C surfaces enforce. entail.materialize_nt(text, regime) is the text-in/text-out twin for callers holding an N-Triples/N-Quads document.

Coverage is measurable rather than asserted: entail.rules(regime) is the rule table the specification defines the regime by, and entail.implemented_rules(regime) is the subset that fires. "owl-direct" and "rif" return [] here — neither has a specification rule table of its own, since one decides through the tableau and the other entails under the caller’s own rules — not a raised error. See Entailment for the full picture and the rule inventory for the per-rule table.

rdflib compatibility

The package ships an rdflib compatibility layer:

from purrdf.compat.rdflib import Graph

For a literal, zero-change import rdflib, there is an opt-in extra:

pip install purrdf[rdflib]

This pulls in the separate purrdf-rdflib distribution, whose top-level rdflib package re-exports the compat surface, so existing third-party code doing import rdflib / from rdflib.namespace import RDF transparently runs on purrdf. Caveat: that shadow claims the rdflib import name and must never be installed alongside the genuine rdflib — the two cannot co-inhabit one environment. It is a separate distribution (never bundled into the main purrdf wheel) precisely so environments that need the real rdflib simply omit it.

The compat layer is gated in CI against rdflib 7.6’s own vendored test suite plus a first-party differential parity suite — see rdflib Compatibility for details and the known, ledgered divergences.

GTS relational rows

The Python package reads a GTS container back as in-memory relational rows:

from purrdf import gts_relational_rows_from_bytes

rows = gts_relational_rows_from_bytes(gts_bytes)  # terms, quads, reifiers, annotations, blobs

gts_to_sqlite(data, path), gts_to_duckdb(data, path) and gts_to_parquet(data, out_dir) write those same five tables out, in the projection’s own row order — so exporting a container twice produces the same content. SQLite needs nothing beyond the standard library; the other two raise ModuleNotFoundError naming the extra to install (purrdf[duckdb], purrdf[parquet]).

Graph, tabular, and research-object archives

purrdf.project(data, format=..., profile=..., config=...) returns canonical USTAR bytes and structured loss records. purrdf.lift(archive, profile=..., config=...) reconstructs RDF for the ten bidirectional profiles. The same strict configuration and deterministic Rust code paths are used in every host; see Graph, Tabular & Research-Object Projections for profiles and a complete example.

Next steps