use oxigraph::model::*; use oxigraph::sparql::Variable; use pyo3::basic::CompareOp; use pyo3::exceptions::{IndexError, NotImplementedError, TypeError, ValueError}; use pyo3::prelude::*; use pyo3::{PyIterProtocol, PyMappingProtocol, PyObjectProtocol}; use std::collections::hash_map::DefaultHasher; use std::hash::Hash; use std::hash::Hasher; use std::vec::IntoIter; /// An RDF `node identified by an IRI `_ /// /// :param value: the IRI as a string /// :type value: str /// :raises ValueError: if the IRI is not valid according to `RFC 3987 `_ /// /// The :py:func:`str` function provides a serialization compatible with NTriples, Turtle and SPARQL: /// /// >>> str(NamedNode('http://example.com')) /// '' #[pyclass(name = NamedNode)] #[text_signature = "(value)"] #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] pub struct PyNamedNode { inner: NamedNode, } impl From for PyNamedNode { fn from(inner: NamedNode) -> Self { Self { inner } } } impl From for NamedNode { fn from(node: PyNamedNode) -> Self { node.inner } } impl From for NamedOrBlankNode { fn from(node: PyNamedNode) -> Self { node.inner.into() } } impl From for Term { fn from(node: PyNamedNode) -> Self { node.inner.into() } } impl From for GraphName { fn from(node: PyNamedNode) -> Self { node.inner.into() } } #[pymethods] impl PyNamedNode { #[new] fn new(value: String) -> PyResult { Ok(NamedNode::new(value) .map_err(|e| ValueError::py_err(e.to_string()))? .into()) } /// :return: the named node IRI /// :rtype: str /// /// >>> NamedNode("http://example.com").value /// 'http://example.com' #[getter] fn value(&self) -> &str { self.inner.as_str() } } #[pyproto] impl PyObjectProtocol for PyNamedNode { fn __str__(&self) -> String { self.inner.to_string() } fn __repr__(&self) -> String { let mut buffer = String::new(); named_node_repr(self.inner.as_ref(), &mut buffer); buffer } fn __hash__(&self) -> u64 { hash(&self.inner) } fn __richcmp__(&self, other: &PyCell, op: CompareOp) -> bool { eq_ord_compare(self, &other.borrow(), op) } } /// An RDF `blank node `_ /// /// :param value: the `blank node ID `_ (if not present, a random blank node ID is automatically generated). /// :type value: str, optional /// /// The :py:func:`str` function provides a serialization compatible with NTriples, Turtle and SPARQL: /// /// >>> str(BlankNode('ex')) /// '_:ex' #[pyclass(name = BlankNode)] #[text_signature = "(value)"] #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct PyBlankNode { inner: BlankNode, } impl From for PyBlankNode { fn from(inner: BlankNode) -> Self { Self { inner } } } impl From for BlankNode { fn from(node: PyBlankNode) -> Self { node.inner } } impl From for NamedOrBlankNode { fn from(node: PyBlankNode) -> Self { node.inner.into() } } impl From for Term { fn from(node: PyBlankNode) -> Self { node.inner.into() } } impl From for GraphName { fn from(node: PyBlankNode) -> Self { node.inner.into() } } #[pymethods] impl PyBlankNode { #[new] fn new(value: Option) -> PyResult { Ok(if let Some(value) = value { BlankNode::new(value).map_err(|e| ValueError::py_err(e.to_string()))? } else { BlankNode::default() } .into()) } /// :return: the `blank node ID `_ /// :rtype: str /// /// >>> BlankNode("ex").value /// 'ex' #[getter] fn value(&self) -> &str { self.inner.as_str() } } #[pyproto] impl PyObjectProtocol for PyBlankNode { fn __str__(&self) -> String { self.inner.to_string() } fn __repr__(&self) -> String { let mut buffer = String::new(); blank_node_repr(self.inner.as_ref(), &mut buffer); buffer } fn __hash__(&self) -> u64 { hash(&self.inner) } fn __richcmp__(&self, other: &PyCell, op: CompareOp) -> PyResult { eq_compare(self, &other.borrow(), op) } } /// An RDF `literal `_ /// /// :param value: the literal value or `lexical form `_ /// :type value: str /// :param datatype: the literal `datatype IRI `_. /// :type datatype: NamedNode, optional /// :param language: the literal `language tag `_ /// :type language: str, optional /// :raises ValueError: if the language tag is not valid according to `RFC 5646 `_ (`BCP 47 `_) /// /// The :py:func:`str` function provides a serialization compatible with NTriples, Turtle and SPARQL: /// /// >>> str(Literal('example')) /// '"example"' /// >>> str(Literal('example', language='en')) /// '"example"@en' /// >>> str(Literal('11', datatype=NamedNode('http://www.w3.org/2001/XMLSchema#integer'))) /// '"11"^^' #[pyclass(name = Literal)] #[text_signature = "(value, *, datatype = None, language = None)"] #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct PyLiteral { inner: Literal, } impl From for PyLiteral { fn from(inner: Literal) -> Self { Self { inner } } } impl From for Literal { fn from(literal: PyLiteral) -> Self { literal.inner } } impl From for Term { fn from(node: PyLiteral) -> Self { node.inner.into() } } #[pymethods] impl PyLiteral { #[new] #[args(value, "*", datatype = "None", language = "None")] fn new( value: String, language: Option, datatype: Option, ) -> PyResult { Ok(if let Some(language) = language { if let Some(datatype) = datatype { if datatype.value() != "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString" { return Err(ValueError::py_err( "The literals with a language tag must use the rdf:langString datatype", )); } } Literal::new_language_tagged_literal(value, language) .map_err(|e| ValueError::py_err(e.to_string()))? } else if let Some(datatype) = datatype { Literal::new_typed_literal(value, datatype) } else { Literal::new_simple_literal(value) } .into()) } /// :return: the literal value or `lexical form `_ /// :rtype: str /// /// >>> Literal("example").value /// 'example' #[getter] fn value(&self) -> &str { self.inner.value() } /// :return: the literal `language tag `_ /// :rtype: str or None /// /// >>> Literal('example', language='en').language /// 'en' /// >>> Literal('example').language /// #[getter] fn language(&self) -> Option<&str> { self.inner.language() } /// :return: the literal `datatype IRI `_ /// :rtype: NamedNode /// /// >>> Literal('11', datatype=NamedNode('http://www.w3.org/2001/XMLSchema#integer')).datatype /// /// >>> Literal('example').datatype /// /// >>> Literal('example', language='en').datatype /// #[getter] fn datatype(&self) -> PyNamedNode { self.inner.datatype().into_owned().into() } } #[pyproto] impl PyObjectProtocol for PyLiteral { fn __str__(&self) -> String { self.inner.to_string() } fn __repr__(&self) -> String { let mut buffer = String::new(); literal_repr(self.inner.as_ref(), &mut buffer); buffer } fn __hash__(&self) -> u64 { hash(&self.inner) } fn __richcmp__(&self, other: &PyCell, op: CompareOp) -> PyResult { eq_compare(self, &other.borrow(), op) } } /// The RDF `default graph name `_ #[pyclass(name = DefaultGraph)] #[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] pub struct PyDefaultGraph {} impl From for GraphName { fn from(_: PyDefaultGraph) -> Self { GraphName::DefaultGraph } } #[pymethods] impl PyDefaultGraph { #[new] fn new() -> Self { PyDefaultGraph {} } #[getter] fn value(&self) -> &str { "" } } #[pyproto] impl PyObjectProtocol for PyDefaultGraph { fn __str__(&self) -> &'p str { "DEFAULT" } fn __repr__(&self) -> &'p str { "" } fn __hash__(&self) -> u64 { 0 } fn __richcmp__(&self, other: &PyCell, op: CompareOp) -> PyResult { eq_compare(self, &other.borrow(), op) } } /// An RDF `triple `_ /// /// :param subject: the triple subject /// :type subject: NamedNode or BlankNode /// :param predicate: the triple predicate /// :type predicate: NamedNode /// :param object: the triple object /// :type object: NamedNode or BlankNode or Literal /// /// The :py:func:`str` function provides a serialization compatible with NTriples, Turtle and SPARQL: /// /// >>> str(Triple(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'))) /// ' "1" .' /// /// A triple could also be easily destructed into its components: /// /// >>> (s, p, o) = Triple(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')) #[pyclass(name = Triple)] #[derive(Eq, PartialEq, Debug, Clone, Hash)] #[text_signature = "(subject, predicate, object)"] pub struct PyTriple { inner: Triple, } impl From for PyTriple { fn from(inner: Triple) -> Self { Self { inner } } } impl From for Triple { fn from(node: PyTriple) -> Self { node.inner } } impl<'a> From<&'a PyTriple> for TripleRef<'a> { fn from(node: &'a PyTriple) -> Self { node.inner.as_ref() } } #[pymethods] impl PyTriple { #[new] fn new(subject: &PyAny, predicate: &PyAny, object: &PyAny) -> PyResult { Ok(Triple::new( extract_named_or_blank_node(subject)?, extract_named_node(predicate)?, extract_term(object)?, ) .into()) } /// :return: the triple subject /// :rtype: NamedNode or BlankNode /// /// >>> Triple(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')).subject /// #[getter] fn subject(&self, py: Python<'_>) -> PyObject { named_or_blank_node_to_python(py, self.inner.subject.clone()) } /// :return: the triple predicate /// :rtype: NamedNode /// /// >>> Triple(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')).predicate /// #[getter] fn predicate(&self) -> PyNamedNode { self.inner.predicate.clone().into() } /// :return: the triple object /// :rtype: NamedNode or BlankNode or Literal /// /// >>> Triple(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')).object /// > #[getter] fn object(&self, py: Python<'_>) -> PyObject { term_to_python(py, self.inner.object.clone()) } } #[pyproto] impl PyObjectProtocol for PyTriple { fn __str__(&self) -> String { self.inner.to_string() } fn __repr__(&self) -> String { let mut buffer = String::new(); buffer.push_str(") -> TripleComponentsIter { TripleComponentsIter { inner: vec![ slf.inner.subject.clone().into(), slf.inner.predicate.clone().into(), slf.inner.object.clone(), ] .into_iter(), } } } /// An RDF `triple `_ /// in a `RDF dataset `_ /// /// :param subject: the quad subject /// :type subject: NamedNode or BlankNode /// :param predicate: the quad predicate /// :type predicate: NamedNode /// :param object: the quad object /// :type object: NamedNode or BlankNode or Literal /// :param graph: the quad graph name. If not present, the default graph is assumed. /// :type graph: NamedNode or BlankNode or DefaultGraph or None, optional /// /// The :py:func:`str` function provides a serialization compatible with NTriples, Turtle and SPARQL: /// /// >>> str(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g'))) /// ' "1" .' /// /// >>> str(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), DefaultGraph())) /// ' "1" .' /// /// A quad could also be easily destructed into its components: /// /// >>> (s, p, o, g) = Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')) #[pyclass(name = Quad)] #[text_signature = "(subject, predicate, object, graph_name = None)"] #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct PyQuad { inner: Quad, } impl From for PyQuad { fn from(inner: Quad) -> Self { Self { inner } } } impl From for Quad { fn from(node: PyQuad) -> Self { node.inner } } impl<'a> From<&'a PyQuad> for QuadRef<'a> { fn from(node: &'a PyQuad) -> Self { node.inner.as_ref() } } #[pymethods] impl PyQuad { #[new] fn new( subject: &PyAny, predicate: &PyAny, object: &PyAny, graph_name: Option<&PyAny>, ) -> PyResult { Ok(Quad::new( extract_named_or_blank_node(subject)?, extract_named_node(predicate)?, extract_term(object)?, if let Some(graph_name) = graph_name { extract_graph_name(graph_name)? } else { GraphName::DefaultGraph }, ) .into()) } /// :return: the quad subject /// :rtype: NamedNode or BlankNode /// /// >>> Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')).subject /// #[getter] fn subject(&self, py: Python<'_>) -> PyObject { named_or_blank_node_to_python(py, self.inner.subject.clone()) } /// :return: the quad predicate /// :rtype: NamedNode /// /// >>> Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')).predicate /// #[getter] fn predicate(&self) -> PyNamedNode { self.inner.predicate.clone().into() } /// :return: the quad object /// :rtype: NamedNode or BlankNode or Literal /// /// >>> Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')).object /// > #[getter] fn object(&self, py: Python<'_>) -> PyObject { term_to_python(py, self.inner.object.clone()) } /// :return: the quad graph name /// :rtype: NamedNode or BlankNode or DefaultGraph /// /// >>> Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')).graph_name /// #[getter] fn graph_name(&self, py: Python<'_>) -> PyObject { graph_name_to_python(py, self.inner.graph_name.clone()) } /// :return: the quad underlying triple /// :rtype: Triple /// /// >>> Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g')).triple /// predicate= object=>> #[getter] fn triple(&self) -> PyTriple { Triple::from(self.inner.clone()).into() } } #[pyproto] impl PyObjectProtocol for PyQuad { fn __str__(&self) -> String { self.inner.to_string() } fn __repr__(&self) -> String { let mut buffer = String::new(); buffer.push_str(") -> QuadComponentsIter { QuadComponentsIter { inner: vec![ Some(slf.inner.subject.clone().into()), Some(slf.inner.predicate.clone().into()), Some(slf.inner.object.clone()), match slf.inner.graph_name.clone() { GraphName::NamedNode(node) => Some(node.into()), GraphName::BlankNode(node) => Some(node.into()), GraphName::DefaultGraph => None, }, ] .into_iter(), } } } /// A SPARQL query variable /// /// :param value: the variable name as a string /// :type value: str /// /// The :py:func:`str` function provides a serialization compatible with SPARQL: /// /// >>> str(Variable('foo')) /// '?foo' #[pyclass(name = Variable)] #[text_signature = "(value)"] #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct PyVariable { inner: Variable, } impl From for PyVariable { fn from(inner: Variable) -> Self { Self { inner } } } impl From for Variable { fn from(variable: PyVariable) -> Self { variable.inner } } impl<'a> From<&'a PyVariable> for &'a Variable { fn from(variable: &'a PyVariable) -> Self { &variable.inner } } #[pymethods] impl PyVariable { #[new] fn new(value: String) -> Self { Variable::new(value).into() } /// :return: the variable name /// :rtype: str /// /// >>> Variable("foo").value /// 'foo' #[getter] fn value(&self) -> &str { self.inner.as_str() } } #[pyproto] impl PyObjectProtocol for PyVariable { fn __str__(&self) -> String { self.inner.to_string() } fn __repr__(&self) -> String { format!("", self.inner.as_str()) } fn __hash__(&self) -> u64 { hash(&self.inner) } fn __richcmp__(&self, other: &PyCell, op: CompareOp) -> PyResult { eq_compare(self, &other.borrow(), op) } } pub fn extract_named_node(py: &PyAny) -> PyResult { if let Ok(node) = py.downcast::>() { Ok(node.borrow().clone().into()) } else { Err(TypeError::py_err(format!( "{} is not an RDF named node", py.get_type().name(), ))) } } pub fn extract_named_or_blank_node(py: &PyAny) -> PyResult { if let Ok(node) = py.downcast::>() { Ok(node.borrow().clone().into()) } else if let Ok(node) = py.downcast::>() { Ok(node.borrow().clone().into()) } else { Err(TypeError::py_err(format!( "{} is not an RDF named or blank node", py.get_type().name(), ))) } } pub fn named_or_blank_node_to_python(py: Python<'_>, node: NamedOrBlankNode) -> PyObject { match node { NamedOrBlankNode::NamedNode(node) => PyNamedNode::from(node).into_py(py), NamedOrBlankNode::BlankNode(node) => PyBlankNode::from(node).into_py(py), } } pub fn extract_term(py: &PyAny) -> PyResult { if let Ok(node) = py.downcast::>() { Ok(node.borrow().clone().into()) } else if let Ok(node) = py.downcast::>() { Ok(node.borrow().clone().into()) } else if let Ok(literal) = py.downcast::>() { Ok(literal.borrow().clone().into()) } else { Err(TypeError::py_err(format!( "{} is not an RDF named or blank node", py.get_type().name(), ))) } } pub fn term_to_python(py: Python<'_>, term: Term) -> PyObject { match term { Term::NamedNode(node) => PyNamedNode::from(node).into_py(py), Term::BlankNode(node) => PyBlankNode::from(node).into_py(py), Term::Literal(literal) => PyLiteral::from(literal).into_py(py), } } pub fn extract_graph_name(py: &PyAny) -> PyResult { if let Ok(node) = py.downcast::>() { Ok(node.borrow().clone().into()) } else if let Ok(node) = py.downcast::>() { Ok(node.borrow().clone().into()) } else if let Ok(node) = py.downcast::>() { Ok(node.borrow().clone().into()) } else { Err(TypeError::py_err(format!( "{} is not a valid RDF graph name", py.get_type().name(), ))) } } pub fn graph_name_to_python(py: Python<'_>, name: GraphName) -> PyObject { match name { GraphName::NamedNode(node) => PyNamedNode::from(node).into_py(py), GraphName::BlankNode(node) => PyBlankNode::from(node).into_py(py), GraphName::DefaultGraph => PyDefaultGraph::new().into_py(py), } } fn eq_compare(a: &T, b: &T, op: CompareOp) -> PyResult { match op { CompareOp::Eq => Ok(a == b), CompareOp::Ne => Ok(a != b), _ => Err(NotImplementedError::py_err("Ordering is not implemented")), } } fn eq_ord_compare(a: &T, b: &T, op: CompareOp) -> bool { match op { CompareOp::Lt => a < b, CompareOp::Le => a <= b, CompareOp::Eq => a == b, CompareOp::Ne => a != b, CompareOp::Gt => a > b, CompareOp::Ge => a >= b, } } fn hash(t: &impl Hash) -> u64 { let mut s = DefaultHasher::new(); t.hash(&mut s); s.finish() } fn named_node_repr(node: NamedNodeRef<'_>, buffer: &mut String) { buffer.push_str(""), } } #[pyclass(unsendable)] pub struct TripleComponentsIter { inner: IntoIter, } #[pyproto] impl PyIterProtocol for TripleComponentsIter { fn __iter__(slf: PyRefMut) -> Py { slf.into() } fn __next__(mut slf: PyRefMut) -> Option { slf.inner.next().map(move |t| term_to_python(slf.py(), t)) } } #[pyclass(unsendable)] pub struct QuadComponentsIter { inner: IntoIter>, } #[pyproto] impl PyIterProtocol for QuadComponentsIter { fn __iter__(slf: PyRefMut) -> Py { slf.into() } fn __next__(mut slf: PyRefMut) -> Option { slf.inner.next().map(move |t| { if let Some(t) = t { term_to_python(slf.py(), t) } else { PyDefaultGraph {}.into_py(slf.py()) } }) } }