python: Allows to unpack QuerySolution

pull/51/head
Tpt 4 years ago
parent 68597ef35a
commit 37fc3f4929
  1. 10
      lib/src/sparql/csv_results.rs
  2. 6
      lib/src/sparql/model.rs
  3. 40
      python/src/sparql.rs
  4. 2
      python/tests/test_store.py

@ -26,18 +26,17 @@ pub fn write_csv_results(
sink.write_all(variable.as_str().as_bytes())?; sink.write_all(variable.as_str().as_bytes())?;
} }
let size = solutions.variables().len();
for solution in solutions { for solution in solutions {
let solution = solution?; let solution = solution?;
sink.write_all(b"\r\n")?; sink.write_all(b"\r\n")?;
let mut start_binding = true; let mut start_binding = true;
for i in 0..size { for value in solution.values() {
if start_binding { if start_binding {
start_binding = false; start_binding = false;
} else { } else {
sink.write_all(b",")?; sink.write_all(b",")?;
} }
if let Some(value) = solution.get(i) { if let Some(value) = value {
match value { match value {
Term::NamedNode(uri) => { Term::NamedNode(uri) => {
sink.write_all(uri.as_str().as_bytes())?; sink.write_all(uri.as_str().as_bytes())?;
@ -102,18 +101,17 @@ pub fn write_tsv_results(
sink.write_all(variable.as_str().as_bytes())?; sink.write_all(variable.as_str().as_bytes())?;
} }
let size = solutions.variables().len();
for solution in solutions { for solution in solutions {
let solution = solution?; let solution = solution?;
sink.write_all(b"\n")?; sink.write_all(b"\n")?;
let mut start_binding = true; let mut start_binding = true;
for i in 0..size { for value in solution.values() {
if start_binding { if start_binding {
start_binding = false; start_binding = false;
} else { } else {
sink.write_all(b"\t")?; sink.write_all(b"\t")?;
} }
if let Some(value) = solution.get(i) { if let Some(value) = value {
//TODO: full Turtle serialization //TODO: full Turtle serialization
sink.write_all( sink.write_all(
match value { match value {

@ -321,6 +321,12 @@ impl QuerySolution {
} }
}) })
} }
/// Returns an iterator over all values, bound or not
#[inline]
pub fn values(&self) -> impl Iterator<Item = Option<&Term>> {
self.values.iter().map(|v| v.as_ref())
}
} }
/// A utility trait to get values for a given variable or tuple position /// A utility trait to get values for a given variable or tuple position

@ -1,13 +1,15 @@
use crate::model::*; use crate::model::*;
use crate::store_utils::*; use crate::store_utils::*;
use oxigraph::model::Term;
use oxigraph::sparql::*; use oxigraph::sparql::*;
use pyo3::exceptions::{RuntimeError, SyntaxError, TypeError, ValueError}; use pyo3::exceptions::{RuntimeError, SyntaxError, TypeError, ValueError};
use pyo3::prelude::{ use pyo3::prelude::{
pyclass, pymethods, pyproto, FromPyObject, IntoPy, Py, PyAny, PyCell, PyErr, PyObject, pyclass, pymethods, pyproto, FromPyObject, IntoPy, Py, PyAny, PyCell, PyErr, PyObject, PyRef,
PyRefMut, PyResult, Python, PyRefMut, PyResult, Python,
}; };
use pyo3::{PyIterProtocol, PyMappingProtocol, PyNativeType, PyObjectProtocol}; use pyo3::{PyIterProtocol, PyMappingProtocol, PyNativeType, PyObjectProtocol};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::vec::IntoIter;
pub fn build_query_options( pub fn build_query_options(
use_default_graph_as_union: bool, use_default_graph_as_union: bool,
@ -78,6 +80,7 @@ pub fn query_results_to_python(py: Python<'_>, results: QueryResults) -> PyResul
/// It is the equivalent of a row in SQL. /// It is the equivalent of a row in SQL.
/// ///
/// 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.
/// ///
/// >>> store = SledStore() /// >>> store = SledStore()
/// >>> 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')))
@ -88,6 +91,9 @@ pub fn query_results_to_python(py: Python<'_>, results: QueryResults) -> PyResul
/// <NamedNode value=http://example.com> /// <NamedNode value=http://example.com>
/// >>> solution[0] /// >>> solution[0]
/// <NamedNode value=http://example.com> /// <NamedNode value=http://example.com>
/// >>> s, p, o = solution
/// >>> s
/// <NamedNode value=http://example.com>
#[pyclass(unsendable, name = QuerySolution)] #[pyclass(unsendable, name = QuerySolution)]
pub struct PyQuerySolution { pub struct PyQuerySolution {
inner: QuerySolution, inner: QuerySolution,
@ -141,6 +147,38 @@ impl PyMappingProtocol for PyQuerySolution {
} }
} }
#[pyproto]
impl PyIterProtocol for PyQuerySolution {
fn __iter__(slf: PyRef<Self>) -> SolutionValueIter {
SolutionValueIter {
inner: slf
.inner
.values()
.map(|v| v.cloned())
.collect::<Vec<_>>()
.into_iter(),
}
}
}
#[pyclass(unsendable)]
pub struct SolutionValueIter {
inner: IntoIter<Option<Term>>,
}
#[pyproto]
impl PyIterProtocol for SolutionValueIter {
fn __iter__(slf: PyRefMut<Self>) -> Py<Self> {
slf.into()
}
fn __next__(mut slf: PyRefMut<Self>) -> Option<Option<PyObject>> {
slf.inner
.next()
.map(|v| v.map(|v| term_to_python(slf.py(), v)))
}
}
/// 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 = SledStore()

@ -102,6 +102,8 @@ class TestAbstractStore(unittest.TestCase, ABC):
self.assertEqual(solution[0], foo) self.assertEqual(solution[0], foo)
self.assertEqual(solution["s"], foo) self.assertEqual(solution["s"], foo)
self.assertEqual(solution[Variable("s")], foo) self.assertEqual(solution[Variable("s")], foo)
s, = solution
self.assertEqual(s, foo)
def test_select_query_union_default_graph(self): def test_select_query_union_default_graph(self):
store = self.store() store = self.store()

Loading…
Cancel
Save