Oxigraph for Python
===================
[![actions status](https://github.com/oxigraph/oxigraph/workflows/build/badge.svg)](https://github.com/oxigraph/oxigraph/actions)
This package provides a Python API on top of Oxigraph.
Oxigraph is a work in progress graph database written in Rust implementing the [SPARQL](https://www.w3.org/TR/sparql11-overview/) standard.
It offers two stores with [SPARQL 1.1 Query](https://www.w3.org/TR/sparql11-query/) capabilities.
One of the store is in-memory, and the other one is disk based.
The store is also able to load RDF serialized in [Turtle](https://www.w3.org/TR/turtle/), [TriG](https://www.w3.org/TR/trig/), [N-Triples](https://www.w3.org/TR/n-triples/), [N-Quads](https://www.w3.org/TR/n-quads/) and [RDF XML](https://www.w3.org/TR/rdf-syntax-grammar/).
## Install
To install the development version of Oxigraph you need first to install the build tool [Maturin](https://github.com/PyO3/maturin).
This could be done using the usual `pip install maturin`.
`maturin build release` allows build a release Oxigraph Python wheel.
This wheel could be installed using `pip install PATH` in the current Python environment where `PATH` is the path to the built Oxigraph wheel.
## Example
Insert the triple ` "example"` and print the name of `` in SPARQL:
```python
from oxigraph import *
store = MemoryStore()
ex = NamedNode('http://example/')
schemaName = NamedNode('http://schema.org/name')
store.add((ex, schemaName, Literal('example')))
for binding in store.query('SELECT ?name WHERE { ?name }'):
print(binding['name'].value)
```
## API
### Model
Oxigraph provides python classes for the basic RDF model elements.
#### `NamedNode`
An RDF [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri).
```python
from oxigraph import NamedNode
assert NamedNode('http://example.com/foo').value == 'http://example.com/foo'
assert str(NamedNode('http://example.com/foo')) == ''
```
#### `BlankNode`
An RDF [blank node](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node).
```python
from oxigraph import BlankNode
assert BlankNode('foo').value == 'foo'
assert str(BlankNode('foo')) == '_:foo'
```
#### `Literal`
An RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal).
```python
from oxigraph import NamedNode, Literal
assert Literal('foo').value == 'foo'
assert str(Literal('foo')) == '"foo"'
assert Literal('foo', language='en').language == 'en'
assert str(Literal('foo', language='en')) == '"foo"@en'
assert Literal('11', datatype=NamedNode('http://www.w3.org/2001/XMLSchema#integer')).datatype == NamedNode('http://www.w3.org/2001/XMLSchema#integer')
assert str(Literal('11', datatype=NamedNode('http://www.w3.org/2001/XMLSchema#integer'))) == '"11"^^'
```
#### `DefaultGraph`
The RDF [default graph name](https://www.w3.org/TR/rdf11-concepts/#dfn-default-graph).
```python
from oxigraph import DefaultGraph
DefaultGraph()
```
### Stores
Oxigraph provides two stores:
* `MemoryStore` that stores the RDF quads in memory
* `SledStore` that stores the graph on disk using [Sled](https://github.com/spacejam/sled).
Both stores provide a similar API. They encode an [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset).
#### Constructor
##### `MemoryStore`
It could be constructed using:
```python
from oxigraph import MemoryStore
store = MemoryStore()
```
##### `SledStore`
The following code creates a store using the directory `foo/bar` for storage.
```python
from oxigraph import SledStore
store = SledStore('foo/bar')
```
It is also possible to use a temporary directory that will be removed when the `SledStore` Python object is dropped:
```python
from oxigraph import SledStore
store = SledStore()
```
#### `add`
To add a quad in the store:
```python
s = NamedNode('http://example.com/subject')
p = NamedNode('http://example.com/predicate')
o = NamedNode('http://example.com/object')
g = NamedNode('http://example.com/graph')
store.add((s, p, o, g))
```
If a triple is provided, it is added to the default graph i.e. `store.add((s, p, o, g))` is the same as `store.add((s, p, o, DefaultGraph()))`
#### `remove`
To remove a quad from the store:
```python
store.remove((s, p, o, g))
```
#### `__contains__`
Checks if a quad is in the store:
```python
assert (s, p, o, g) in store
```
#### `__len__`
Returns the number of quads in the store:
```python
assert len(store) == 1
```
#### `__iter__`
Iterates on all quads in the store:
```python
assert list(iter(store)) == [(s, p, o, g)]
```
#### `match`
Returns all the quads matching a given pattern using an iterator.
Return all the quads with the subject `s`:
```python
assert list(store.match(s, None, None, None)) == [(s, p, o, g)]
```
Return all the quads in the default graph:
```python
assert list(store.match(s, None, None, DefaultGraph())) == []
```
#### `query`
Executes a [SPARQL 1.1 Query](https://www.w3.org/TR/sparql11-query/).
The `ASK` queries return a boolean:
```python
assert store.query('ASK { ?s ?s ?s }')
```
The `SELECT` queries return an iterator of query solutions that could be indexed by variable name or position in the `SELECT` clause:
```python
for solution in store.query('SELECT ?s WHERE { ?s ?p ?o }'):
assert solution[0] == solution['s']
```
The `CONSTRUCT` and `DESCRIBE` queries return an iterator of triples:
```python
for (s, p, o) in store.query('CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }'):
print(s)
```
#### `load`
Loads serialized RDF triples or quad into the store.
The method arguments are:
1. `data`: the serialized RDF triples or quads.
2. `mime_type`: the MIME type of the serialization. See below for the supported mime types.
3. `base_iri`: the base IRI used to resolve the relative IRIs in the serialization.
4. `to_named_graph`: for triple serialization formats, the name of the named graph the triple should be loaded to.
The available formats are:
* [Turtle](https://www.w3.org/TR/turtle/): `text/turtle`
* [TriG](https://www.w3.org/TR/trig/): `application/trig`
* [N-Triples](https://www.w3.org/TR/n-triples/): `application/n-triples`
* [N-Quads](https://www.w3.org/TR/n-quads/): `application/n-quads`
* [RDF XML](https://www.w3.org/TR/rdf-syntax-grammar/): `application/rdf+xml`
Example of loading a Turtle file into the named graph `` with the base IRI `http://example.com`:
```python
store.load(' <> .', mime_type='text/turtle', base_iri="http://example.com", to_graph=NamedNode('http://example.com/graph'))
```
## How to contribute
The Oxigraph bindings are written in Rust using [PyO3](https://github.com/PyO3/pyo3).
They are build using [Maturin](https://github.com/PyO3/maturin).
Maturin could be installed using the usual `pip install maturin`.
To install development version of Oxigraph just run `maturin develop`.
The Python bindings tests are written in Python.
To run them use the usual `python -m unittest` in the `tests` directory.