Read and write documents¶
serialize() writes a document in any supported format,
deserialize() reads one when you know the format, and
prov.read() reads one when you do not. Every format takes the same three kinds of
destination and source: a path, a string and an open stream.
Write to a file, a string or a stream¶
import io
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.json") # a path; format="json" is the default
text = document.serialize(format="provn") # no destination: the text comes back
with io.StringIO() as stream:
document.serialize(stream, format="xml") # an open text or binary stream
xml_text = stream.getvalue()
The format is never inferred from a file extension; pass format= for anything but
PROV-JSON. Keyword arguments beyond format go to the format’s writer, such as indent=2
for the two JSON formats.
Read from a file, a string or a stream¶
source takes a path or an open stream; content takes the text itself, as str or bytes:
loaded = pm.ProvDocument.deserialize("document.json") # a path
loaded = pm.ProvDocument.deserialize(content=text, format="provn") # the text
with open("document.json") as f:
loaded = pm.ProvDocument.deserialize(f) # an open stream
assert loaded == document
Detect the format with prov.read()¶
prov.read() reads a document without being told its format. It tries every
registered deserializer in turn, in the order PROV-JSON, PROV-O (RDF), PROV-N, PROV-XML,
PROV-JSONLD, and returns the first result that is a non-empty document.
import prov
loaded = prov.read("document.json")
assert loaded == document
A seekable stream, such as an open file or io.StringIO, is rewound between attempts. A
non-seekable stream is consumed by the first attempt, so pass format= for those.
Auto-detection treats each deserializer’s error as “not this format” and raises
ProvFormatNotDetected when no format succeeds. It is a
TypeError, so except TypeError keeps working, and a
ProvSerializationError. When the content starts the way
PROV-JSON, PROV-JSONLD or PROV-N starts and that reader rejected it, the reader’s own error
is its __cause__. Pass format= to get the error from one deserializer on its own.
Keyword arguments such as profile reach each candidate that declares them.
Formats¶
Every format reads and writes.
Format |
|
Extra |
Usual extension |
Media type |
|---|---|---|---|---|
|
none |
|
|
|
|
none |
|
|
|
|
none |
|
|
|
|
|
|
|
|
|
|
|
that of the RDF syntax, |
Installation lists the extras. prov dispatches on neither the extension nor
the media type; they are conventions for other tools.
How errors surface¶
Each reader raises its format’s own exception for input it cannot represent:
prov.serializers.provjson.ProvJSONException,
prov.serializers.provjsonld.ProvJSONLDException,
ProvNSyntaxError,
prov.serializers.provxml.ProvXMLException and
prov.serializers.provrdf.ProvRDFException. All five derive from
ProvSerializationError, which sits under prov.Error
beside ProvException.
When the model rejects a record a reader builds, such as a value no PROV form holds or a
name that does not resolve, the reader raises its own error. The message names the record
and carries the model’s sentence, and the model’s exception is the error’s __cause__.
Code that builds the record itself gets the model’s exception.
Choose the except clause by what the code handles:
Clause |
Catches |
|---|---|
|
a failure to read or write a document in any format, a reader’s rejection of a record included, and the writers’ error for a serializer with no document |
|
a refusal by the model when code builds or changes a record, and |
|
both of the above, and |
Input that is not well-formed in the underlying syntax raises the parsing library’s own
error, which is outside the base: json.JSONDecodeError for the two JSON formats,
lxml’s XMLSyntaxError, and the error of the rdflib parser for the chosen rdf_format,
BadSyntax for TriG and Turtle.
try:
pm.ProvDocument.deserialize(content="not json", format="json")
except Exception as error:
print(f"{type(error).__name__}: {error}")
JSONDecodeError: Expecting value: line 1 column 1 (char 0)
Attribute order is stable¶
A record stores its attribute values in the order they were added, and every format writes them in that order. Two runs of the same program produce the same text, so the output is safe to use in doctests and golden files:
document.entity("e2", [(pm.PROV_TYPE, "foo"), (pm.PROV_TYPE, "bar")])
print(document.get_provn())
# entity(e2, [prov:type="foo", prov:type="bar"])