Work with PROV-O (RDF)

PROV-O needs the rdf extra, which installs rdflib:

python -m pip install "prov[rdf]"

Two keywords are involved. format="rdf" selects PROV-O. rdf_format chooses the concrete RDF syntax and defaults to "trig".

Serialize to a file

import prov.model as pm

document = pm.ProvDocument()
document.set_default_namespace("http://example.org/")
e = document.entity("e1")
a = document.activity("a1")
document.wasGeneratedBy(e, a)

document.serialize("document.trig", format="rdf")  # rdf_format="trig" is the default

Serialize to a string

trig_str = document.serialize(format="rdf")

Choose a different RDF syntax

rdf_format accepts any syntax rdflib can write, such as "turtle", "xml" for RDF/XML, "nt" or "nquads":

turtle_str = document.serialize(format="rdf", rdf_format="turtle")
print(turtle_str)

Important

Only quad-based syntaxes, "trig" and "nquads", keep bundles as separate named graphs. Triple-based syntaxes such as "turtle" and "xml" flatten every bundle into one graph and lose which bundle each statement came from. Keep the default TriG if your document has bundles.

Deserialize from a file or stream

loaded = pm.ProvDocument.deserialize("document.trig", format="rdf")
assert loaded == document

Pass the matching rdf_format when the input is not TriG:

loaded = pm.ProvDocument.deserialize(content=turtle_str, format="rdf", rdf_format="turtle")

Deserialize from a string

loaded = pm.ProvDocument.deserialize(content=trig_str, format="rdf")

Auto-detect the format with prov.read()

prov.read() tries PROV-O second, after PROV-JSON. See Auto-detect the format with prov.read() in the PROV-JSON guide for how detection works and when to pass format= instead.

import prov

loaded = prov.read("document.trig")
assert loaded == document

Common errors

Malformed RDF raises rdflib’s own parser error:

try:
    pm.ProvDocument.deserialize(content="not rdf", format="rdf")
except Exception as e:
    print(f"{type(e).__name__}")
BadSyntax

Two relations that share an identifier but differ in a formal attribute cannot round-trip through PROV-O, because PROV-O stores a relation as one node named by its identifier. Decoding such RDF raises prov.model.ProvException:

document = pm.ProvDocument()
document.set_default_namespace("http://example.org/")
document.wasGeneratedBy("e1", "a1", time="2024-01-01T00:00:00", identifier="g1")
document.wasGeneratedBy("e1", "a1", time="2024-01-02T00:00:00", identifier="g1")
trig_str = document.serialize(format="rdf")

try:
    pm.ProvDocument.deserialize(content=trig_str, format="rdf")
except pm.ProvException as e:
    print(type(e).__name__)
ProvException

Conformance explains the limitation.