Python Drops MemoryStore and renames SledStore to Store

pull/171/head
Tpt 3 years ago
parent 122db6a2c3
commit 3726d2cbfc
  1. 5
      python/docs/index.rst
  2. 5
      python/docs/store.rst
  3. 5
      python/docs/store/memory.rst
  4. 5
      python/docs/store/sled.rst
  5. 13
      python/src/io.rs
  6. 10
      python/src/lib.rs
  7. 470
      python/src/memory_store.rs
  8. 10
      python/src/sparql.rs
  9. 94
      python/src/store.rs
  10. 54
      python/src/store_utils.rs
  11. 71
      python/tests/test_store.py

@ -50,7 +50,7 @@ Insert the triple ``<http://example/> <http://schema.org/name> "example"`` and p
from pyoxigraph import * from pyoxigraph import *
store = MemoryStore() store = Store()
ex = NamedNode('http://example/') ex = NamedNode('http://example/')
schema_name = NamedNode('http://schema.org/name') schema_name = NamedNode('http://schema.org/name')
store.add(Quad(ex, schema_name, Literal('example'))) store.add(Quad(ex, schema_name, Literal('example')))
@ -66,6 +66,5 @@ Table of contents
model model
io io
store/memory store
store/sled
sparql sparql

@ -0,0 +1,5 @@
Disk-based RDF Store
====================
.. autoclass:: pyoxigraph.Store
:members:

@ -1,5 +0,0 @@
In-Memory Store
===============
.. autoclass:: pyoxigraph.MemoryStore
:members:

@ -1,5 +0,0 @@
Disk-based Store
================
.. autoclass:: pyoxigraph.SledStore
:members:

@ -1,10 +1,9 @@
use crate::model::{PyQuad, PyTriple}; use crate::model::{PyQuad, PyTriple};
use crate::store_utils::map_io_err;
use oxigraph::io::read::{QuadReader, TripleReader}; use oxigraph::io::read::{QuadReader, TripleReader};
use oxigraph::io::{ use oxigraph::io::{
DatasetFormat, DatasetParser, DatasetSerializer, GraphFormat, GraphParser, GraphSerializer, DatasetFormat, DatasetParser, DatasetSerializer, GraphFormat, GraphParser, GraphSerializer,
}; };
use pyo3::exceptions::PyValueError; use pyo3::exceptions::{PyIOError, PySyntaxError, PyValueError};
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::PyBytes; use pyo3::types::PyBytes;
use pyo3::wrap_pyfunction; use pyo3::wrap_pyfunction;
@ -243,3 +242,13 @@ fn to_io_err(error: impl Into<PyErr>, py: Python<'_>) -> io::Error {
io::Error::new(io::ErrorKind::Other, "An unknown error has occurred") io::Error::new(io::ErrorKind::Other, "An unknown error has occurred")
} }
} }
pub fn map_io_err(error: io::Error) -> PyErr {
match error.kind() {
io::ErrorKind::InvalidInput => PyValueError::new_err(error.to_string()),
io::ErrorKind::InvalidData | io::ErrorKind::UnexpectedEof => {
PySyntaxError::new_err(error.to_string())
}
_ => PyIOError::new_err(error.to_string()),
}
}

@ -9,16 +9,13 @@
)] )]
mod io; mod io;
mod memory_store;
mod model; mod model;
mod sled_store;
mod sparql; mod sparql;
mod store_utils; mod store;
use crate::memory_store::*;
use crate::model::*; use crate::model::*;
use crate::sled_store::*;
use crate::sparql::*; use crate::sparql::*;
use crate::store::*;
use pyo3::prelude::*; use pyo3::prelude::*;
/// Oxigraph Python bindings /// Oxigraph Python bindings
@ -34,8 +31,7 @@ fn pyoxigraph(_py: Python<'_>, module: &PyModule) -> PyResult<()> {
module.add_class::<PyDefaultGraph>()?; module.add_class::<PyDefaultGraph>()?;
module.add_class::<PyTriple>()?; module.add_class::<PyTriple>()?;
module.add_class::<PyQuad>()?; module.add_class::<PyQuad>()?;
module.add_class::<PyMemoryStore>()?; module.add_class::<PyStore>()?;
module.add_class::<PySledStore>()?;
module.add_class::<PyVariable>()?; module.add_class::<PyVariable>()?;
module.add_class::<PyQuerySolutions>()?; module.add_class::<PyQuerySolutions>()?;
module.add_class::<PyQuerySolution>()?; module.add_class::<PyQuerySolution>()?;

@ -1,470 +0,0 @@
use crate::io::PyFileLike;
use crate::model::*;
use crate::sparql::*;
use crate::store_utils::*;
use oxigraph::io::{DatasetFormat, GraphFormat};
use oxigraph::model::GraphNameRef;
use oxigraph::store::memory::*;
use pyo3::basic::CompareOp;
use pyo3::exceptions::{PyNotImplementedError, PyValueError};
use pyo3::prelude::{
pyclass, pymethods, pyproto, Py, PyAny, PyCell, PyObject, PyRef, PyRefMut, PyResult, Python,
};
use pyo3::{PyIterProtocol, PyObjectProtocol, PySequenceProtocol};
use std::convert::TryFrom;
use std::io::BufReader;
/// In-memory store.
/// It encodes a `RDF dataset <https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset>`_ and allows to query it using SPARQL.
///
/// The :py:func:`str` function provides a serialization of the store in NQuads:
///
/// >>> store = MemoryStore()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')))
/// >>> str(store)
/// '<http://example.com> <http://example.com/p> "1" <http://example.com/g> .\n'
#[pyclass(name = "MemoryStore", module = "oxigraph")]
#[derive(Eq, PartialEq, Clone)]
#[text_signature = "()"]
pub struct PyMemoryStore {
inner: MemoryStore,
}
#[pymethods]
impl PyMemoryStore {
#[new]
fn new() -> Self {
Self {
inner: MemoryStore::new(),
}
}
/// Adds a quad to the store
///
/// :param quad: the quad to add
/// :type quad: Quad
///
/// >>> store = MemoryStore()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')))
/// >>> list(store)
/// [<Quad subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<NamedNode value=http://example.com/g>>]
#[text_signature = "($self, quad)"]
fn add(&self, quad: PyQuad) {
self.inner.insert(quad)
}
/// Removes a quad from the store
///
/// :param quad: the quad to remove
/// :type quad: Quad
///
/// >>> store = MemoryStore()
/// >>> quad = Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g'))
/// >>> store.add(quad)
/// >>> store.remove(quad)
/// >>> list(store)
/// []
#[text_signature = "($self, quad)"]
fn remove(&self, quad: &PyQuad) {
self.inner.remove(quad)
}
/// Looks for the quads matching a given pattern
///
/// :param subject: the quad subject or :py:const:`None` to match everything.
/// :type subject: NamedNode or BlankNode or None
/// :param predicate: the quad predicate or :py:const:`None` to match everything.
/// :type predicate: NamedNode or None
/// :param object: the quad object or :py:const:`None` to match everything.
/// :type object: NamedNode or BlankNode or Literal or None
/// :param graph: the quad graph name. To match only the default graph, use :py:class:`DefaultGraph`. To match everything use :py:const:`None`.
/// :type graph: NamedNode or BlankNode or DefaultGraph or None
/// :return: an iterator of the quads matching the pattern
/// :rtype: iter(Quad)
///
/// >>> store = MemoryStore()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')))
/// >>> list(store.quads_for_pattern(NamedNode('http://example.com'), None, None, None))
/// [<Quad subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<NamedNode value=http://example.com/g>>]
#[text_signature = "($self, subject, predicate, object, graph_name = None)"]
fn quads_for_pattern(
&self,
subject: &PyAny,
predicate: &PyAny,
object: &PyAny,
graph_name: Option<&PyAny>,
) -> PyResult<QuadIter> {
let (subject, predicate, object, graph_name) =
extract_quads_pattern(subject, predicate, object, graph_name)?;
Ok(QuadIter {
inner: self.inner.quads_for_pattern(
subject.as_ref().map(|p| p.into()),
predicate.as_ref().map(|p| p.into()),
object.as_ref().map(|p| p.into()),
graph_name.as_ref().map(|p| p.into()),
),
})
}
/// Executes a `SPARQL 1.1 query <https://www.w3.org/TR/sparql11-query/>`_.
///
/// :param query: the query to execute
/// :type query: str
/// :param use_default_graph_as_union: if the SPARQL query should look for triples in all the dataset graphs by default (i.e. without `GRAPH` operations). Disabled by default.
/// :type use_default_graph_as_union: bool, optional
/// :param default_graph: list of the graphs that should be used as the query default graph. By default, the store default graph is used.
/// :type default_graph: NamedNode or BlankNode or DefaultGraph or list(NamedNode or BlankNode or DefaultGraph) or None, optional
/// :param named_graphs: list of the named graphs that could be used in SPARQL `GRAPH` clause. By default, all the store named graphs are available.
/// :type named_graphs: list(NamedNode or BlankNode) or None, optional
/// :return: a :py:class:`bool` for ``ASK`` queries, an iterator of :py:class:`Triple` for ``CONSTRUCT`` and ``DESCRIBE`` queries and an iterator of :py:class:`QuerySolution` for ``SELECT`` queries.
/// :rtype: QuerySolutions or QueryTriples or bool
/// :raises SyntaxError: if the provided query is invalid
///
/// ``SELECT`` query:
///
/// >>> store = MemoryStore()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> list(solution['s'] for solution in store.query('SELECT ?s WHERE { ?s ?p ?o }'))
/// [<NamedNode value=http://example.com>]
///
/// ``CONSTRUCT`` query:
///
/// >>> store = MemoryStore()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> list(store.query('CONSTRUCT WHERE { ?s ?p ?o }'))
/// [<Triple subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>>>]
///
/// ``ASK`` query:
///
/// >>> store = MemoryStore()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> store.query('ASK { ?s ?p ?o }')
/// True
#[text_signature = "($self, query, *, use_default_graph_as_union, default_graph, named_graphs)"]
#[args(
query,
"*",
use_default_graph_as_union = "false",
default_graph = "None",
named_graphs = "None"
)]
fn query(
&self,
query: &str,
use_default_graph_as_union: bool,
default_graph: Option<&PyAny>,
named_graphs: Option<&PyAny>,
py: Python<'_>,
) -> PyResult<PyObject> {
let query = parse_query(
query,
use_default_graph_as_union,
default_graph,
named_graphs,
)?;
let results = self.inner.query(query).map_err(map_evaluation_error)?;
query_results_to_python(py, results)
}
/// Executes a `SPARQL 1.1 update <https://www.w3.org/TR/sparql11-update/>`_.
///
/// :param update: the update to execute
/// :type update: str
/// :raises SyntaxError: if the provided update is invalid
///
/// The store does not track the existence of empty named graphs.
/// This method has no ACID guarantees.
///
/// ``INSERT DATA`` update:
///
/// >>> store = MemoryStore()
/// >>> store.update('INSERT DATA { <http://example.com> <http://example.com/p> "1" }')
/// >>> list(store)
/// [<Quad subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<DefaultGraph>>]
///
/// ``DELETE DATA`` update:
///
/// >>> store = MemoryStore()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> store.update('DELETE DATA { <http://example.com> <http://example.com/p> "1" }')
/// >>> list(store)
/// []
///
/// ``DELETE`` update:
///
/// >>> store = MemoryStore()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> store.update('DELETE WHERE { <http://example.com> ?p ?o }')
/// >>> list(store)
/// []
#[text_signature = "($self, update)"]
fn update(&self, update: &str) -> PyResult<()> {
self.inner.update(update).map_err(map_evaluation_error)
}
/// Loads an RDF serialization into the store
///
/// It currently supports the following formats:
///
/// * `N-Triples <https://www.w3.org/TR/n-triples/>`_ (``application/n-triples``)
/// * `N-Quads <https://www.w3.org/TR/n-quads/>`_ (``application/n-quads``)
/// * `Turtle <https://www.w3.org/TR/turtle/>`_ (``text/turtle``)
/// * `TriG <https://www.w3.org/TR/trig/>`_ (``application/trig``)
/// * `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_ (``application/rdf+xml``)
///
/// It supports also some MIME type aliases.
/// For example ``application/turtle`` could also be used for `Turtle <https://www.w3.org/TR/turtle/>`_
/// and ``application/xml`` for `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_.
///
/// :param input: The binary I/O object to read from. For example, it could be a file opened in binary mode with ``open('my_file.ttl', 'rb')``.
/// :type input: io.RawIOBase or io.BufferedIOBase
/// :param mime_type: the MIME type of the RDF serialization
/// :type mime_type: str
/// :param base_iri: the base IRI used to resolve the relative IRIs in the file or :py:const:`None` if relative IRI resolution should not be done
/// :type base_iri: str or None, optional
/// :param to_graph: if it is a file composed of triples, the graph in which store the triples. By default, the default graph is used.
/// :type to_graph: NamedNode or BlankNode or DefaultGraph or None, optional
/// :raises ValueError: if the MIME type is not supported or the `to_graph` parameter is given with a quad file.
/// :raises SyntaxError: if the provided data is invalid
///
/// >>> store = MemoryStore()
/// >>> store.load(io.BytesIO(b'<foo> <p> "1" .'), "text/turtle", base_iri="http://example.com/", to_graph=NamedNode("http://example.com/g"))
/// >>> list(store)
/// [<Quad subject=<NamedNode value=http://example.com/foo> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<NamedNode value=http://example.com/g>>]
#[text_signature = "($self, input, /, mime_type, *, base_iri = None, to_graph = None)"]
#[args(input, mime_type, "*", base_iri = "None", to_graph = "None")]
fn load(
&self,
input: PyObject,
mime_type: &str,
base_iri: Option<&str>,
to_graph: Option<&PyAny>,
) -> PyResult<()> {
let to_graph_name = if let Some(graph_name) = to_graph {
Some(PyGraphNameRef::try_from(graph_name)?)
} else {
None
};
let input = BufReader::new(PyFileLike::new(input));
if let Some(graph_format) = GraphFormat::from_media_type(mime_type) {
self.inner
.load_graph(
input,
graph_format,
&to_graph_name.unwrap_or(PyGraphNameRef::DefaultGraph),
base_iri,
)
.map_err(map_io_err)
} else if let Some(dataset_format) = DatasetFormat::from_media_type(mime_type) {
if to_graph_name.is_some() {
return Err(PyValueError::new_err(
"The target graph name parameter is not available for dataset formats",
));
}
self.inner
.load_dataset(input, dataset_format, base_iri)
.map_err(map_io_err)
} else {
Err(PyValueError::new_err(format!(
"Not supported MIME type: {}",
mime_type
)))
}
}
/// Dumps the store quads or triples into a file
///
/// It currently supports the following formats:
///
/// * `N-Triples <https://www.w3.org/TR/n-triples/>`_ (``application/n-triples``)
/// * `N-Quads <https://www.w3.org/TR/n-quads/>`_ (``application/n-quads``)
/// * `Turtle <https://www.w3.org/TR/turtle/>`_ (``text/turtle``)
/// * `TriG <https://www.w3.org/TR/trig/>`_ (``application/trig``)
/// * `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_ (``application/rdf+xml``)
///
/// It supports also some MIME type aliases.
/// For example ``application/turtle`` could also be used for `Turtle <https://www.w3.org/TR/turtle/>`_
/// and ``application/xml`` for `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_.
///
/// :param output: The binary I/O object to write to. For example, it could be a file opened in binary mode with ``open('my_file.ttl', 'wb')``.
/// :type input: io.RawIOBase or io.BufferedIOBase
/// :param mime_type: the MIME type of the RDF serialization
/// :type mime_type: str
/// :param from_graph: if a triple based format is requested, the store graph from which dump the triples. By default, the default graph is used.
/// :type from_graph: NamedNode or BlankNode or DefaultGraph or None, optional
/// :raises ValueError: if the MIME type is not supported or the `from_graph` parameter is given with a quad syntax.
///
/// >>> store = MemoryStore()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')))
/// >>> output = io.BytesIO()
/// >>> store.dump(output, "text/turtle", from_graph=NamedNode("http://example.com/g"))
/// >>> output.getvalue()
/// b'<http://example.com> <http://example.com/p> "1" .\n'
#[text_signature = "($self, output, /, mime_type, *, from_graph = None)"]
#[args(output, mime_type, "*", from_graph = "None")]
fn dump(&self, output: PyObject, mime_type: &str, from_graph: Option<&PyAny>) -> PyResult<()> {
let from_graph_name = if let Some(graph_name) = from_graph {
Some(PyGraphNameRef::try_from(graph_name)?)
} else {
None
};
let output = PyFileLike::new(output);
if let Some(graph_format) = GraphFormat::from_media_type(mime_type) {
self.inner
.dump_graph(
output,
graph_format,
&from_graph_name.unwrap_or(PyGraphNameRef::DefaultGraph),
)
.map_err(map_io_err)
} else if let Some(dataset_format) = DatasetFormat::from_media_type(mime_type) {
if from_graph_name.is_some() {
return Err(PyValueError::new_err(
"The target graph name parameter is not available for dataset formats",
));
}
self.inner
.dump_dataset(output, dataset_format)
.map_err(map_io_err)
} else {
Err(PyValueError::new_err(format!(
"Not supported MIME type: {}",
mime_type
)))
}
}
/// Returns an iterator over all the store named graphs
///
/// :return: an iterator of the store graph names
/// :rtype: iter(NamedNode or BlankNode)
///
/// >>> store = MemoryStore()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')))
/// >>> list(store.named_graphs())
/// [<NamedNode value=http://example.com/g>]
#[text_signature = "($self)"]
fn named_graphs(&self) -> GraphNameIter {
GraphNameIter {
inner: self.inner.named_graphs(),
}
}
/// Adds a named graph to the store
///
/// :param graph_name: the name of the name graph to add
/// :type graph_name: NamedNode or BlankNode
///
/// >>> store = MemoryStore()
/// >>> store.add_graph(NamedNode('http://example.com/g'))
/// >>> list(store.named_graphs())
/// [<NamedNode value=http://example.com/g>]
#[text_signature = "($self, graph_name)"]
fn add_graph(&self, graph_name: PyGraphName) {
match graph_name {
PyGraphName::DefaultGraph(_) => (),
PyGraphName::NamedNode(graph_name) => self.inner.insert_named_graph(graph_name),
PyGraphName::BlankNode(graph_name) => self.inner.insert_named_graph(graph_name),
}
}
/// Removes a graph from the store
///
/// The default graph will not be remove but just cleared.
///
/// :param graph_name: the name of the name graph to remove
/// :type graph_name: NamedNode or BlankNode or DefaultGraph
///
/// >>> store = MemoryStore()
/// >>> quad = Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g'))
/// >>> store.remove_graph(NamedNode('http://example.com/g'))
/// >>> list(store)
/// []
#[text_signature = "($self, graph_name)"]
fn remove_graph(&self, graph_name: &PyAny) -> PyResult<()> {
match PyGraphNameRef::try_from(graph_name)? {
PyGraphNameRef::DefaultGraph => self.inner.clear_graph(GraphNameRef::DefaultGraph),
PyGraphNameRef::NamedNode(graph_name) => self
.inner
.remove_named_graph(&PyNamedOrBlankNodeRef::NamedNode(graph_name)),
PyGraphNameRef::BlankNode(graph_name) => self
.inner
.remove_named_graph(&PyNamedOrBlankNodeRef::BlankNode(graph_name)),
}
Ok(())
}
}
#[pyproto]
impl PyObjectProtocol for PyMemoryStore {
fn __str__(&self) -> String {
self.inner.to_string()
}
fn __richcmp__(&self, other: &PyCell<Self>, op: CompareOp) -> PyResult<bool> {
let other: &PyMemoryStore = &other.borrow();
match op {
CompareOp::Eq => Ok(self == other),
CompareOp::Ne => Ok(self != other),
_ => Err(PyNotImplementedError::new_err(
"Ordering is not implemented",
)),
}
}
fn __bool__(&self) -> bool {
!self.inner.is_empty()
}
}
#[pyproto]
impl<'p> PySequenceProtocol<'p> for PyMemoryStore {
fn __len__(&self) -> usize {
self.inner.len()
}
fn __contains__(&self, quad: PyQuad) -> bool {
self.inner.contains(&quad)
}
}
#[pyproto]
impl PyIterProtocol for PyMemoryStore {
fn __iter__(slf: PyRef<Self>) -> QuadIter {
QuadIter {
inner: slf.inner.iter(),
}
}
}
#[pyclass(unsendable, module = "oxigraph")]
pub struct QuadIter {
inner: MemoryQuadIter,
}
#[pyproto]
impl PyIterProtocol for QuadIter {
fn __iter__(slf: PyRefMut<Self>) -> Py<Self> {
slf.into()
}
fn __next__(mut slf: PyRefMut<Self>) -> Option<PyQuad> {
slf.inner.next().map(|q| q.into())
}
}
#[pyclass(unsendable, module = "oxigraph")]
pub struct GraphNameIter {
inner: MemoryGraphNameIter,
}
#[pyproto]
impl PyIterProtocol for GraphNameIter {
fn __iter__(slf: PyRefMut<Self>) -> Py<Self> {
slf.into()
}
fn __next__(mut slf: PyRefMut<Self>) -> Option<PyNamedOrBlankNode> {
slf.inner.next().map(|q| q.into())
}
}

@ -1,5 +1,5 @@
use crate::io::map_io_err;
use crate::model::*; use crate::model::*;
use crate::store_utils::*;
use oxigraph::model::Term; use oxigraph::model::Term;
use oxigraph::sparql::*; use oxigraph::sparql::*;
use pyo3::exceptions::{PyRuntimeError, PySyntaxError, PyTypeError, PyValueError}; use pyo3::exceptions::{PyRuntimeError, PySyntaxError, PyTypeError, PyValueError};
@ -73,7 +73,7 @@ pub fn query_results_to_python(py: Python<'_>, results: QueryResults) -> PyResul
/// It could be indexes by variable name (:py:class:`Variable` or :py:class:`str`) or position in the tuple (:py:class:`int`). /// It could be indexes by variable name (:py:class:`Variable` or :py:class:`str`) or position in the tuple (:py:class:`int`).
/// Unpacking also works. /// Unpacking also works.
/// ///
/// >>> store = SledStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> solution = next(store.query('SELECT ?s ?p ?o WHERE { ?s ?p ?o }')) /// >>> solution = next(store.query('SELECT ?s ?p ?o WHERE { ?s ?p ?o }'))
/// >>> solution[Variable('s')] /// >>> solution[Variable('s')]
@ -164,7 +164,7 @@ impl PyIterProtocol for SolutionValueIter {
/// An iterator of :py:class:`QuerySolution` returned by a SPARQL ``SELECT`` query /// An iterator of :py:class:`QuerySolution` returned by a SPARQL ``SELECT`` query
/// ///
/// >>> store = SledStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> list(store.query('SELECT ?s WHERE { ?s ?p ?o }')) /// >>> list(store.query('SELECT ?s WHERE { ?s ?p ?o }'))
/// [<QuerySolution s=<NamedNode value=http://example.com>>] /// [<QuerySolution s=<NamedNode value=http://example.com>>]
@ -178,7 +178,7 @@ impl PyQuerySolutions {
/// :return: the ordered list of all variables that could appear in the query results /// :return: the ordered list of all variables that could appear in the query results
/// :rtype: list(Variable) /// :rtype: list(Variable)
/// ///
/// >>> store = SledStore() /// >>> store = Store()
/// >>> store.query('SELECT ?s WHERE { ?s ?p ?o }').variables /// >>> store.query('SELECT ?s WHERE { ?s ?p ?o }').variables
/// [<Variable value=s>] /// [<Variable value=s>]
#[getter] #[getter]
@ -209,7 +209,7 @@ impl PyIterProtocol for PyQuerySolutions {
/// An iterator of :py:class:`Triple` returned by a SPARQL ``CONSTRUCT`` or ``DESCRIBE`` query /// An iterator of :py:class:`Triple` returned by a SPARQL ``CONSTRUCT`` or ``DESCRIBE`` query
/// ///
/// >>> store = MemoryStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> list(store.query('CONSTRUCT WHERE { ?s ?p ?o }')) /// >>> list(store.query('CONSTRUCT WHERE { ?s ?p ?o }'))
/// [<Triple subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>>>] /// [<Triple subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>>>]

@ -1,7 +1,6 @@
use crate::io::PyFileLike; use crate::io::{map_io_err, PyFileLike};
use crate::model::*; use crate::model::*;
use crate::sparql::*; use crate::sparql::*;
use crate::store_utils::*;
use oxigraph::io::{DatasetFormat, GraphFormat}; use oxigraph::io::{DatasetFormat, GraphFormat};
use oxigraph::model::GraphNameRef; use oxigraph::model::GraphNameRef;
use oxigraph::store::sled::*; use oxigraph::store::sled::*;
@ -10,35 +9,33 @@ use pyo3::prelude::{
pyclass, pymethods, pyproto, Py, PyAny, PyObject, PyRef, PyRefMut, PyResult, Python, pyclass, pymethods, pyproto, Py, PyAny, PyObject, PyRef, PyRefMut, PyResult, Python,
}; };
use pyo3::{PyIterProtocol, PyObjectProtocol, PySequenceProtocol}; use pyo3::{PyIterProtocol, PyObjectProtocol, PySequenceProtocol};
use std::convert::TryFrom; use std::convert::{TryFrom, TryInto};
use std::io::BufReader; use std::io::BufReader;
/// Store based on the `Sled <https://sled.rs/>`_ key-value database. /// Disk-based RDF store.
/// ///
/// In-memory store.
/// It encodes a `RDF dataset <https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset>`_ and allows to query it using SPARQL. /// It encodes a `RDF dataset <https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset>`_ and allows to query it using SPARQL.
/// It is based on the `Sled <https://sled.rs/>`_ key-value database
/// ///
/// :param path: the path of the directory in which Sled should read and write its data. If the directory does not exist, it is created. If no directory is provided a temporary one is created and removed when the Python garbage collector removes the store. /// :param path: the path of the directory in which the store should read and write its data. If the directory does not exist, it is created. If no directory is provided a temporary one is created and removed when the Python garbage collector removes the store.
/// :type path: str or None, optional /// :type path: str or None, optional
/// :raises IOError: if the target directory contains invalid data or could not be accessed /// :raises IOError: if the target directory contains invalid data or could not be accessed
/// ///
/// Warning: Sled is not stable yet and might break its storage format.
///
/// The :py:func:`str` function provides a serialization of the store in NQuads: /// The :py:func:`str` function provides a serialization of the store in NQuads:
/// ///
/// >>> store = SledStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')))
/// >>> str(store) /// >>> str(store)
/// '<http://example.com> <http://example.com/p> "1" <http://example.com/g> .\n' /// '<http://example.com> <http://example.com/p> "1" <http://example.com/g> .\n'
#[pyclass(name = "SledStore", module = "oxigraph")] #[pyclass(name = "Store", module = "oxigraph")]
#[text_signature = "(path = None)"] #[text_signature = "(path = None)"]
#[derive(Clone)] #[derive(Clone)]
pub struct PySledStore { pub struct PyStore {
inner: SledStore, inner: SledStore,
} }
#[pymethods] #[pymethods]
impl PySledStore { impl PyStore {
#[new] #[new]
fn new(path: Option<&str>) -> PyResult<Self> { fn new(path: Option<&str>) -> PyResult<Self> {
Ok(Self { Ok(Self {
@ -57,7 +54,7 @@ impl PySledStore {
/// :type quad: Quad /// :type quad: Quad
/// :raises IOError: if an I/O error happens during the quad insertion /// :raises IOError: if an I/O error happens during the quad insertion
/// ///
/// >>> store = SledStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')))
/// >>> list(store) /// >>> list(store)
/// [<Quad subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<NamedNode value=http://example.com/g>>] /// [<Quad subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<NamedNode value=http://example.com/g>>]
@ -72,7 +69,7 @@ impl PySledStore {
/// :type quad: Quad /// :type quad: Quad
/// :raises IOError: if an I/O error happens during the quad removal /// :raises IOError: if an I/O error happens during the quad removal
/// ///
/// >>> store = SledStore() /// >>> store = Store()
/// >>> quad = Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')) /// >>> quad = Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g'))
/// >>> store.add(quad) /// >>> store.add(quad)
/// >>> store.remove(quad) /// >>> store.remove(quad)
@ -97,7 +94,7 @@ impl PySledStore {
/// :rtype: iter(Quad) /// :rtype: iter(Quad)
/// :raises IOError: if an I/O error happens during the quads lookup /// :raises IOError: if an I/O error happens during the quads lookup
/// ///
/// >>> store = SledStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')))
/// >>> list(store.quads_for_pattern(NamedNode('http://example.com'), None, None, None)) /// >>> list(store.quads_for_pattern(NamedNode('http://example.com'), None, None, None))
/// [<Quad subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<NamedNode value=http://example.com/g>>] /// [<Quad subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<NamedNode value=http://example.com/g>>]
@ -138,21 +135,21 @@ impl PySledStore {
/// ///
/// ``SELECT`` query: /// ``SELECT`` query:
/// ///
/// >>> store = SledStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> list(solution['s'] for solution in store.query('SELECT ?s WHERE { ?s ?p ?o }')) /// >>> list(solution['s'] for solution in store.query('SELECT ?s WHERE { ?s ?p ?o }'))
/// [<NamedNode value=http://example.com>] /// [<NamedNode value=http://example.com>]
/// ///
/// ``CONSTRUCT`` query: /// ``CONSTRUCT`` query:
/// ///
/// >>> store = SledStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> list(store.query('CONSTRUCT WHERE { ?s ?p ?o }')) /// >>> list(store.query('CONSTRUCT WHERE { ?s ?p ?o }'))
/// [<Triple subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>>>] /// [<Triple subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>>>]
/// ///
/// ``ASK`` query: /// ``ASK`` query:
/// ///
/// >>> store = SledStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> store.query('ASK { ?s ?p ?o }') /// >>> store.query('ASK { ?s ?p ?o }')
/// True /// True
@ -194,14 +191,14 @@ impl PySledStore {
/// ///
/// ``INSERT DATA`` update: /// ``INSERT DATA`` update:
/// ///
/// >>> store = MemoryStore() /// >>> store = Store()
/// >>> store.update('INSERT DATA { <http://example.com> <http://example.com/p> "1" }') /// >>> store.update('INSERT DATA { <http://example.com> <http://example.com/p> "1" }')
/// >>> list(store) /// >>> list(store)
/// [<Quad subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<DefaultGraph>>] /// [<Quad subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<DefaultGraph>>]
/// ///
/// ``DELETE DATA`` update: /// ``DELETE DATA`` update:
/// ///
/// >>> store = MemoryStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> store.update('DELETE DATA { <http://example.com> <http://example.com/p> "1" }') /// >>> store.update('DELETE DATA { <http://example.com> <http://example.com/p> "1" }')
/// >>> list(store) /// >>> list(store)
@ -209,7 +206,7 @@ impl PySledStore {
/// ///
/// ``DELETE`` update: /// ``DELETE`` update:
/// ///
/// >>> store = MemoryStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')))
/// >>> store.update('DELETE WHERE { <http://example.com> ?p ?o }') /// >>> store.update('DELETE WHERE { <http://example.com> ?p ?o }')
/// >>> list(store) /// >>> list(store)
@ -245,7 +242,7 @@ impl PySledStore {
/// :raises SyntaxError: if the provided data is invalid /// :raises SyntaxError: if the provided data is invalid
/// :raises IOError: if an I/O error happens during a quad insertion /// :raises IOError: if an I/O error happens during a quad insertion
/// ///
/// >>> store = SledStore() /// >>> store = Store()
/// >>> store.load(io.BytesIO(b'<foo> <p> "1" .'), "text/turtle", base_iri="http://example.com/", to_graph=NamedNode("http://example.com/g")) /// >>> store.load(io.BytesIO(b'<foo> <p> "1" .'), "text/turtle", base_iri="http://example.com/", to_graph=NamedNode("http://example.com/g"))
/// >>> list(store) /// >>> list(store)
/// [<Quad subject=<NamedNode value=http://example.com/foo> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<NamedNode value=http://example.com/g>>] /// [<Quad subject=<NamedNode value=http://example.com/foo> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>> graph_name=<NamedNode value=http://example.com/g>>]
@ -313,7 +310,7 @@ impl PySledStore {
/// :raises ValueError: if the MIME type is not supported or the `from_graph` parameter is given with a quad syntax. /// :raises ValueError: if the MIME type is not supported or the `from_graph` parameter is given with a quad syntax.
/// :raises IOError: if an I/O error happens during a quad lookup /// :raises IOError: if an I/O error happens during a quad lookup
/// ///
/// >>> store = MemoryStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')))
/// >>> output = io.BytesIO() /// >>> output = io.BytesIO()
/// >>> store.dump(output, "text/turtle", from_graph=NamedNode("http://example.com/g")) /// >>> store.dump(output, "text/turtle", from_graph=NamedNode("http://example.com/g"))
@ -359,7 +356,7 @@ impl PySledStore {
/// :rtype: iter(NamedNode or BlankNode) /// :rtype: iter(NamedNode or BlankNode)
/// :raises IOError: if an I/O error happens during the named graphs lookup /// :raises IOError: if an I/O error happens during the named graphs lookup
/// ///
/// >>> store = MemoryStore() /// >>> store = Store()
/// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g'))) /// >>> store.add(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')))
/// >>> list(store.named_graphs()) /// >>> list(store.named_graphs())
/// [<NamedNode value=http://example.com/g>] /// [<NamedNode value=http://example.com/g>]
@ -376,7 +373,7 @@ impl PySledStore {
/// :type graph_name: NamedNode or BlankNode /// :type graph_name: NamedNode or BlankNode
/// :raises IOError: if an I/O error happens during the named graph insertion /// :raises IOError: if an I/O error happens during the named graph insertion
/// ///
/// >>> store = MemoryStore() /// >>> store = Store()
/// >>> store.add_graph(NamedNode('http://example.com/g')) /// >>> store.add_graph(NamedNode('http://example.com/g'))
/// >>> list(store.named_graphs()) /// >>> list(store.named_graphs())
/// [<NamedNode value=http://example.com/g>] /// [<NamedNode value=http://example.com/g>]
@ -402,7 +399,7 @@ impl PySledStore {
/// :type graph_name: NamedNode or BlankNode or DefaultGraph /// :type graph_name: NamedNode or BlankNode or DefaultGraph
/// :raises IOError: if an I/O error happens during the named graph removal /// :raises IOError: if an I/O error happens during the named graph removal
/// ///
/// >>> store = MemoryStore() /// >>> store = Store()
/// >>> quad = Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')) /// >>> quad = Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g'))
/// >>> store.remove_graph(NamedNode('http://example.com/g')) /// >>> store.remove_graph(NamedNode('http://example.com/g'))
/// >>> list(store) /// >>> list(store)
@ -423,7 +420,7 @@ impl PySledStore {
} }
#[pyproto] #[pyproto]
impl PyObjectProtocol for PySledStore { impl PyObjectProtocol for PyStore {
fn __str__(&self) -> String { fn __str__(&self) -> String {
self.inner.to_string() self.inner.to_string()
} }
@ -434,7 +431,7 @@ impl PyObjectProtocol for PySledStore {
} }
#[pyproto] #[pyproto]
impl PySequenceProtocol for PySledStore { impl PySequenceProtocol for PyStore {
fn __len__(&self) -> usize { fn __len__(&self) -> usize {
self.inner.len() self.inner.len()
} }
@ -445,7 +442,7 @@ impl PySequenceProtocol for PySledStore {
} }
#[pyproto] #[pyproto]
impl PyIterProtocol for PySledStore { impl PyIterProtocol for PyStore {
fn __iter__(slf: PyRef<Self>) -> QuadIter { fn __iter__(slf: PyRef<Self>) -> QuadIter {
QuadIter { QuadIter {
inner: slf.inner.iter(), inner: slf.inner.iter(),
@ -490,3 +487,42 @@ impl PyIterProtocol for GraphNameIter {
.transpose() .transpose()
} }
} }
pub fn extract_quads_pattern<'a>(
subject: &'a PyAny,
predicate: &'a PyAny,
object: &'a PyAny,
graph_name: Option<&'a PyAny>,
) -> PyResult<(
Option<PyNamedOrBlankNodeRef<'a>>,
Option<PyNamedNodeRef<'a>>,
Option<PyTermRef<'a>>,
Option<PyGraphNameRef<'a>>,
)> {
Ok((
if subject.is_none() {
None
} else {
Some(subject.try_into()?)
},
if predicate.is_none() {
None
} else {
Some(predicate.try_into()?)
},
if object.is_none() {
None
} else {
Some(object.try_into()?)
},
if let Some(graph_name) = graph_name {
if graph_name.is_none() {
None
} else {
Some(graph_name.try_into()?)
}
} else {
None
},
))
}

@ -1,54 +0,0 @@
use crate::model::*;
use pyo3::exceptions::{PyIOError, PySyntaxError, PyValueError};
use pyo3::{PyAny, PyErr, PyResult};
use std::convert::TryInto;
use std::io;
pub fn extract_quads_pattern<'a>(
subject: &'a PyAny,
predicate: &'a PyAny,
object: &'a PyAny,
graph_name: Option<&'a PyAny>,
) -> PyResult<(
Option<PyNamedOrBlankNodeRef<'a>>,
Option<PyNamedNodeRef<'a>>,
Option<PyTermRef<'a>>,
Option<PyGraphNameRef<'a>>,
)> {
Ok((
if subject.is_none() {
None
} else {
Some(subject.try_into()?)
},
if predicate.is_none() {
None
} else {
Some(predicate.try_into()?)
},
if object.is_none() {
None
} else {
Some(object.try_into()?)
},
if let Some(graph_name) = graph_name {
if graph_name.is_none() {
None
} else {
Some(graph_name.try_into()?)
}
} else {
None
},
))
}
pub fn map_io_err(error: io::Error) -> PyErr {
match error.kind() {
io::ErrorKind::InvalidInput => PyValueError::new_err(error.to_string()),
io::ErrorKind::InvalidData | io::ErrorKind::UnexpectedEof => {
PySyntaxError::new_err(error.to_string())
}
_ => PyIOError::new_err(error.to_string()),
}
}

@ -1,5 +1,4 @@
import unittest import unittest
from abc import ABC, abstractmethod
from io import BytesIO from io import BytesIO
from pyoxigraph import * from pyoxigraph import *
@ -10,20 +9,16 @@ baz = NamedNode("http://baz")
graph = NamedNode("http://graph") graph = NamedNode("http://graph")
class TestAbstractStore(unittest.TestCase, ABC): class TestStore(unittest.TestCase):
@abstractmethod
def store(self):
pass
def test_add(self): def test_add(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz)) store.add(Quad(foo, bar, baz))
store.add(Quad(foo, bar, baz, DefaultGraph())) store.add(Quad(foo, bar, baz, DefaultGraph()))
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
self.assertEqual(len(store), 2) self.assertEqual(len(store), 2)
def test_remove(self): def test_remove(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz)) store.add(Quad(foo, bar, baz))
store.add(Quad(foo, bar, baz, DefaultGraph())) store.add(Quad(foo, bar, baz, DefaultGraph()))
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
@ -31,13 +26,13 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(len(store), 1) self.assertEqual(len(store), 1)
def test_len(self): def test_len(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz)) store.add(Quad(foo, bar, baz))
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
self.assertEqual(len(store), 2) self.assertEqual(len(store), 2)
def test_in(self): def test_in(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz)) store.add(Quad(foo, bar, baz))
store.add(Quad(foo, bar, baz, DefaultGraph())) store.add(Quad(foo, bar, baz, DefaultGraph()))
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
@ -47,7 +42,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertNotIn(Quad(foo, bar, baz, foo), store) self.assertNotIn(Quad(foo, bar, baz, foo), store)
def test_iter(self): def test_iter(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz, DefaultGraph())) store.add(Quad(foo, bar, baz, DefaultGraph()))
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
self.assertEqual( self.assertEqual(
@ -56,7 +51,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
) )
def test_quads_for_pattern(self): def test_quads_for_pattern(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz, DefaultGraph())) store.add(Quad(foo, bar, baz, DefaultGraph()))
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
self.assertEqual( self.assertEqual(
@ -77,13 +72,13 @@ class TestAbstractStore(unittest.TestCase, ABC):
) )
def test_ask_query(self): def test_ask_query(self):
store = self.store() store = Store()
store.add(Quad(foo, foo, foo)) store.add(Quad(foo, foo, foo))
self.assertTrue(store.query("ASK { ?s ?s ?s }")) self.assertTrue(store.query("ASK { ?s ?s ?s }"))
self.assertFalse(store.query("ASK { FILTER(false) }")) self.assertFalse(store.query("ASK { FILTER(false) }"))
def test_construct_query(self): def test_construct_query(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz)) store.add(Quad(foo, bar, baz))
results = store.query("CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }") results = store.query("CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }")
self.assertIsInstance(results, QueryTriples) self.assertIsInstance(results, QueryTriples)
@ -92,7 +87,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
) )
def test_select_query(self): def test_select_query(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz)) store.add(Quad(foo, bar, baz))
solutions = store.query("SELECT ?s ?o WHERE { ?s ?p ?o }") solutions = store.query("SELECT ?s ?o WHERE { ?s ?p ?o }")
self.assertIsInstance(solutions, QuerySolutions) self.assertIsInstance(solutions, QuerySolutions)
@ -110,7 +105,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(o, baz) self.assertEqual(o, baz)
def test_select_query_union_default_graph(self): def test_select_query_union_default_graph(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
self.assertEqual(len(list(store.query("SELECT ?s WHERE { ?s ?p ?o }"))), 0) self.assertEqual(len(list(store.query("SELECT ?s WHERE { ?s ?p ?o }"))), 0)
results = store.query( results = store.query(
@ -125,7 +120,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(len(list(results)), 1) self.assertEqual(len(list(results)), 1)
def test_select_query_with_default_graph(self): def test_select_query_with_default_graph(self):
store = self.store() store = Store()
graph_bnode = BlankNode("g") graph_bnode = BlankNode("g")
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
store.add(Quad(foo, bar, foo)) store.add(Quad(foo, bar, foo))
@ -140,7 +135,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(len(list(results)), 3) self.assertEqual(len(list(results)), 3)
def test_select_query_with_named_graph(self): def test_select_query_with_named_graph(self):
store = self.store() store = Store()
graph_bnode = BlankNode("g") graph_bnode = BlankNode("g")
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
store.add(Quad(foo, bar, foo)) store.add(Quad(foo, bar, foo))
@ -153,29 +148,29 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(len(list(results)), 2) self.assertEqual(len(list(results)), 2)
def test_update_insert_data(self): def test_update_insert_data(self):
store = self.store() store = Store()
store.update('INSERT DATA { <http://foo> <http://foo> <http://foo> }') store.update('INSERT DATA { <http://foo> <http://foo> <http://foo> }')
self.assertEqual(len(store), 1) self.assertEqual(len(store), 1)
def test_update_delete_data(self): def test_update_delete_data(self):
store = self.store() store = Store()
store.add(Quad(foo, foo, foo)) store.add(Quad(foo, foo, foo))
store.update('DELETE DATA { <http://foo> <http://foo> <http://foo> }') store.update('DELETE DATA { <http://foo> <http://foo> <http://foo> }')
self.assertEqual(len(store), 0) self.assertEqual(len(store), 0)
def test_update_delete_where(self): def test_update_delete_where(self):
store = self.store() store = Store()
store.add(Quad(foo, foo, foo)) store.add(Quad(foo, foo, foo))
store.update('DELETE WHERE { ?v ?v ?v }') store.update('DELETE WHERE { ?v ?v ?v }')
self.assertEqual(len(store), 0) self.assertEqual(len(store), 0)
def test_update_load(self): def test_update_load(self):
store = self.store() store = Store()
store.update('LOAD <https://www.w3.org/1999/02/22-rdf-syntax-ns>') store.update('LOAD <https://www.w3.org/1999/02/22-rdf-syntax-ns>')
self.assertGreater(len(store), 100) self.assertGreater(len(store), 100)
def test_load_ntriples_to_default_graph(self): def test_load_ntriples_to_default_graph(self):
store = self.store() store = Store()
store.load( store.load(
BytesIO(b"<http://foo> <http://bar> <http://baz> ."), BytesIO(b"<http://foo> <http://bar> <http://baz> ."),
mime_type="application/n-triples", mime_type="application/n-triples",
@ -183,7 +178,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(set(store), {Quad(foo, bar, baz, DefaultGraph())}) self.assertEqual(set(store), {Quad(foo, bar, baz, DefaultGraph())})
def test_load_ntriples_to_named_graph(self): def test_load_ntriples_to_named_graph(self):
store = self.store() store = Store()
store.load( store.load(
BytesIO(b"<http://foo> <http://bar> <http://baz> ."), BytesIO(b"<http://foo> <http://bar> <http://baz> ."),
mime_type="application/n-triples", mime_type="application/n-triples",
@ -192,7 +187,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(set(store), {Quad(foo, bar, baz, graph)}) self.assertEqual(set(store), {Quad(foo, bar, baz, graph)})
def test_load_turtle_with_base_iri(self): def test_load_turtle_with_base_iri(self):
store = self.store() store = Store()
store.load( store.load(
BytesIO(b"<http://foo> <http://bar> <> ."), BytesIO(b"<http://foo> <http://bar> <> ."),
mime_type="text/turtle", mime_type="text/turtle",
@ -201,7 +196,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(set(store), {Quad(foo, bar, baz, DefaultGraph())}) self.assertEqual(set(store), {Quad(foo, bar, baz, DefaultGraph())})
def test_load_nquads(self): def test_load_nquads(self):
store = self.store() store = Store()
store.load( store.load(
BytesIO(b"<http://foo> <http://bar> <http://baz> <http://graph>."), BytesIO(b"<http://foo> <http://bar> <http://baz> <http://graph>."),
mime_type="application/n-quads", mime_type="application/n-quads",
@ -209,7 +204,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(set(store), {Quad(foo, bar, baz, graph)}) self.assertEqual(set(store), {Quad(foo, bar, baz, graph)})
def test_load_trig_with_base_iri(self): def test_load_trig_with_base_iri(self):
store = self.store() store = Store()
store.load( store.load(
BytesIO(b"<http://graph> { <http://foo> <http://bar> <> . }"), BytesIO(b"<http://graph> { <http://foo> <http://bar> <> . }"),
mime_type="application/trig", mime_type="application/trig",
@ -218,7 +213,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(set(store), {Quad(foo, bar, baz, graph)}) self.assertEqual(set(store), {Quad(foo, bar, baz, graph)})
def test_dump_ntriples(self): def test_dump_ntriples(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
output = BytesIO() output = BytesIO()
store.dump(output, "application/n-triples", from_graph=graph) store.dump(output, "application/n-triples", from_graph=graph)
@ -227,7 +222,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
) )
def test_dump_nquads(self): def test_dump_nquads(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
output = BytesIO() output = BytesIO()
store.dump(output, "application/n-quads") store.dump(output, "application/n-quads")
@ -237,7 +232,7 @@ class TestAbstractStore(unittest.TestCase, ABC):
) )
def test_write_in_read(self): def test_write_in_read(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, bar)) store.add(Quad(foo, bar, bar))
store.add(Quad(foo, bar, baz)) store.add(Quad(foo, bar, baz))
for triple in store: for triple in store:
@ -245,12 +240,12 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(len(store), 4) self.assertEqual(len(store), 4)
def test_add_graph(self): def test_add_graph(self):
store = self.store() store = Store()
store.add_graph(graph) store.add_graph(graph)
self.assertEqual(list(store.named_graphs()), [graph]) self.assertEqual(list(store.named_graphs()), [graph])
def test_remove_graph(self): def test_remove_graph(self):
store = self.store() store = Store()
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
store.add_graph(NamedNode("http://graph2")) store.add_graph(NamedNode("http://graph2"))
store.remove_graph(graph) store.remove_graph(graph)
@ -259,17 +254,5 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(list(store), []) self.assertEqual(list(store), [])
class TestMemoryStore(TestAbstractStore):
def store(self):
return MemoryStore()
class TestSledStore(TestAbstractStore):
def store(self):
return SledStore()
del TestAbstractStore # We do not want to expose this class to the test runner
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

Loading…
Cancel
Save