Backends

HolonicStore Protocol

class holonic.backends.store.HolonicStore(*args, **kwargs)[source]

Bases: Protocol

Mandatory interface for a quad-aware graph store.

Every backend must satisfy this protocol. The methods cover named-graph CRUD and SPARQL dispatch — enough for all holonic operations when combined with the library’s Python-side helpers (MetadataRefresher, ScopeResolver, run_projection).

Any object matching this protocol shape can be used with HolonicDataset, regardless of whether it inherits AbstractHolonicStore. Inheritance is recommended for the defaults-for-optional-methods it provides, but not required.

Choosing between Protocol and ABC

Use the Protocol (HolonicStore) for type annotations on library-public functions and APIs. It captures the structural contract without requiring inheritance from users:

def do_something(store: HolonicStore) -> None: ...

Use the ABC (AbstractHolonicStore) as the base class for new backend implementations. It adds @abstractmethod enforcement (so Python refuses to instantiate a subclass that forgets a method) plus hook points for optional-method defaults:

class MyBackend(AbstractHolonicStore):
    def graph_exists(self, graph_iri): ...
    # ... all the other abstract methods

Examples:

The two first-party backends (RdflibBackend, FusekiBackend) both inherit the ABC. Duck-typed protocol satisfaction works too, as verified by isinstance(backend, HolonicStore).

See Also:

AbstractHolonicStore : Recommended base class for new backends. holonic.backends.rdflib_backend.RdflibBackend : First-party default. holonic.backends.fuseki_backend.FusekiBackend : First-party HTTP.

graph_exists(graph_iri)[source]

Return True if the named graph contains at least one triple.

Return type:

bool

Parameters:

graph_iri (str)

get_graph(graph_iri)[source]

Return the named graph as an rdflib.Graph (for local processing).

Return type:

Graph

Parameters:

graph_iri (str)

put_graph(graph_iri, g)[source]

Replace the named graph with the contents of g.

Return type:

None

Parameters:
post_graph(graph_iri, g)[source]

Append triples from g into the named graph.

Return type:

None

Parameters:
delete_graph(graph_iri)[source]

Delete the named graph entirely.

Return type:

None

Parameters:

graph_iri (str)

parse_into(graph_iri, data, format='turtle')[source]

Parse serialized RDF into the named graph (append).

Return type:

None

Parameters:
query(sparql, **bindings)[source]

Execute a SELECT query. Return list of binding dicts.

Each dict maps variable names (without ?) to their values. Values are strings (IRIs/literals) — callers convert as needed.

Return type:

list[dict[str, Any]]

Parameters:
construct(sparql, **bindings)[source]

Execute a CONSTRUCT query. Return results as an rdflib.Graph.

Return type:

Graph

Parameters:
ask(sparql, **bindings)[source]

Execute an ASK query. Return boolean.

Return type:

bool

Parameters:
update(sparql)[source]

Execute a SPARQL UPDATE (INSERT/DELETE/DROP/CREATE).

Return type:

None

Parameters:

sparql (str)

list_named_graphs()[source]

Return IRIs of all named graphs containing triples.

Return type:

list[str]

AbstractHolonicStore

Recommended base class for new backend implementations. Marks mandatory methods as abstract and provides hook points for optional native methods (0.4.x growing).

class holonic.backends.store.AbstractHolonicStore[source]

Bases: ABC

Abstract base class for holonic stores with optional-method defaults.

Inheriting this is the recommended way to implement a backend. Subclasses define the mandatory methods (abstract here); the ABC provides Python fallback implementations of optional methods so backend authors don’t have to ship them.

Mandatory surface

Eleven methods marked @abstractmethod: named-graph CRUD (graph_exists, get_graph, put_graph, post_graph, delete_graph, parse_into), SPARQL dispatch (query, construct, ask, update), and one utility (list_named_graphs). Python refuses to instantiate a subclass that doesn’t implement all eleven.

Optional surface

Additional methods that backends MAY override to replace the library’s generic Python fallbacks with native, typically faster implementations. Discovery is duck-typed via hasattr(store, method_name); no registration is required.

As of 0.4.0, one optional method is recognized:

  • refresh_graph_metadata(graph_iri, registry_iri) -> GraphMetadata | None recompute per-graph metadata (triple count, class inventory, last-modified timestamp) natively. The library’s MetadataRefresher.refresh_graph dispatches to this if the method exists on the store; otherwise it runs the generic Python implementation.

Future 0.4.x releases will add more optional methods for scope walking, bulk load, and pipeline execution (see SPEC R9.17).

Example:

A minimal backend implementing only the mandatory surface:

from holonic.backends.store import AbstractHolonicStore

class MyBackend(AbstractHolonicStore):
    def __init__(self):
        self._store = {}  # graph_iri -> set[(s, p, o)]

    def graph_exists(self, graph_iri):
        return bool(self._store.get(graph_iri))

    def get_graph(self, graph_iri):
        from rdflib import Graph
        g = Graph()
        for triple in self._store.get(graph_iri, ()):
            g.add(triple)
        return g

    # ... other mandatory methods ...

A backend with a native metadata fast path:

class FusekiBackend(AbstractHolonicStore):
    # ... mandatory methods ...

    def refresh_graph_metadata(self, graph_iri, registry_iri):
        # Use Fuseki's native statistics endpoint
        stats = self._fetch_stats(graph_iri)
        return GraphMetadata(
            iri=graph_iri,
            triple_count=stats["count"],
            last_modified=stats["modified"],
            ...
        )

See Also:

HolonicStoreThe Protocol view of the mandatory surface;

use this for type annotations on library APIs.

holonic._metadata.MetadataRefresherDispatcher that

chooses native vs generic metadata paths.

abstractmethod graph_exists(graph_iri)[source]

Return True if the named graph contains at least one triple.

Implementations SHOULD treat “does not exist” and “exists but empty” as equivalent — both return False. Callers use this as a cheap presence check before committing to a full read.

Return type:

bool

Parameters:

graph_iri (str)

abstractmethod get_graph(graph_iri)[source]

Return the named graph as an rdflib.Graph.

The returned graph is a copy for local processing; mutations do not flow back to the store. Callers wanting to mutate the backing state use put_graph / post_graph / parse_into / update.

If the named graph does not exist, implementations SHOULD return an empty rdflib.Graph rather than raise.

Return type:

Graph

Parameters:

graph_iri (str)

abstractmethod put_graph(graph_iri, g)[source]

Replace the named graph with the contents of g.

Existing triples in the named graph are removed; the new triples are then added. Atomic with respect to other callers where the backing store supports it; non-atomic implementations SHOULD document the window.

Return type:

None

Parameters:
abstractmethod post_graph(graph_iri, g)[source]

Append the triples in g to the named graph.

Existing triples are preserved. Duplicate triples are coalesced at the RDF level (a quad store stores each (s, p, o, g) at most once).

Return type:

None

Parameters:
abstractmethod delete_graph(graph_iri)[source]

Delete the named graph entirely.

SHOULD be idempotent: deleting a non-existent graph is a no-op, not an error.

Return type:

None

Parameters:

graph_iri (str)

abstractmethod parse_into(graph_iri, data, format='turtle')[source]

Parse serialized RDF and append into the named graph.

format is an rdflib parser name; common values are "turtle", "xml", "n3", "json-ld", "nquads". Semantic equivalent to post_graph(graph_iri, rdflib.Graph().parse(data=data, format=format)) but implementations MAY optimize (e.g. stream-parse into the backing store directly).

Return type:

None

Parameters:
abstractmethod query(sparql, **bindings)[source]

Execute a SPARQL SELECT query.

Returns a list of binding dictionaries, one per result row. Each dict maps variable names (without the leading ?) to their bound values. Values are Python scalars for literals (strings, ints, floats, booleans, datetime objects for xsd:dateTime) and strings for IRIs.

bindings is reserved for future parameterized-query support; implementations MAY raise NotImplementedError on non-empty bindings in 0.4.x.

Return type:

list[dict[str, Any]]

Parameters:
abstractmethod construct(sparql, **bindings)[source]

Execute a SPARQL CONSTRUCT query.

Returns the constructed triples as an rdflib.Graph. The return value is a fresh graph, not bound to any named graph in the store; callers wanting to persist it use put_graph or post_graph.

bindings: see query.

Return type:

Graph

Parameters:
abstractmethod ask(sparql, **bindings)[source]

Execute a SPARQL ASK query.

Returns True if the query has at least one solution, False otherwise. bindings: see query.

Return type:

bool

Parameters:
abstractmethod update(sparql)[source]

Execute a SPARQL UPDATE (INSERT / DELETE / DROP / CREATE).

Mutates the backing store according to the update request. Callers using this path bypass the library’s metadata-refresh machinery; if metadata_updates="eager" is the dataset policy, call HolonicDataset.refresh_metadata after out-of-band updates to reconcile.

Return type:

None

Parameters:

sparql (str)

abstractmethod list_named_graphs()[source]

Return the IRIs of all named graphs in the store.

Implementations SHOULD exclude graphs that exist as identifiers but contain no triples. The default graph (if the backing store has one) is NOT included; the library does not use the default graph and expects every triple to live in a named graph per R1.4.

Return type:

list[str]

RdflibBackend

class holonic.backends.rdflib_backend.RdflibBackend(dataset=None)[source]

Bases: AbstractHolonicStore

HolonicStore implementation backed by an rdflib.Dataset.

Parameters:

dataset (Dataset | None) – An existing rdflib.Dataset instance. If None, a fresh in-memory dataset is created.

graph_exists(graph_iri)[source]

Check if graph exists in the dataset.

Return type:

bool

Parameters:

graph_iri (str)

get_graph(graph_iri)[source]

Get named graph from the dataset.

Return type:

Graph

Parameters:

graph_iri (str)

put_graph(graph_iri, g)[source]

Replace graph data in the dataset named graph.

Return type:

None

Parameters:
post_graph(graph_iri, g)[source]

Add graph data to the dataset named graph.

Return type:

None

Parameters:
delete_graph(graph_iri)[source]

Remove named graph from the dataset.

Return type:

None

Parameters:

graph_iri (str)

parse_into(graph_iri, data, format='turtle')[source]

Parse data into dataset named graph.

Return type:

None

Parameters:
query(sparql, **bindings)[source]

Execute query against the dataset.

Return type:

list[dict[str, Any]]

Parameters:
construct(sparql, **bindings)[source]

Execute CONSTRUCT query on the dataset.

Return type:

Graph

Parameters:
ask(sparql, **bindings)[source]

Execute ASK query on the dataset.

Return type:

bool

Parameters:
update(sparql)[source]

Update the dataset using SPARQL string.

Return type:

None

Parameters:

sparql (str)

list_named_graphs()[source]

Return each graph idendifier in the dataset.

Return type:

list[str]

property dataset: Dataset

Returns dataset.

FusekiBackend

class holonic.backends.fuseki_backend.FusekiBackend(base_url, *, dataset, extra_headers=None, **client_kwargs)[source]

Bases: AbstractHolonicStore

HolonicStore implementation backed by an Apache Jena Fuseki server.

Parameters:
  • base_url (str) – Fuseki server URL, e.g. “http://localhost:3030”. Positional.

  • dataset (str) – Dataset name on the server. Keyword-only since 0.4.0.

  • extra_headers (dict[str, str] | None) – Optional HTTP headers merged into every outbound request.

  • **client_kwargs (Any) – Extra kwargs forwarded to FusekiClient.

  • versionchanged: (..) – 0.4.0: dataset is now keyword-only. Callers using the legacy positional form (FusekiBackend(base_url, dataset)) receive a TypeError; migrate to FusekiBackend(base_url, dataset=name).

graph_exists(graph_iri)[source]

Check if named graph exists in the dataset.

Return type:

bool

Parameters:

graph_iri (str)

get_graph(graph_iri)[source]

Return named graph in dataset.

Return type:

Graph

Parameters:

graph_iri (str)

put_graph(graph_iri, g)[source]

Replace graph data from named graph to dataset.

Return type:

None

Parameters:
post_graph(graph_iri, g)[source]

Add graph data from named graph to dataset.

Return type:

None

Parameters:
delete_graph(graph_iri)[source]

Remove graph from dataset.

Return type:

None

Parameters:

graph_iri (str)

parse_into(graph_iri, data, format='turtle')[source]

Add data to graph and post to the dataset.

Return type:

None

Parameters:
query(sparql, **bindings)[source]

Execute a query against the dataset.

Return type:

list[dict[str, Any]]

Parameters:
construct(sparql, **bindings)[source]

Execute a CONSTRUCT query against the dataset.

Return type:

Graph

Parameters:
ask(sparql, **bindings)[source]

Execute an ASK query against the dataset.

Return type:

bool

Parameters:
update(sparql)[source]

Update dataset with SPARQL string.

Return type:

None

Parameters:

sparql (str)

list_named_graphs()[source]

List all named graphs in the dataset.

Return type:

list[str]

Implementing a Custom Backend

Any object satisfying the HolonicStore protocol can back a HolonicDataset. The protocol requires named-graph CRUD and SPARQL query/update methods. Inheriting AbstractHolonicStore is recommended (you get @abstractmethod enforcement of the mandatory surface) but not required — duck-typing works too.

from holonic.backends.store import AbstractHolonicStore

class MyBackend(AbstractHolonicStore):
    def graph_exists(self, graph_iri: str) -> bool: ...
    def get_graph(self, graph_iri: str) -> Graph: ...
    def put_graph(self, graph_iri: str, g: Graph) -> None: ...
    def post_graph(self, graph_iri: str, g: Graph) -> None: ...
    def delete_graph(self, graph_iri: str) -> None: ...
    def parse_into(self, graph_iri: str, data: str, format: str) -> None: ...
    def query(self, sparql: str, **bindings) -> list[dict]: ...
    def construct(self, sparql: str, **bindings) -> Graph: ...
    def ask(self, sparql: str, **bindings) -> bool: ...
    def update(self, sparql: str) -> None: ...
    def list_named_graphs(self) -> list[str]: ...

# Use it:
ds = HolonicDataset(backend=MyBackend())

Optional Native Methods

Backends that can compute operations natively (faster than the library’s Python fallback) can override optional methods. The library discovers them via hasattr — no registration required. As of 0.4.0, one optional method is recognized:

class MyBackend(AbstractHolonicStore):
    # ... mandatory methods above ...

    def refresh_graph_metadata(
        self,
        graph_iri: str,
        registry_iri: str,
    ) -> GraphMetadata | None:
        # Fast native path: compute triple count + class inventory,
        # write to registry, return GraphMetadata (or None to let
        # the library re-read it via the standard read path).
        ...

If refresh_graph_metadata is absent, MetadataRefresher runs its generic Python implementation. Additional optional methods (scope walking, bulk load, pipeline execution) are planned additively across the 0.4.x series.

Migrating from GraphBackend (0.3.x)

GraphBackend is a deprecated alias for HolonicStore kept through all of 0.4.x. New code should import HolonicStore from holonic.backends.store (or from the top-level holonic package). See docs/MIGRATION.md for the full 0.3.x → 0.4.0 migration checklist.