Upgrading to 3.0¶
3.0 is the one release in the prov roadmap allowed to break compatibility. Every
change below was signposted in 2.4.0 (with a runtime warning where that was feasible), so
code that only saw a DeprecationWarning/FutureWarning under 2.4.0 needed no change to
keep working right up to 3.0; this page lists what to do for each change 3.0 makes.
See ROADMAP.md for
the release-by-release plan and the
modernisation roadmap design
for the full rationale.
Smaller install footprint¶
Change |
Signposted in 2.4.0 by |
What to do |
|---|---|---|
Done in 3.0.0.dev0: |
Importing |
Depend on |
Done in 3.0.0.dev0: |
Importing |
Depend on |
|
Not separately warned (internal dependency swap) |
No action for typical ISO-8601 timestamps. If you rely on |
|
Not separately warned (dependency floor) |
If you pin rdflib 6.x alongside |
A plain pip install prov keeps working for the core data model and the JSON/PROV-N
serializers; add the relevant extra(s) if you use graphics export or graph interop. The
plot extra (ProvBundle.plot()/ProvDocument.plot()) also now carries pydot and
networkx, since its interactive-display path renders through prov.dot.
Behaviour-changing bug fixes¶
These fix real bugs, but each one changes output or equality semantics for inputs that work (without error) today, so none of them can land in the 2.x series under the API-stability promise. No 2.4.0 runtime warning is feasible for these — the affected code paths are ordinary attribute/literal handling with no single import or call site to hook a warning onto — so this table is their only 2.4.0 signpost.
Issue |
Problem today |
What changes in 3.0 |
What to do |
|---|---|---|---|
A literal parsed with an explicit |
Serializers emit one consistent canonical form for string literals: the plain, undecorated form. RDF output no longer decorates a plain string attribute value with |
If you compare serialized RDF output byte-for-byte, re-check it against 3.0 output. Document-level (semantic) equality is unaffected: RDF 1.1 treats a plain literal and an |
|
Each attribute’s values are stored in a plain Python |
Each attribute’s values are now held internally in a type-aware container keyed on |
If your code depends on same-value-different-type attributes collapsing (or on which of the colliding values survives) when read through |
|
|
|
If you rely on lexical (string) inequality between differently-formatted decimal literals denoting the same value, re-check that code against 3.0. |
|
|
Language tags compare and hash case-insensitively; the stored tag itself is left untouched, so serialized output still carries its original case verbatim. |
If you rely on differently-cased language tags comparing unequal, re-check that code against 3.0. Serialized output is unaffected. |
|
PROV-JSON encodes |
PROV-JSON emits |
If you consume PROV-JSON produced by |
|
A |
Asserting a |
If your code inspects the type of an attribute value asserted from a |
|
|
A typed |
If you byte-compare PROV-N output or relied on an asserted |
|
PROV-JSON, PROV-XML, and PROV-O always tag a plain Python |
Plain ints are typed by magnitude ( |
If you parse PROV-JSON |
|
PROV-O (RDF) decoding rebuilds a typed |
RDF decoding reconstructs a |
If you byte-compare PROV-O (RDF) output, expect |
|
PROV-N output does not escape a qualified name’s local part, so an identifier containing a PROV-N metacharacter ( |
PROV-N escapes local-part metacharacters per the grammar’s |
If you consume PROV-N produced by |
|
PROV-O output for an anonymous qualified Communication/Attribution/Delegation/Influence node omits that relation’s influencer property ( |
Every qualification node — identified or anonymous — now carries its influencer property directly, alongside the existing shorthand triple; decoding matches a delegation’s qualifiedDelegation node by that property instead of an ambiguous “last node seen” guess. |
If you consume PROV-O produced by |
|
PROV-O output for |
|
If you byte-compare PROV-O output for |
|
PROV-O (RDF) decoding of an |
|
If you consume PROV-O produced by |
|
PROV-O (RDF) decoding of a qualified |
Decoding reconciles |
If you consume third-party PROV-O using the binary predicate spelling on a qualified Start/End node, expect the time to now populate the formal |
|
PROV-O (RDF) decoding of an anonymous (unidentified) Communication/Attribution/Influence relation carrying extra attributes reconstructs it twice: once from the shorthand binary triple ( |
Decoding reconciles the binary triple onto the same |
If you decode third-party PROV-O with this shape, expect one record back instead of two; document-level (semantic) equality now matches what was actually asserted. |
|
A qualified name whose local part ends in a PROV-N metacharacter ( |
Decoding resolves an IRI to a |
If you decode PROV-O containing such identifiers, they now round-trip instead of raising. Since |
|
A namespace registered only on a bundle (not the document), or a document/bundle default namespace ( |
Bundle-local and default namespace prefixes are bound into the output alongside document-level ones; a bundle-local prefix colliding with a document-level one is renamed by rdflib rather than clobbering the document-level binding. |
If you byte-compare turtle/TriG output, expect different (and now-declared) prefix labels; document-level (semantic) equality is unaffected. |
|
A document containing two relations that share one identifier but disagree on a formal attribute (e.g. two |
Not a fix — documented as a permanent PROV-O representational limitation (see Conformance matrix): the RDF serializer’s decoder now raises a chained |
If you decode third-party RDF and hit this error, the source document encodes a construct PROV-O cannot represent; there is no |
|
An attribute whose value is the empty string |
PROV-XML deserialization coalesces an empty text-value element back to |
None — this is a straightforward round-trip fix. If you depended on empty-string attributes vanishing through XML, they now survive instead. |
|
An attribute name is written as a PROV-XML child element tag, but the serializer never checked that the name’s local part is a legal XML NCName; a name starting with a digit or containing a character such as |
The serializer escapes NCName-illegal characters using the |
None for existing (legal-NCName) attribute names — output is unchanged. If you previously worked around the |
|
The PROV-JSON deserializer assumes its input has the expected shape (a JSON object container, with a dict-of-records under each record-type key, a dict under |
The deserializer validates container and typed-literal shape as it decodes and raises |
BREAKING for exception-handling code: if you catch |
|
PROV-XML deserialization parses with |
PROV-XML parsing always uses an explicit, hardened parser ( |
If you relied on DTD entity resolution inside PROV-XML input (there is no legitimate PROV use for it), it no longer happens; affected attribute values come back empty instead of expanded. Note that this is a silent substitution: combined with the empty-string round-trip fix above (#224), an attribute whose entity was neutered is indistinguishable from one that genuinely carried an empty string. If you deserialize untrusted PROV-XML and need to detect entity use rather than merely neutralise it, reject documents containing a |
Unification rework (PROV-CONSTRAINTS)¶
Before 3.0, ProvBundle.unified() and ProvDocument.unified() performed a simple,
identifier-keyed attribute union: for each identifier shared by more than one record,
the attributes of all such records were merged onto a copy of the first, with no
conflict detection. 2.4.0 signposted the change with a FutureWarning on every call.
See the unification and flattening explanation
for the full write-up of the 3.0 behaviour.
3.0 lands that reimplementation (closing umbrella issue #253): unified() now follows
the merging rules of W3C PROV-CONSTRAINTS
(key constraints and term unification), and the 2.4.0 FutureWarning is gone —
unified() just does this now, unconditionally. Concretely:
Records sharing an identifier are merged by unifying their formal attributes position by position. Two records that hold different concrete values for the same formal attribute now raise
prov.model.ProvUnificationError(aProvExceptionsubclass) instead of having their attributes silently unioned.An absent formal attribute is treated as an existential (“unknown”) and unifies with any concrete value, so partial records still merge — this is PROV-CONSTRAINTS §6.1’s worked example, and it is unchanged from 2.x.
prov’s model cannot represent PROV-N’s placeholder-(every deserializer drops the distinction between “absent” and “explicitly-“), so the specification’s--versus-concrete merge failure is out of scope by representation.Non-formal (“extra”) attributes keep their set-union semantics.
Records sharing an identifier but holding incompatible types — an entity and an activity, or two distinct relation kinds such as a generation and a usage — now raise
ProvUnificationErrornaming both types, in either assertion order, instead of merging into a copy of whichever record was asserted first (PROV-CONSTRAINTS Constraints 53/54/55). Overlaps the specification explicitly permits — an agent that is also an entity and/or an activity — are kept as separate, per-type-merged records rather than combined into one.Each scope is unified independently:
ProvDocument.unified()unifies the top-level records and each bundle separately, and never merges across a bundle boundary (PROV-CONSTRAINTS §7.2).Out of scope for
unified(): the uniqueness constraints keyed on something other than the record identifier (Constraints 24–29) are not checked and never raise; they belong to the opt-in validation engine tracked as #62. If that engine ever pulls these constraints in, the natural shape for them is ProvToolbox’s — existential expansion plus a substitution fixpoint — not an extension of this simpler per-group merge.The
#34attribute-merging fix above landed as part of this same rework.
What to do: if your code calls unified() on documents where records sharing an
identifier can disagree on a formal attribute (for example, two wasGeneratedBy
statements for the same identifier asserting different prov:time values — the
“scruffy” pattern used in this repo’s own test suite, and the RDF representational
limitation tracked as #217 above) — or where an identifier is shared by incompatible
types, such as an entity and an activity asserted under the same identifier — expect
that call to raise where it previously merged silently. Catch the new exception
(or restructure the document to avoid conflicting formal attributes or incompatible
same-identifier types). ProvUnificationError is importable from prov.model, and
subclasses ProvException, so code that already catches ProvException around
unified() keeps working. Documents without this pattern are unaffected. This is
strictly an opt-in unified() behaviour: prov performs no structural validation at
assertion/serialization time (a deliberate design choice, #257 — validation is separate,
opt-in territory belonging to #62), so building and serializing a “scruffy” document
remains legal in 3.0 — only calling unified() on one, or decoding its RDF form, raises.
Note that prov_to_dot() (prov.dot) and prov_to_graph() (prov.graph, not
graph_to_prov(), which does not unify) call unified() internally, so a document
whose records disagree on a formal attribute or incompatible type now raises from these
functions too, regardless of whether the caller ever calls unified() directly. The two
differ in how they handle a document that fails to unify: prov_to_dot() catches the
exception and renders the original, non-unified bundle (as it has always done for the
generic ProvException the merge used to raise), whereas prov_to_graph() lets
ProvUnificationError propagate to the caller.
Removal of names deprecated in 2.4.0¶
3.0 removes everything 2.4.0 marked deprecated:
The unconditional
pydot/networkxdependencies (superseded by thedot/graphextras above) —import prov.dot/import prov.graphraisesModuleNotFoundErrorif the corresponding extra isn’t installed, rather than working out of the box.python-dateutilas a runtime dependency (superseded by the stdlib swap above), along with the incidentalprov.model.dateutilmodule re-export that came from importing it there.
There are no other 2.4.0-introduced deprecations beyond these two extras moves; nothing else is scheduled for removal.
Summary: is my code affected?¶
If your code:
Doesn’t import
prov.dot/prov.graph(or theirprov_to_dot/prov_to_graph/graph_to_provre-exports elsewhere) — unaffected by the extras moves.Doesn’t compare
xsd:decimalliterals across differing lexical forms, doesn’t rely on same-value-different-type attribute collapsing when reading attributes throughattributes/extra_attributes/equality/serialization, doesn’t rely onargs/formal_attributes/an activity’s start or end time resolving an ambiguous multi-value case to a specific arbitrary value, doesn’t byte-compare serialized string literals, doesn’t parse the"type"of PROV-JSON qualified-name attribute values, and doesn’t inspect the type of an attribute value asserted from aprov:QUALIFIED_NAME-typedLiteral— unaffected by the bug fixes.Doesn’t call
unified()on documents with conflicting formal attributes, or incompatible types, sharing an identifier — unaffected by the unification rework.Doesn’t catch
KeyError/AttributeError/TypeErroraround PROV-JSON deserialization to handle malformed input — unaffected by the #228 exception-contract change.
then upgrading to 3.0 should require no code changes at all. If you are upgrading from
before 2.4.0, note that 2.4.0 signposted both the extras moves and the unified()
rework with a runtime warning (DeprecationWarning/FutureWarning respectively) on
every affected call, so running your test suite under 2.4.0 with
-W error::DeprecationWarning -W error::FutureWarning would have surfaced every call
site ahead of time; 3.0 itself emits neither warning — by 3.0 these are just how the
functions behave.