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.
1159 lines
34 KiB
1159 lines
34 KiB
use oxigraph::model::*;
|
|
use oxigraph::sparql::Variable;
|
|
use pyo3::basic::CompareOp;
|
|
use pyo3::exceptions::{PyIndexError, PyNotImplementedError, PyTypeError, PyValueError};
|
|
use pyo3::prelude::*;
|
|
use pyo3::{PyIterProtocol, PyMappingProtocol, PyObjectProtocol, PyTypeInfo};
|
|
use std::collections::hash_map::DefaultHasher;
|
|
use std::convert::TryFrom;
|
|
use std::hash::Hash;
|
|
use std::hash::Hasher;
|
|
use std::vec::IntoIter;
|
|
|
|
/// An RDF `node identified by an IRI <https://www.w3.org/TR/rdf11-concepts/#dfn-iri>`_
|
|
///
|
|
/// :param value: the IRI as a string
|
|
/// :type value: str
|
|
/// :raises ValueError: if the IRI is not valid according to `RFC 3987 <https://tools.ietf.org/rfc/rfc3987>`_
|
|
///
|
|
/// The :py:func:`str` function provides a serialization compatible with NTriples, Turtle and SPARQL:
|
|
///
|
|
/// >>> str(NamedNode('http://example.com'))
|
|
/// '<http://example.com>'
|
|
#[pyclass(name = "NamedNode", module = "oxigraph")]
|
|
#[text_signature = "(value)"]
|
|
#[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| 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()
|
|
}
|
|
}
|
|
|
|
#[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: &PyAny, op: CompareOp) -> PyResult<bool> {
|
|
if let Ok(other) = other.downcast::<PyCell<PyNamedNode>>() {
|
|
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 <https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node>`_
|
|
///
|
|
/// :param value: the `blank node ID <https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node-identifier>`_ (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")]
|
|
#[text_signature = "(value)"]
|
|
#[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| PyValueError::new_err(e.to_string()))?
|
|
} else {
|
|
BlankNode::default()
|
|
}
|
|
.into())
|
|
}
|
|
|
|
/// :return: the `blank node ID <https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node-identifier>`_
|
|
/// :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: &PyAny, op: CompareOp) -> PyResult<bool> {
|
|
if let Ok(other) = other.downcast::<PyCell<PyBlankNode>>() {
|
|
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 <https://www.w3.org/TR/rdf11-concepts/#dfn-literal>`_
|
|
///
|
|
/// :param value: the literal value or `lexical form <https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form>`_
|
|
/// :type value: str
|
|
/// :param datatype: the literal `datatype IRI <https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri>`_.
|
|
/// :type datatype: NamedNode, optional
|
|
/// :param language: the literal `language tag <https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag>`_
|
|
/// :type language: str, optional
|
|
/// :raises ValueError: if the language tag is not valid according to `RFC 5646 <https://tools.ietf.org/rfc/rfc5646>`_ (`BCP 47 <https://tools.ietf.org/rfc/bcp/bcp47>`_)
|
|
///
|
|
/// 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"^^<http://www.w3.org/2001/XMLSchema#integer>'
|
|
#[pyclass(name = "Literal", module = "oxigraph")]
|
|
#[text_signature = "(value, *, datatype = None, language = None)"]
|
|
#[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, "*", datatype = "None", language = "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(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 <https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form>`_
|
|
/// :rtype: str
|
|
///
|
|
/// >>> Literal("example").value
|
|
/// 'example'
|
|
#[getter]
|
|
fn value(&self) -> &str {
|
|
self.inner.value()
|
|
}
|
|
|
|
/// :return: the literal `language tag <https://www.w3.org/TR/rdf11-concepts/#dfn-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 <https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri>`_
|
|
/// :rtype: NamedNode
|
|
///
|
|
/// >>> Literal('11', datatype=NamedNode('http://www.w3.org/2001/XMLSchema#integer')).datatype
|
|
/// <NamedNode value=http://www.w3.org/2001/XMLSchema#integer>
|
|
/// >>> Literal('example').datatype
|
|
/// <NamedNode value=http://www.w3.org/2001/XMLSchema#string>
|
|
/// >>> Literal('example', language='en').datatype
|
|
/// <NamedNode value=http://www.w3.org/1999/02/22-rdf-syntax-ns#langString>
|
|
#[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: &PyAny, op: CompareOp) -> PyResult<bool> {
|
|
if let Ok(other) = other.downcast::<PyCell<PyLiteral>>() {
|
|
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 <https://www.w3.org/TR/rdf11-concepts/#dfn-default-graph>`_
|
|
#[pyclass(name = "DefaultGraph", module = "oxigraph")]
|
|
#[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: &PyAny, op: CompareOp) -> PyResult<bool> {
|
|
if let Ok(other) = other.downcast::<PyCell<PyDefaultGraph>>() {
|
|
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<PyNamedOrBlankNode> for NamedOrBlankNode {
|
|
fn from(node: PyNamedOrBlankNode) -> Self {
|
|
match node {
|
|
PyNamedOrBlankNode::NamedNode(node) => node.into(),
|
|
PyNamedOrBlankNode::BlankNode(node) => node.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<NamedOrBlankNode> for PyNamedOrBlankNode {
|
|
fn from(node: NamedOrBlankNode) -> Self {
|
|
match node {
|
|
NamedOrBlankNode::NamedNode(node) => PyNamedOrBlankNode::NamedNode(node.into()),
|
|
NamedOrBlankNode::BlankNode(node) => PyNamedOrBlankNode::BlankNode(node.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IntoPy<PyObject> for PyNamedOrBlankNode {
|
|
fn into_py(self, py: Python<'_>) -> PyObject {
|
|
match self {
|
|
PyNamedOrBlankNode::NamedNode(node) => node.into_py(py),
|
|
PyNamedOrBlankNode::BlankNode(node) => node.into_py(py),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(FromPyObject)]
|
|
pub enum PyTerm {
|
|
NamedNode(PyNamedNode),
|
|
BlankNode(PyBlankNode),
|
|
Literal(PyLiteral),
|
|
}
|
|
|
|
impl From<PyTerm> for Term {
|
|
fn from(term: PyTerm) -> Self {
|
|
match term {
|
|
PyTerm::NamedNode(node) => node.into(),
|
|
PyTerm::BlankNode(node) => node.into(),
|
|
PyTerm::Literal(literal) => literal.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Term> for PyTerm {
|
|
fn from(term: Term) -> Self {
|
|
match term {
|
|
Term::NamedNode(node) => PyTerm::NamedNode(node.into()),
|
|
Term::BlankNode(node) => PyTerm::BlankNode(node.into()),
|
|
Term::Literal(literal) => PyTerm::Literal(literal.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IntoPy<PyObject> for PyTerm {
|
|
fn into_py(self, py: Python<'_>) -> PyObject {
|
|
match self {
|
|
PyTerm::NamedNode(node) => node.into_py(py),
|
|
PyTerm::BlankNode(node) => node.into_py(py),
|
|
PyTerm::Literal(literal) => literal.into_py(py),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An RDF `triple <https://www.w3.org/TR/rdf11-concepts/#dfn-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')))
|
|
/// '<http://example.com> <http://example.com/p> "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)]
|
|
#[text_signature = "(subject, predicate, object)"]
|
|
pub struct PyTriple {
|
|
inner: Triple,
|
|
}
|
|
|
|
impl From<Triple> for PyTriple {
|
|
fn from(inner: Triple) -> Self {
|
|
Self { inner }
|
|
}
|
|
}
|
|
|
|
impl From<PyTriple> 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: PyNamedOrBlankNode, 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
|
|
/// <NamedNode value=http://example.com>
|
|
#[getter]
|
|
fn subject(&self) -> PyNamedOrBlankNode {
|
|
self.inner.subject.clone().into()
|
|
}
|
|
|
|
/// :return: the triple predicate
|
|
/// :rtype: NamedNode
|
|
///
|
|
/// >>> Triple(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1')).predicate
|
|
/// <NamedNode value=http://example.com/p>
|
|
#[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
|
|
/// <Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>>
|
|
#[getter]
|
|
fn object(&self) -> PyTerm {
|
|
self.inner.object.clone().into()
|
|
}
|
|
}
|
|
|
|
#[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("<Triple subject=");
|
|
term_repr(self.inner.subject.as_ref().into(), &mut buffer);
|
|
buffer.push_str(" predicate=");
|
|
named_node_repr(self.inner.predicate.as_ref(), &mut buffer);
|
|
buffer.push_str(" object=");
|
|
term_repr(self.inner.object.as_ref(), &mut buffer);
|
|
buffer.push('>');
|
|
buffer
|
|
}
|
|
|
|
fn __hash__(&self) -> u64 {
|
|
hash(&self.inner)
|
|
}
|
|
|
|
fn __richcmp__(&self, other: &PyCell<Self>, op: CompareOp) -> PyResult<bool> {
|
|
eq_compare(self, &other.borrow(), op)
|
|
}
|
|
}
|
|
|
|
#[pyproto]
|
|
impl PyMappingProtocol<'p> for PyTriple {
|
|
fn __len__(&self) -> usize {
|
|
3
|
|
}
|
|
|
|
fn __getitem__(&self, input: usize) -> PyResult<PyTerm> {
|
|
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")),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pyproto]
|
|
impl PyIterProtocol for PyTriple {
|
|
fn __iter__(slf: PyRef<Self>) -> TripleComponentsIter {
|
|
TripleComponentsIter {
|
|
inner: vec![
|
|
slf.inner.subject.clone().into(),
|
|
slf.inner.predicate.clone().into(),
|
|
slf.inner.object.clone(),
|
|
]
|
|
.into_iter(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(FromPyObject)]
|
|
pub enum PyGraphName {
|
|
NamedNode(PyNamedNode),
|
|
BlankNode(PyBlankNode),
|
|
DefaultGraph(PyDefaultGraph),
|
|
}
|
|
|
|
impl From<PyGraphName> 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<GraphName> for PyGraphName {
|
|
fn from(graph_name: GraphName) -> Self {
|
|
match graph_name {
|
|
GraphName::NamedNode(node) => PyGraphName::NamedNode(node.into()),
|
|
GraphName::BlankNode(node) => PyGraphName::BlankNode(node.into()),
|
|
GraphName::DefaultGraph => PyGraphName::DefaultGraph(PyDefaultGraph::new()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IntoPy<PyObject> for PyGraphName {
|
|
fn into_py(self, py: Python<'_>) -> PyObject {
|
|
match self {
|
|
PyGraphName::NamedNode(node) => node.into_py(py),
|
|
PyGraphName::BlankNode(node) => node.into_py(py),
|
|
PyGraphName::DefaultGraph(node) => node.into_py(py),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An RDF `triple <https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-triple>`_
|
|
/// in a `RDF dataset <https://www.w3.org/TR/rdf11-concepts/#dfn-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')))
|
|
/// '<http://example.com> <http://example.com/p> "1" <http://example.com/g> .'
|
|
///
|
|
/// >>> str(Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), DefaultGraph()))
|
|
/// '<http://example.com> <http://example.com/p> "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")]
|
|
#[text_signature = "(subject, predicate, object, graph_name = None)"]
|
|
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
|
|
pub struct PyQuad {
|
|
inner: Quad,
|
|
}
|
|
|
|
impl From<Quad> for PyQuad {
|
|
fn from(inner: Quad) -> Self {
|
|
Self { inner }
|
|
}
|
|
}
|
|
|
|
impl From<PyQuad> 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: PyNamedOrBlankNode,
|
|
predicate: PyNamedNode,
|
|
object: PyTerm,
|
|
graph_name: Option<PyGraphName>,
|
|
) -> 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
|
|
/// <NamedNode value=http://example.com>
|
|
#[getter]
|
|
fn subject(&self) -> PyNamedOrBlankNode {
|
|
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
|
|
/// <NamedNode value=http://example.com/p>
|
|
#[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
|
|
/// <Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>>
|
|
#[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
|
|
/// <NamedNode value=http://example.com/g>
|
|
#[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
|
|
/// <Triple subject=<NamedNode value=http://example.com> predicate=<NamedNode value=http://example.com/p> object=<Literal value=1 datatype=<NamedNode value=http://www.w3.org/2001/XMLSchema#string>>>
|
|
#[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("<Quad subject=");
|
|
term_repr(self.inner.subject.as_ref().into(), &mut buffer);
|
|
buffer.push_str(" predicate=");
|
|
named_node_repr(self.inner.predicate.as_ref(), &mut buffer);
|
|
buffer.push_str(" object=");
|
|
term_repr(self.inner.object.as_ref(), &mut buffer);
|
|
buffer.push_str(" graph_name=");
|
|
graph_name_repr(self.inner.graph_name.as_ref(), &mut buffer);
|
|
buffer.push('>');
|
|
buffer
|
|
}
|
|
|
|
fn __hash__(&self) -> u64 {
|
|
hash(&self.inner)
|
|
}
|
|
|
|
fn __richcmp__(&self, other: &PyCell<Self>, op: CompareOp) -> PyResult<bool> {
|
|
eq_compare(self, &other.borrow(), op)
|
|
}
|
|
}
|
|
|
|
#[pyproto]
|
|
impl PyMappingProtocol<'p> for PyQuad {
|
|
fn __len__(&self) -> usize {
|
|
4
|
|
}
|
|
|
|
fn __getitem__(&self, input: usize) -> PyResult<PyObject> {
|
|
let gil = Python::acquire_gil();
|
|
let py = gil.python();
|
|
match input {
|
|
0 => Ok(PyNamedOrBlankNode::from(self.inner.subject.clone()).into_py(py)),
|
|
1 => Ok(PyNamedNode::from(self.inner.predicate.clone()).into_py(py)),
|
|
2 => Ok(PyTerm::from(self.inner.object.clone()).into_py(py)),
|
|
3 => Ok(PyGraphName::from(self.inner.graph_name.clone()).into_py(py)),
|
|
_ => Err(PyIndexError::new_err("A quad has only 4 elements")),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pyproto]
|
|
impl PyIterProtocol for PyQuad {
|
|
fn __iter__(slf: PyRef<Self>) -> 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")]
|
|
#[text_signature = "(value)"]
|
|
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
|
|
pub struct PyVariable {
|
|
inner: Variable,
|
|
}
|
|
|
|
impl From<Variable> for PyVariable {
|
|
fn from(inner: Variable) -> Self {
|
|
Self { inner }
|
|
}
|
|
}
|
|
|
|
impl From<PyVariable> 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<Self> {
|
|
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()
|
|
}
|
|
}
|
|
|
|
#[pyproto]
|
|
impl PyObjectProtocol for PyVariable {
|
|
fn __str__(&self) -> String {
|
|
self.inner.to_string()
|
|
}
|
|
|
|
fn __repr__(&self) -> String {
|
|
format!("<Variable 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)
|
|
}
|
|
}
|
|
|
|
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<Self> {
|
|
if let Ok(node) = value.downcast::<PyCell<PyNamedNode>>() {
|
|
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<Self> {
|
|
if let Ok(node) = value.downcast::<PyCell<PyNamedNode>>() {
|
|
Ok(Self::NamedNode(node.borrow()))
|
|
} else if let Ok(node) = value.downcast::<PyCell<PyBlankNode>>() {
|
|
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 PyTermRef<'a> {
|
|
NamedNode(PyRef<'a, PyNamedNode>),
|
|
BlankNode(PyRef<'a, PyBlankNode>),
|
|
Literal(PyRef<'a, PyLiteral>),
|
|
}
|
|
|
|
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(),
|
|
}
|
|
}
|
|
}
|
|
|
|
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<Self> {
|
|
if let Ok(node) = value.downcast::<PyCell<PyNamedNode>>() {
|
|
Ok(Self::NamedNode(node.borrow()))
|
|
} else if let Ok(node) = value.downcast::<PyCell<PyBlankNode>>() {
|
|
Ok(Self::BlankNode(node.borrow()))
|
|
} else if let Ok(node) = value.downcast::<PyCell<PyLiteral>>() {
|
|
Ok(Self::Literal(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<Self> {
|
|
if let Ok(node) = value.downcast::<PyCell<PyNamedNode>>() {
|
|
Ok(Self::NamedNode(node.borrow()))
|
|
} else if let Ok(node) = value.downcast::<PyCell<PyBlankNode>>() {
|
|
Ok(Self::BlankNode(node.borrow()))
|
|
} else if value.downcast::<PyCell<PyDefaultGraph>>().is_ok() {
|
|
Ok(Self::DefaultGraph)
|
|
} else {
|
|
Err(PyTypeError::new_err(format!(
|
|
"{} is not an RDF graph name",
|
|
value.get_type().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(PyNotImplementedError::new_err(
|
|
"Ordering is not implemented",
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn eq_compare_other_type(op: CompareOp) -> PyResult<bool> {
|
|
match op {
|
|
CompareOp::Eq => Ok(false),
|
|
CompareOp::Ne => Ok(true),
|
|
_ => Err(PyNotImplementedError::new_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()
|
|
}
|
|
|
|
fn named_node_repr(node: NamedNodeRef<'_>, buffer: &mut String) {
|
|
buffer.push_str("<NamedNode value=");
|
|
buffer.push_str(node.as_str());
|
|
buffer.push('>');
|
|
}
|
|
|
|
fn blank_node_repr(node: BlankNodeRef<'_>, buffer: &mut String) {
|
|
buffer.push_str("<BlankNode value=");
|
|
buffer.push_str(node.as_str());
|
|
buffer.push('>');
|
|
}
|
|
|
|
fn literal_repr(literal: LiteralRef<'_>, buffer: &mut String) {
|
|
buffer.push_str("<Literal value=");
|
|
buffer.push_str(literal.value());
|
|
if let Some(language) = literal.language() {
|
|
buffer.push_str(" language=");
|
|
buffer.push_str(language);
|
|
} else {
|
|
buffer.push_str(" datatype=");
|
|
named_node_repr(literal.datatype(), buffer);
|
|
}
|
|
buffer.push('>');
|
|
}
|
|
|
|
pub fn term_repr(term: TermRef<'_>, buffer: &mut String) {
|
|
match term {
|
|
TermRef::NamedNode(node) => named_node_repr(node, buffer),
|
|
TermRef::BlankNode(node) => blank_node_repr(node, buffer),
|
|
TermRef::Literal(literal) => literal_repr(literal, buffer),
|
|
}
|
|
}
|
|
|
|
fn graph_name_repr(term: GraphNameRef<'_>, buffer: &mut String) {
|
|
match term {
|
|
GraphNameRef::NamedNode(node) => named_node_repr(node, buffer),
|
|
GraphNameRef::BlankNode(node) => blank_node_repr(node, buffer),
|
|
GraphNameRef::DefaultGraph => buffer.push_str("<DefaultGraph>"),
|
|
}
|
|
}
|
|
|
|
#[pyclass(module = "oxigraph")]
|
|
pub struct TripleComponentsIter {
|
|
inner: IntoIter<Term>,
|
|
}
|
|
|
|
#[pyproto]
|
|
impl PyIterProtocol for TripleComponentsIter {
|
|
fn __iter__(slf: PyRefMut<Self>) -> Py<Self> {
|
|
slf.into()
|
|
}
|
|
|
|
fn __next__(mut slf: PyRefMut<Self>) -> Option<PyTerm> {
|
|
slf.inner.next().map(PyTerm::from)
|
|
}
|
|
}
|
|
|
|
#[pyclass(module = "oxigraph")]
|
|
pub struct QuadComponentsIter {
|
|
inner: IntoIter<Option<Term>>,
|
|
}
|
|
|
|
#[pyproto]
|
|
impl PyIterProtocol for QuadComponentsIter {
|
|
fn __iter__(slf: PyRefMut<Self>) -> Py<Self> {
|
|
slf.into()
|
|
}
|
|
|
|
fn __next__(mut slf: PyRefMut<Self>) -> Option<PyObject> {
|
|
slf.inner.next().map(move |t| {
|
|
if let Some(t) = t {
|
|
PyTerm::from(t).into_py(slf.py())
|
|
} else {
|
|
PyDefaultGraph {}.into_py(slf.py())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|