diff --git a/python/examples/query.py b/python/examples/query.py new file mode 100644 index 00000000..ac7baff0 --- /dev/null +++ b/python/examples/query.py @@ -0,0 +1,21 @@ +from pyrudof import Rudof, RudofConfig, QuerySolutions + +rudof = Rudof(RudofConfig()) +rdf = """prefix : +:alice a :Person ; + :name "Alice" ; + :knows :bob . +:bob a :Person ; + :name "Robert" . +""" +rudof.read_data_str(rdf) + +query = """prefix : +select * where { + ?x a :Person +} +""" + +results = rudof.run_query_str(query) +for result in iter(results): + print(result.show()) diff --git a/python/src/lib.rs b/python/src/lib.rs index 5ad8f7f5..77e70757 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -10,10 +10,10 @@ pub mod pyrudof { #[pymodule_export] use super::{ - PyDCTAP, PyDCTapFormat, PyRDFFormat, PyReaderMode, PyRudof, PyRudofConfig, PyRudofError, - PyShExFormat, PyShExFormatter, PyShaclFormat, PyShaclValidationMode, PyShapeMapFormat, - PyShapeMapFormatter, PyShapesGraphSource, PyUmlGenerationMode, PyValidationReport, - PyValidationStatus, + PyDCTAP, PyDCTapFormat, PyQuerySolution, PyQuerySolutions, PyRDFFormat, PyReaderMode, + PyRudof, PyRudofConfig, PyRudofError, PyShExFormat, PyShExFormatter, PyShaclFormat, + PyShaclValidationMode, PyShapeMapFormat, PyShapeMapFormatter, PyShapesGraphSource, + PyUmlGenerationMode, PyValidationReport, PyValidationStatus, }; #[pymodule_init] diff --git a/python/src/pyrudof_lib.rs b/python/src/pyrudof_lib.rs index ab9a7da5..eb87bfa5 100644 --- a/python/src/pyrudof_lib.rs +++ b/python/src/pyrudof_lib.rs @@ -1,10 +1,12 @@ //! This is a wrapper of the methods provided by `rudof_lib` //! -use pyo3::{exceptions::PyValueError, pyclass, pymethods, PyErr, PyResult, Python}; +use pyo3::{ + exceptions::PyValueError, pyclass, pymethods, Py, PyErr, PyRef, PyRefMut, PyResult, Python, +}; use rudof_lib::{ - iri, DCTAPFormat, PrefixMap, QueryShapeMap, QuerySolutions, RDFFormat, RdfData, ReaderMode, - ResultShapeMap, Rudof, RudofConfig, RudofError, ShExFormat, ShExFormatter, ShExSchema, - ShaclFormat, ShaclSchema, ShaclValidationMode, ShapeMapFormat, ShapeMapFormatter, + iri, DCTAPFormat, PrefixMap, QueryShapeMap, QuerySolution, QuerySolutions, RDFFormat, RdfData, + ReaderMode, ResultShapeMap, Rudof, RudofConfig, RudofError, ShExFormat, ShExFormatter, + ShExSchema, ShaclFormat, ShaclSchema, ShaclValidationMode, ShapeMapFormat, ShapeMapFormatter, ShapesGraphSource, UmlGenerationMode, ValidationReport, ValidationStatus, DCTAP, }; use std::{ffi::OsStr, fs::File, io::BufReader, path::Path}; @@ -123,6 +125,28 @@ impl PyRudof { shacl_schema.map(|s| PyShaclSchema { inner: s.clone() }) } + /// Run a SPARQL query obtained from a string on the RDF data + #[pyo3(signature = (input))] + pub fn run_query_str(&mut self, input: &str) -> PyResult { + let results = self.inner.run_query_str(input).map_err(cnv_err)?; + Ok(PyQuerySolutions { inner: results }) + } + + /// Run a SPARQL query obtained from a file path on the RDF data + #[pyo3(signature = (path_name))] + pub fn run_query_path(&mut self, path_name: &str) -> PyResult { + let path = Path::new(path_name); + let file = File::open::<&OsStr>(path.as_ref()) + .map_err(|e| RudofError::ReadingDCTAPPath { + path: path_name.to_string(), + error: format!("{e}"), + }) + .map_err(cnv_err)?; + let mut reader = BufReader::new(file); + let results = self.inner.run_query(&mut reader).map_err(cnv_err)?; + Ok(PyQuerySolutions { inner: results }) + } + /// Reads DCTAP from a String #[pyo3(signature = (input, format = &PyDCTapFormat::CSV))] pub fn read_dctap_str(&mut self, input: &str, format: &PyDCTapFormat) -> PyResult<()> { @@ -683,15 +707,60 @@ pub enum PyShapesGraphSource { CurrentSchema, } -#[pyclass(name = "QuerySulutions")] +#[pyclass(name = "QuerySolution")] +pub struct PyQuerySolution { + inner: QuerySolution, +} + +#[pymethods] +impl PyQuerySolution { + pub fn show(&self) -> String { + self.inner.show().to_string() + } +} + +#[pyclass(name = "QuerySolutions")] pub struct PyQuerySolutions { inner: QuerySolutions, } +#[pymethods] impl PyQuerySolutions { pub fn show(&self) -> String { format!("Solutions: {:?}", self.inner) } + + pub fn count(&self) -> usize { + self.inner.count() + } + + fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { + let rs: Vec = slf + .inner + .iter() + .map(|qs| PyQuerySolution { inner: qs.clone() }) + .collect(); + let iter = QuerySolutionIter { + inner: rs.into_iter(), + }; + Py::new(slf.py(), iter) + } +} + +#[pyclass] +struct QuerySolutionIter { + inner: std::vec::IntoIter, +} + +#[pymethods] +impl QuerySolutionIter { + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { + slf.inner.next() + } } #[pyclass(frozen, name = "ResultShapeMap")]