use oxigraph::model::*; use oxigraph::sparql::Variable; use pyo3::basic::CompareOp; use pyo3::exceptions::{PyIndexError, PyNotImplementedError, PyTypeError, PyValueError}; use pyo3::prelude::*; use pyo3::PyTypeInfo; 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", module = "oxigraph")] #[pyo3(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 Subject { 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| PyValueError::new_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() } 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: &PyAny, op: CompareOp) -> PyResult { if let Ok(other) = other.downcast::>() { Ok(eq_ord_compare(self, &other.borrow(), op)) } else if PyBlankNode::is_type_of(other) || PyLiteral::is_type_of(other) || PyDefaultGraph::is_type_of(other) { eq_compare_other_type(op) } else { Err(PyTypeError::new_err( "NamedNode could only be compared with RDF terms", )) } } } /// 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 /// :raises ValueError: if the blank node ID is invalid according to NTriples, Turtle and SPARQL grammars. /// /// The :py:func:`str` function provides a serialization compatible with NTriples, Turtle and SPARQL: /// /// >>> str(BlankNode('ex')) /// '_:ex' #[pyclass(name = "BlankNode", module = "oxigraph")] #[pyo3(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 Subject { 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| PyValueError::new_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() } 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: &PyAny, op: CompareOp) -> PyResult { if let Ok(other) = other.downcast::>() { eq_compare(self, &other.borrow(), op) } else if PyNamedNode::is_type_of(other) || PyLiteral::is_type_of(other) || PyDefaultGraph::is_type_of(other) { eq_compare_other_type(op) } else { Err(PyTypeError::new_err( "BlankNode could only be compared with RDF terms", )) } } } /// 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", module = "oxigraph")] #[pyo3(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(PyValueError::new_err( "The literals with a language tag must use the rdf:langString datatype", )); } } Literal::new_language_tagged_literal(value, language) .map_err(|e| PyValueError::new_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() } 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: &PyAny, op: CompareOp) -> PyResult { if let Ok(other) = other.downcast::>() { eq_compare(self, &other.borrow(), op) } else if PyNamedNode::is_type_of(other) || PyBlankNode::is_type_of(other) || PyDefaultGraph::is_type_of(other) { eq_compare_other_type(op) } else { Err(PyTypeError::new_err( "Literal could only be compared with RDF terms", )) } } } /// The RDF `default graph name `_. #[pyclass(name = "DefaultGraph", module = "oxigraph")] #[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 { Self {} } #[getter] fn value(&self) -> &str { "" } fn __str__(&self) -> &str { "DEFAULT" } fn __repr__(&self) -> &str { "" } fn __hash__(&self) -> u64 { 0 } fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult { if let Ok(other) = other.downcast::>() { eq_compare(self, &other.borrow(), op) } else if PyNamedNode::is_type_of(other) || PyBlankNode::is_type_of(other) || PyLiteral::is_type_of(other) { eq_compare_other_type(op) } else { Err(PyTypeError::new_err( "DefaultGraph could only be compared with RDF terms", )) } } } #[derive(FromPyObject)] pub enum PyNamedOrBlankNode { NamedNode(PyNamedNode), BlankNode(PyBlankNode), } impl From for NamedOrBlankNode { fn from(node: PyNamedOrBlankNode) -> Self { match node { PyNamedOrBlankNode::NamedNode(node) => node.into(), PyNamedOrBlankNode::BlankNode(node) => node.into(), } } } impl From for PyNamedOrBlankNode { fn from(node: NamedOrBlankNode) -> Self { match node { NamedOrBlankNode::NamedNode(node) => Self::NamedNode(node.into()), NamedOrBlankNode::BlankNode(node) => Self::BlankNode(node.into()), } } } impl IntoPy for PyNamedOrBlankNode { fn into_py(self, py: Python<'_>) -> PyObject { match self { Self::NamedNode(node) => node.into_py(py), Self::BlankNode(node) => node.into_py(py), } } } #[derive(FromPyObject)] pub enum PySubject { NamedNode(PyNamedNode), BlankNode(PyBlankNode), Triple(PyTriple), } impl From for Subject { fn from(node: PySubject) -> Self { match node { PySubject::NamedNode(node) => node.into(), PySubject::BlankNode(node) => node.into(), PySubject::Triple(triple) => triple.into(), } } } impl From for PySubject { fn from(node: Subject) -> Self { match node { Subject::NamedNode(node) => Self::NamedNode(node.into()), Subject::BlankNode(node) => Self::BlankNode(node.into()), Subject::Triple(triple) => Self::Triple(triple.as_ref().clone().into()), } } } impl IntoPy for PySubject { fn into_py(self, py: Python<'_>) -> PyObject { match self { Self::NamedNode(node) => node.into_py(py), Self::BlankNode(node) => node.into_py(py), Self::Triple(triple) => triple.into_py(py), } } } #[derive(FromPyObject)] pub enum PyTerm { NamedNode(PyNamedNode), BlankNode(PyBlankNode), Literal(PyLiteral), Triple(PyTriple), } impl From for Term { fn from(term: PyTerm) -> Self { match term { PyTerm::NamedNode(node) => node.into(), PyTerm::BlankNode(node) => node.into(), PyTerm::Literal(literal) => literal.into(), PyTerm::Triple(triple) => triple.into(), } } } impl From for PyTerm { fn from(term: Term) -> Self { match term { Term::NamedNode(node) => Self::NamedNode(node.into()), Term::BlankNode(node) => Self::BlankNode(node.into()), Term::Literal(literal) => Self::Literal(literal.into()), Term::Triple(triple) => Self::Triple(triple.as_ref().clone().into()), } } } impl IntoPy for PyTerm { fn into_py(self, py: Python<'_>) -> PyObject { match self { Self::NamedNode(node) => node.into_py(py), Self::BlankNode(node) => node.into_py(py), Self::Literal(literal) => literal.into_py(py), Self::Triple(triple) => triple.into_py(py), } } } /// 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", module = "oxigraph")] #[derive(Eq, PartialEq, Debug, Clone, Hash)] #[pyo3(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(triple: PyTriple) -> Self { triple.inner } } impl<'a> From<&'a PyTriple> for TripleRef<'a> { fn from(triple: &'a PyTriple) -> Self { triple.inner.as_ref() } } impl From for Subject { fn from(triple: PyTriple) -> Self { triple.inner.into() } } impl From for Term { fn from(triple: PyTriple) -> Self { triple.inner.into() } } #[pymethods] impl PyTriple { #[new] fn new(subject: PySubject, predicate: PyNamedNode, object: PyTerm) -> Self { Triple::new(subject, predicate, 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) -> PySubject { self.inner.subject.clone().into() } /// :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) -> PyTerm { self.inner.object.clone().into() } fn __str__(&self) -> String { self.inner.to_string() } fn __repr__(&self) -> String { let mut buffer = String::new(); triple_repr(self.inner.as_ref(), &mut buffer); buffer } fn __hash__(&self) -> u64 { hash(&self.inner) } fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult { eq_compare(self, other, op) } fn __len__(&self) -> usize { 3 } fn __getitem__(&self, input: usize) -> PyResult { match input { 0 => Ok(Term::from(self.inner.subject.clone()).into()), 1 => Ok(Term::from(self.inner.predicate.clone()).into()), 2 => Ok(self.inner.object.clone().into()), _ => Err(PyIndexError::new_err("A triple has only 3 elements")), } } fn __iter__(&self) -> TripleComponentsIter { TripleComponentsIter { inner: vec![ self.inner.subject.clone().into(), self.inner.predicate.clone().into(), self.inner.object.clone(), ] .into_iter(), } } } #[derive(FromPyObject)] pub enum PyGraphName { NamedNode(PyNamedNode), BlankNode(PyBlankNode), DefaultGraph(PyDefaultGraph), } impl From for GraphName { fn from(graph_name: PyGraphName) -> Self { match graph_name { PyGraphName::NamedNode(node) => node.into(), PyGraphName::BlankNode(node) => node.into(), PyGraphName::DefaultGraph(default_graph) => default_graph.into(), } } } impl From for PyGraphName { fn from(graph_name: GraphName) -> Self { match graph_name { GraphName::NamedNode(node) => Self::NamedNode(node.into()), GraphName::BlankNode(node) => Self::BlankNode(node.into()), GraphName::DefaultGraph => Self::DefaultGraph(PyDefaultGraph::new()), } } } impl IntoPy for PyGraphName { fn into_py(self, py: Python<'_>) -> PyObject { match self { Self::NamedNode(node) => node.into_py(py), Self::BlankNode(node) => node.into_py(py), Self::DefaultGraph(node) => node.into_py(py), } } } /// 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", module = "oxigraph")] #[pyo3(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: PySubject, predicate: PyNamedNode, object: PyTerm, graph_name: Option, ) -> Self { Quad::new( subject, predicate, object, graph_name.unwrap_or(PyGraphName::DefaultGraph(PyDefaultGraph {})), ) .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) -> PySubject { self.inner.subject.clone().into() } /// :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) -> PyTerm { self.inner.object.clone().into() } /// :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) -> PyGraphName { self.inner.graph_name.clone().into() } /// :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() } 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 /// :raises ValueError: if the variable name is invalid according to the SPARQL grammar. /// /// The :py:func:`str` function provides a serialization compatible with SPARQL: /// /// >>> str(Variable('foo')) /// '?foo' #[pyclass(name = "Variable", module = "oxigraph")] #[pyo3(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) -> PyResult { Ok(Variable::new(value) .map_err(|e| PyValueError::new_err(e.to_string()))? .into()) } /// :return: the variable name. /// :rtype: str /// /// >>> Variable("foo").value /// 'foo' #[getter] fn value(&self) -> &str { self.inner.as_str() } 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: &Self, op: CompareOp) -> PyResult { eq_compare(self, other, op) } } pub struct PyNamedNodeRef<'a>(PyRef<'a, PyNamedNode>); impl<'a> From<&'a PyNamedNodeRef<'a>> for NamedNodeRef<'a> { fn from(value: &'a PyNamedNodeRef<'a>) -> Self { value.0.inner.as_ref() } } impl<'a> TryFrom<&'a PyAny> for PyNamedNodeRef<'a> { type Error = PyErr; fn try_from(value: &'a PyAny) -> PyResult { if let Ok(node) = value.downcast::>() { Ok(Self(node.borrow())) } else { Err(PyTypeError::new_err(format!( "{} is not an RDF named node", value.get_type().name()?, ))) } } } pub enum PyNamedOrBlankNodeRef<'a> { NamedNode(PyRef<'a, PyNamedNode>), BlankNode(PyRef<'a, PyBlankNode>), } impl<'a> From<&'a PyNamedOrBlankNodeRef<'a>> for NamedOrBlankNodeRef<'a> { fn from(value: &'a PyNamedOrBlankNodeRef<'a>) -> Self { match value { PyNamedOrBlankNodeRef::NamedNode(value) => value.inner.as_ref().into(), PyNamedOrBlankNodeRef::BlankNode(value) => value.inner.as_ref().into(), } } } impl<'a> TryFrom<&'a PyAny> for PyNamedOrBlankNodeRef<'a> { type Error = PyErr; fn try_from(value: &'a PyAny) -> PyResult { if let Ok(node) = value.downcast::>() { Ok(Self::NamedNode(node.borrow())) } else if let Ok(node) = value.downcast::>() { Ok(Self::BlankNode(node.borrow())) } else { Err(PyTypeError::new_err(format!( "{} is not an RDF named or blank node", value.get_type().name()?, ))) } } } pub enum PySubjectRef<'a> { NamedNode(PyRef<'a, PyNamedNode>), BlankNode(PyRef<'a, PyBlankNode>), Triple(PyRef<'a, PyTriple>), } impl<'a> From<&'a PySubjectRef<'a>> for SubjectRef<'a> { fn from(value: &'a PySubjectRef<'a>) -> Self { match value { PySubjectRef::NamedNode(value) => value.inner.as_ref().into(), PySubjectRef::BlankNode(value) => value.inner.as_ref().into(), PySubjectRef::Triple(value) => (&value.inner).into(), } } } impl<'a> TryFrom<&'a PyAny> for PySubjectRef<'a> { type Error = PyErr; fn try_from(value: &'a PyAny) -> PyResult { if let Ok(node) = value.downcast::>() { Ok(Self::NamedNode(node.borrow())) } else if let Ok(node) = value.downcast::>() { Ok(Self::BlankNode(node.borrow())) } else if let Ok(node) = value.downcast::>() { Ok(Self::Triple(node.borrow())) } else { Err(PyTypeError::new_err(format!( "{} is not an RDF named or blank node", value.get_type().name()?, ))) } } } pub enum PyTermRef<'a> { NamedNode(PyRef<'a, PyNamedNode>), BlankNode(PyRef<'a, PyBlankNode>), Literal(PyRef<'a, PyLiteral>), Triple(PyRef<'a, PyTriple>), } impl<'a> From<&'a PyTermRef<'a>> for TermRef<'a> { fn from(value: &'a PyTermRef<'a>) -> Self { match value { PyTermRef::NamedNode(value) => value.inner.as_ref().into(), PyTermRef::BlankNode(value) => value.inner.as_ref().into(), PyTermRef::Literal(value) => value.inner.as_ref().into(), PyTermRef::Triple(value) => (&value.inner).into(), } } } impl<'a> From<&'a PyTermRef<'a>> for Term { fn from(value: &'a PyTermRef<'a>) -> Self { TermRef::from(value).into() } } impl<'a> TryFrom<&'a PyAny> for PyTermRef<'a> { type Error = PyErr; fn try_from(value: &'a PyAny) -> PyResult { if let Ok(node) = value.downcast::>() { Ok(Self::NamedNode(node.borrow())) } else if let Ok(node) = value.downcast::>() { Ok(Self::BlankNode(node.borrow())) } else if let Ok(node) = value.downcast::>() { Ok(Self::Literal(node.borrow())) } else if let Ok(node) = value.downcast::>() { Ok(Self::Triple(node.borrow())) } else { Err(PyTypeError::new_err(format!( "{} is not an RDF term", value.get_type().name()?, ))) } } } pub enum PyGraphNameRef<'a> { NamedNode(PyRef<'a, PyNamedNode>), BlankNode(PyRef<'a, PyBlankNode>), DefaultGraph, } impl<'a> From<&'a PyGraphNameRef<'a>> for GraphNameRef<'a> { fn from(value: &'a PyGraphNameRef<'a>) -> Self { match value { PyGraphNameRef::NamedNode(value) => value.inner.as_ref().into(), PyGraphNameRef::BlankNode(value) => value.inner.as_ref().into(), PyGraphNameRef::DefaultGraph => Self::DefaultGraph, } } } impl<'a> From<&'a PyGraphNameRef<'a>> for GraphName { fn from(value: &'a PyGraphNameRef<'a>) -> Self { GraphNameRef::from(value).into() } } impl<'a> TryFrom<&'a PyAny> for PyGraphNameRef<'a> { type Error = PyErr; fn try_from(value: &'a PyAny) -> PyResult { if let Ok(node) = value.downcast::>() { Ok(Self::NamedNode(node.borrow())) } else if let Ok(node) = value.downcast::>() { Ok(Self::BlankNode(node.borrow())) } else if value.downcast::>().is_ok() { Ok(Self::DefaultGraph) } else { Err(PyTypeError::new_err(format!( "{} is not an RDF graph name", value.get_type().name()?, ))) } } } fn eq_compare(a: &T, b: &T, op: CompareOp) -> PyResult { match op { CompareOp::Eq => Ok(a == b), CompareOp::Ne => Ok(a != b), _ => Err(PyNotImplementedError::new_err( "Ordering is not implemented", )), } } fn eq_compare_other_type(op: CompareOp) -> PyResult { match op { CompareOp::Eq => Ok(false), CompareOp::Ne => Ok(true), _ => Err(PyNotImplementedError::new_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(""), } } fn triple_repr(triple: TripleRef<'_>, buffer: &mut String) { buffer.push_str(", } #[pymethods] impl TripleComponentsIter { fn __iter__(slf: PyRef<'_, Self>) -> Py { slf.into() } fn __next__(&mut self) -> Option { self.inner.next().map(PyTerm::from) } } #[pyclass(module = "oxigraph")] pub struct QuadComponentsIter { inner: IntoIter>, } #[pymethods] impl QuadComponentsIter { fn __iter__(slf: PyRef<'_, Self>) -> Py { slf.into() } fn __next__(&mut self, py: Python<'_>) -> Option { self.inner.next().map(move |t| { if let Some(t) = t { PyTerm::from(t).into_py(py) } else { PyDefaultGraph {}.into_py(py) } }) } }