Fork of https://github.com/oxigraph/oxigraph.git for the purpose of NextGraph project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
oxigraph/python/src/model.rs

423 lines
10 KiB

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<NamedNode> for PyNamedNode {
fn from(inner: NamedNode) -> Self {
Self { inner }
}
}
impl From<PyNamedNode> for NamedNode {
fn from(node: PyNamedNode) -> Self {
node.inner
}
}
impl From<PyNamedNode> for NamedOrBlankNode {
fn from(node: PyNamedNode) -> Self {
node.inner.into()
}
}
impl From<PyNamedNode> for Term {
fn from(node: PyNamedNode) -> Self {
node.inner.into()
}
}
impl From<PyNamedNode> for GraphName {
fn from(node: PyNamedNode) -> Self {
node.inner.into()
}
}
#[pymethods]
impl PyNamedNode {
#[new]
fn new(value: String) -> PyResult<Self> {
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!("<NamedNode value={}>", self.inner.as_str())
}
fn __hash__(&self) -> u64 {
hash(&self.inner)
}
fn __richcmp__(&self, other: &PyCell<Self>, 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<BlankNode> for PyBlankNode {
fn from(inner: BlankNode) -> Self {
Self { inner }
}
}
impl From<PyBlankNode> for BlankNode {
fn from(node: PyBlankNode) -> Self {
node.inner
}
}
impl From<PyBlankNode> for NamedOrBlankNode {
fn from(node: PyBlankNode) -> Self {
node.inner.into()
}
}
impl From<PyBlankNode> for Term {
fn from(node: PyBlankNode) -> Self {
node.inner.into()
}
}
impl From<PyBlankNode> for GraphName {
fn from(node: PyBlankNode) -> Self {
node.inner.into()
}
}
#[pymethods]
impl PyBlankNode {
#[new]
fn new(value: Option<String>) -> PyResult<Self> {
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!("<BlankNode value={}>", self.inner.as_str())
}
fn __hash__(&self) -> u64 {
hash(&self.inner)
}
fn __richcmp__(&self, other: &PyCell<Self>, op: CompareOp) -> PyResult<bool> {
eq_compare(self, &other.borrow(), op)
}
}
#[pyclass(name = Literal)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct PyLiteral {
inner: Literal,
}
impl From<Literal> for PyLiteral {
fn from(inner: Literal) -> Self {
Self { inner }
}
}
impl From<PyLiteral> for Literal {
fn from(literal: PyLiteral) -> Self {
literal.inner
}
}
impl From<PyLiteral> 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<String>,
datatype: Option<PyNamedNode>,
) -> PyResult<Self> {
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().into_owned().into()
}
}
#[pyproto]
impl PyObjectProtocol for PyLiteral {
fn __str__(&self) -> String {
self.inner.to_string()
}
fn __repr__(&self) -> String {
format!(
"<Literal value={} language={} datatype={}>",
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<Self>, op: CompareOp) -> PyResult<bool> {
eq_compare(self, &other.borrow(), op)
}
}
#[pyclass(name = DefaultGraph)]
#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
pub struct PyDefaultGraph {}
impl From<PyDefaultGraph> 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 {
"<DefaultGraph>"
}
fn __hash__(&self) -> u64 {
0
}
fn __richcmp__(&self, other: &PyCell<Self>, op: CompareOp) -> PyResult<bool> {
eq_compare(self, &other.borrow(), op)
}
}
pub fn extract_named_node(py: &PyAny) -> PyResult<NamedNode> {
if let Ok(node) = py.downcast::<PyCell<PyNamedNode>>() {
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<NamedOrBlankNode> {
if let Ok(node) = py.downcast::<PyCell<PyNamedNode>>() {
Ok(node.borrow().clone().into())
} else if let Ok(node) = py.downcast::<PyCell<PyBlankNode>>() {
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<Term> {
if let Ok(node) = py.downcast::<PyCell<PyNamedNode>>() {
Ok(node.borrow().clone().into())
} else if let Ok(node) = py.downcast::<PyCell<PyBlankNode>>() {
Ok(node.borrow().clone().into())
} else if let Ok(literal) = py.downcast::<PyCell<PyLiteral>>() {
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<GraphName> {
if let Ok(node) = py.downcast::<PyCell<PyNamedNode>>() {
Ok(node.borrow().clone().into())
} else if let Ok(node) = py.downcast::<PyCell<PyBlankNode>>() {
Ok(node.borrow().clone().into())
} else if let Ok(node) = py.downcast::<PyCell<PyDefaultGraph>>() {
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<Quad> {
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<T: Eq>(a: &T, b: &T, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Eq => Ok(a == b),
CompareOp::Ne => Ok(a != b),
_ => Err(NotImplementedError::py_err("Ordering is not implemented")),
}
}
fn eq_ord_compare<T: Eq + Ord>(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()
}