Apache Jena backend (SPARQL)

cvcdocdb.jena_graph stores graphs as RDF 1.2 in Apache Jena Fuseki (or another SPARQL 1.2 store, see below), with the same API and behaviour as every other backend. cvcdocdb.jena_rdf defines the RDF layout, so the data can also be queried directly with SPARQL.

from cvcdocdb import JenaGraph, Node

graph = JenaGraph("http://localhost:3030/ds")      # a Fuseki dataset
graph.insertNode(Node(pk={"doc": "D1"}, main_label="Document", title="Padró"))
graph.query(
    "PREFIX label: <urn:cvcdocdb:label/> PREFIX prop: <urn:cvcdocdb:prop/> "
    "SELECT ?t WHERE { ?d a label:Document ; prop:title ?t }"
)  # [{'t': 'Padró'}]

How it works:

  • It reuses the NetworkXGraph logic (identical to Neo4jGraph’s: the shared propagation scenarios check it) on an in-memory copy of the graph, so the graph has to fit in RAM.

  • Every mutating call, or a whole batch(), is sent as one atomic SPARQL Update with only what changed. If it fails, nothing is sent and the copy is rolled back.

  • A version stamp detects writes from other clients. The copy is refreshed before each write. If another client writes in the middle of a call or batch, nothing is applied and ConcurrentModificationError is raised.

  • query() runs SPARQL strings on the server (SPARQL Update is refused: writes go through the API so the propagation policy applies). Dict filters and Cypher run on the in-memory copy, as on NetworkXGraph.

  • namespace (default urn:cvcdocdb:) prefixes every IRI it writes. Triples outside it are never touched, and graph_iri stores the data in a named graph. clear() deletes everything in the namespace.

  • Vector indexes are not supported (as on Neo4j).

Requirement: Apache Jena Fuseki >= 6.2.0 (RDF 1.2 triple terms and annotations). JenaGraph reads the version from Fuseki’s /$/server endpoint on connection, before reading any data, and raises FusekiVersionError if it is older or can’t be determined. Pass server_url= if Fuseki is behind a proxy, or check_fuseki_version=False to use another SPARQL 1.2 store at your own risk. The version is available as graph.server_version.

Natural-language questions: Natural-language queries in SPARQL (Text2SPARQL) or Natural-language queries on any backend (Text2Query).

JenaGraph — Apache Jena (SPARQL) backend for the DRM graph model.

JenaGraph stores the graph as RDF 1.2 in a SPARQL store such as Apache Jena Fuseki (see cvcdocdb.jena_rdf for the RDF layout), with the same API and the same behaviour as every other backend.

How it works:

  • It reuses NetworkXGraph’s logic, which behaves exactly like Neo4jGraph (the shared propagation scenarios check it), on an in-memory copy of the graph.

  • Every mutating call, or a whole batch(), is sent to the server as a single atomic SPARQL Update request with only what changed. If the call fails, nothing is sent and the in-memory copy is rolled back.

  • Writes are protected by a version stamp. Before each write, the copy is refreshed if another client changed the graph. If another client writes in the middle of a call or batch, nothing is applied, the copy is reloaded, and ConcurrentModificationError is raised.

  • query() with a SPARQL string runs it on the server. Dict filters and Cypher strings run on the in-memory copy, as on NetworkXGraph.

The whole graph is held in memory, so the graph has to fit in RAM.

exception cvcdocdb.jena_graph.ConcurrentModificationError[source]

Bases: RuntimeError

Another client changed the graph during this call or batch; nothing was applied and the in-memory copy has been reloaded. Retry the call.

exception cvcdocdb.jena_graph.FusekiVersionError[source]

Bases: SparqlError

The server isn’t Apache Jena Fuseki >= MIN_FUSEKI_VERSION, or its version can’t be determined.

class cvcdocdb.jena_graph.JenaGraph(url, user=None, password=None, *, namespace='urn:cvcdocdb:', graph_iri=None, query_url=None, update_url=None, timeout=30.0, lock_timeout=None, server_url=None, check_fuseki_version=True)[source]

Bases: NetworkXGraph

Graph store on Apache Jena Fuseki (or another SPARQL 1.2 store).

Parameters:
  • url (str) – Dataset URL, e.g. http://localhost:3030/ds. Queries go to <url>/query and updates to <url>/update unless query_url/update_url are given.

  • user (Optional[str]) – Optional HTTP basic-auth user.

  • password (Optional[str]) – Optional HTTP basic-auth password.

  • namespace (str) – IRI prefix of everything this graph writes (default urn:cvcdocdb:). Triples outside it are never touched, so several graphs can share a dataset with different namespaces.

  • graph_iri (Optional[str]) – Store the data in this named graph instead of the default graph.

  • query_url (Optional[str]) – Explicit SPARQL query endpoint.

  • update_url (Optional[str]) – Explicit SPARQL update endpoint.

  • timeout (float) – Seconds to wait for each HTTP request.

  • lock_timeout (Optional[float]) – Seconds a mutating call waits for another thread of this process using the same instance (None: forever).

  • server_url (Optional[str]) – Fuseki’s base URL, if it can’t be derived from url (e.g. behind a proxy); its /$/server endpoint reports the version.

  • check_fuseki_version (bool) – Require Apache Jena Fuseki >= MIN_FUSEKI_VERSION (default). Set to False to use another SPARQL 1.2 store, at your own risk.

server_version

The Fuseki version reported by the server, or None if it couldn’t be determined (only possible with check_fuseki_version=False).

Raises:
  • FusekiVersionError – If the server isn’t Fuseki >= 6.2.0, or its version can’t be determined (and check_fuseki_version is on). Raised before any data is read.

  • SparqlError – If the server can’t be reached or rejects a request.

Parameters:
  • url (str)

  • user (Optional[str])

  • password (Optional[str])

  • namespace (str)

  • graph_iri (Optional[str])

  • query_url (Optional[str])

  • update_url (Optional[str])

  • timeout (float)

  • lock_timeout (Optional[float])

  • server_url (Optional[str])

  • check_fuseki_version (bool)

__init__(url, user=None, password=None, *, namespace='urn:cvcdocdb:', graph_iri=None, query_url=None, update_url=None, timeout=30.0, lock_timeout=None, server_url=None, check_fuseki_version=True)[source]
Parameters:
  • persistence_path – Pickle file backing the graph; defaults to a per-workspace file under ~/.cache/cvcdocdb.

  • lock_timeout (float | None) – Maximum seconds a mutating call (or batch(), including cvcdocdb.migration.migrate()) waits for the cross-process file lock before raising GraphLockTimeout. None (default) waits forever, as before.

  • url (str)

  • user (str | None)

  • password (str | None)

  • namespace (str)

  • graph_iri (str | None)

  • query_url (str | None)

  • update_url (str | None)

  • timeout (float)

  • server_url (str | None)

  • check_fuseki_version (bool)

Raises:

ValueError – If lock_timeout is negative.

Return type:

None

query(filter_dict=None, projection=None, sort=None, limit_val=None, params=None)[source]

Like NetworkXGraph.query(), plus SPARQL.

A SPARQL string (SELECT/ASK/CONSTRUCT/DESCRIBE, with an optional PREFIX/BASE prologue) runs on the server and sees the committed data. params are bound with a trailing VALUES clause ({"name": value} binds ?name). Rows map variables to Python values (IRIs as strings). ASK returns [{"ask": bool}]; CONSTRUCT/DESCRIBE return one {"subject", "predicate", "object"} row per triple.

SPARQL Update is refused (ValueError): writes go through the GraphStore API so the propagation policy is applied. Dict filters and Cypher run on the in-memory copy, as on NetworkXGraph.

Parameters:
Return type:

List[Dict[str, Any]]

clear()[source]

Delete everything this graph wrote (its namespace) on the server. Triples outside the namespace are left alone.

Return type:

None

enable_vector_index(*args, **kwargs)[source]

Not supported on Jena (as on Neo4j).

Parameters:
Return type:

None

query_vector_index(*args, **kwargs)[source]

Not supported on Jena (as on Neo4j).

Parameters:
Return type:

List[Tuple[int, float]]

list_vector_indexes()[source]

JenaGraph has no vector indexes — always empty.

Return type:

List[Dict[str, Any]]

close()[source]

Release resources and clear the graph.

Does NOT persist anything itself — every mutating method (insertNode/insertRelation/deleteNode/init_propagation/ create_group/enable_vector_index) already reloads the latest on-disk state and saves as part of a cross-process file-locked load-mutate-save cycle (see _guarded_write), so by the time close() runs the on-disk state already reflects every change made through this instance — including any other process’s writes that happened in between.

This used to call _save_state() unconditionally, which re-wrote the entire file with whatever was loaded into memory when this instance was opened — including for an instance that only ever did reads. Two overlapping opens of the same persistence path (e.g. a slow read in one process/request and a write in another) would race: whichever instance closed last silently overwrote the other’s writes with its own (possibly stale) snapshot, even though it never mutated anything itself. Removing the call here means a read-only instance can never clobber someone else’s concurrent write on close. The remaining “two writers” race (both mutate, neither just reads) is handled separately by _guarded_write’s reload-before-mutate under a cross-process lock.

Return type:

None

batch(write=True)[source]

Group calls into ONE atomic SPARQL Update, sent when the block ends. If the block fails, nothing is sent and the in-memory copy is rolled back. Reads inside the block see its own writes (SPARQL strings excepted: they run on the server).

Parameters:

write (bool)

Return type:

Iterator[None]

cvcdocdb.jena_graph.MIN_FUSEKI_VERSION = (6, 2, 0)

Oldest Apache Jena Fuseki release JenaGraph supports (RDF 1.2 triple terms and annotations, SPARQL 1.2 results; the version CI tests against).

exception cvcdocdb.jena_graph.SparqlError[source]

Bases: RuntimeError

The SPARQL server rejected a request or could not be reached.

cvcdocdb.jena_graph.is_sparql(text)[source]

Whether text is SPARQL (query or update) rather than Cypher.

Parameters:

text (str)

Return type:

bool

cvcdocdb.jena_graph.is_sparql_update(text)[source]

Whether text is a SPARQL Update request.

Parameters:

text (str)

Return type:

bool

RDF layout

Mapping between the state of a cvcdocdb graph and RDF 1.2 triples.

Used by JenaGraph to store, in a SPARQL store such as Apache Jena Fuseki, exactly the state NetworkXGraph keeps in memory. The RDF is meant to be queried directly with SPARQL. With the default namespace urn:cvcdocdb::

  • a node is <urn:cvcdocdb:node/ID>; it is a <urn:cvcdocdb:label/L> for each of its labels, and each property name is a triple <urn:cvcdocdb:prop/name> value;

  • a relation is the triple <node/U> <urn:cvcdocdb:rel/TYPE> <node/V>. Its properties hang from a reifier <urn:cvcdocdb:edge/U/V/TYPE> with rdf:reifies <<( <node/U> <rel/TYPE> <node/V> )>> (RDF 1.2);

  • bookkeeping lives under <urn:cvcdocdb:meta/...>: each node’s main label and pk, the label order, and the id counter.

Values: str → plain literal, bool → xsd:boolean, int → xsd:integer, float → xsd:double, date/datetime → xsd:date/xsd:dateTime, and anything else (lists, dicts, None) → an rdf:JSON literal. Names (labels, properties, relation types) are percent-encoded into the IRIs.

Terms are handled in two forms: canonical N-Triples-like strings (what is sent to the server, and what triples are compared by) and parsed tuples (("uri", iri), ("literal", lexical, datatype, lang), ("triple", s, p, o)).

cvcdocdb.jena_rdf.term_to_string(term)[source]

Canonical string form of a parsed term.

Parameters:

term (Tuple[Any, ...])

Return type:

str

cvcdocdb.jena_rdf.parse_term(text)[source]

Parse a canonical term string (see term_to_string()).

Parameters:

text (str)

Return type:

Tuple[Any, …]

cvcdocdb.jena_rdf.term_from_binding(binding)[source]

Parsed term from a SPARQL 1.2 JSON results binding.

Parameters:

binding (Dict[str, Any])

Return type:

Tuple[Any, …]

class cvcdocdb.jena_rdf.RdfMapping(namespace='urn:cvcdocdb:')[source]

IRIs and value encoding for one namespace (see the module docstring).

Parameters:

namespace (str)

__init__(namespace='urn:cvcdocdb:')[source]
Parameters:

namespace (str)

Return type:

None

node_iri(node_id)[source]
Parameters:

node_id (int)

Return type:

str

label_iri(label)[source]
Parameters:

label (str)

Return type:

str

prop_iri(name)[source]
Parameters:

name (str)

Return type:

str

rel_iri(rel_type)[source]
Parameters:

rel_type (str)

Return type:

str

edge_iri(src, dst, rel_type)[source]
Parameters:
Return type:

str

encode_value(value)[source]
Parameters:

value (Any)

Return type:

str

decode_term(term)[source]
Parameters:

term (Tuple[Any, ...])

Return type:

Any

state_to_triples(node_attrs, edge_attrs, meta)[source]

Every triple that represents node_attrs/edge_attrs/meta.

Parameters:
Return type:

Set[Tuple[str, str, str]]

state_from_triples(triples)[source]

Inverse of state_to_triples(). Triples outside the namespace (or not written by cvcdocdb) are ignored.

Parameters:

triples (Iterable[Tuple[Tuple[Any, ...], Tuple[Any, ...], Tuple[Any, ...]]])

Return type:

Tuple[Dict[int, Dict[str, Any]], Dict[Tuple[int, int, str], Dict[str, Any]], Dict[str, Any]]

static subjects(triples)[source]

Group triples by subject.

Parameters:

triples (Iterable[Tuple[str, str, str]])

Return type:

Dict[str, FrozenSet[Tuple[str, str, str]]]