use oxigraph::model::*; use pyo3::basic::CompareOp; use pyo3::exceptions::{NotImplementedError, TypeError, ValueError}; use pyo3::prelude::*; use pyo3::types::PyTuple; use pyo3::PyObjectProtocol; use std::collections::hash_map::DefaultHasher; use std::hash::Hash; use std::hash::Hasher; #[pyclass(name = NamedNode)] #[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()) } #[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 { format!("", self.inner.as_str()) } fn __hash__(&self) -> u64 { hash(&self.inner) } fn __richcmp__(&self, other: &PyCell, op: CompareOp) -> bool { eq_ord_compare(self, &other.borrow(), op) } } #[pyclass(name = BlankNode)] #[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()) } #[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 { 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) } } #[pyclass(name = Literal)] #[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, "*", language = "None", datatype = "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()) } #[getter] fn value(&self) -> &str { self.inner.value() } #[getter] fn language(&self) -> Option<&str> { self.inner.language() } #[getter] fn datatype(&self) -> PyNamedNode { self.inner.datatype().clone().into() } } #[pyproto] impl PyObjectProtocol for PyLiteral { fn __str__(&self) -> String { self.inner.to_string() } fn __repr__(&self) -> String { format!( "", self.inner.value(), self.inner.language().unwrap_or(""), self.inner.datatype().as_str() ) } fn __hash__(&self) -> u64 { hash(&self.inner) } fn __richcmp__(&self, other: &PyCell, op: CompareOp) -> PyResult { eq_compare(self, &other.borrow(), op) } } #[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) } } 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 a 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 a 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 a 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), } } pub fn triple_to_python(py: Python<'_>, triple: Triple) -> (PyObject, PyObject, PyObject) { ( named_or_blank_node_to_python(py, triple.subject), PyNamedNode::from(triple.predicate).into_py(py), term_to_python(py, triple.object), ) } pub fn extract_quad(tuple: &PyTuple) -> PyResult { let len = tuple.len(); if len != 3 && len != 4 { return Err(TypeError::py_err( "A quad should be tuple with 3 or 4 elements", )); } Ok(Quad { subject: extract_named_or_blank_node(tuple.get_item(0))?, predicate: extract_named_node(tuple.get_item(1))?, object: extract_term(tuple.get_item(2))?, graph_name: if len == 4 { extract_graph_name(tuple.get_item(3))? } else { GraphName::DefaultGraph }, }) } pub fn quad_to_python(py: Python<'_>, quad: Quad) -> (PyObject, PyObject, PyObject, PyObject) { ( named_or_blank_node_to_python(py, quad.subject), PyNamedNode::from(quad.predicate).into_py(py), term_to_python(py, quad.object), graph_name_to_python(py, quad.graph_name), ) } 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() }