Python: allows giving pathlib.Path for input

pull/587/head
Tpt 1 year ago committed by Thomas Tanon
parent 4cb377bda4
commit 12a738279f
  1. 17
      python/src/io.rs
  2. 23
      python/src/store.rs
  3. 8
      python/tests/test_store.py

@ -13,6 +13,7 @@ use std::cmp::max;
use std::error::Error; use std::error::Error;
use std::fs::File; use std::fs::File;
use std::io::{self, BufWriter, Cursor, Read, Write}; use std::io::{self, BufWriter, Cursor, Read, Write};
use std::path::{Path, PathBuf};
pub fn add_to_module(module: &PyModule) -> PyResult<()> { pub fn add_to_module(module: &PyModule) -> PyResult<()> {
module.add_wrapped(wrap_pyfunction!(parse))?; module.add_wrapped(wrap_pyfunction!(parse))?;
@ -34,7 +35,7 @@ pub fn add_to_module(module: &PyModule) -> PyResult<()> {
/// and ``application/xml`` for `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_. /// and ``application/xml`` for `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_.
/// ///
/// :param input: The binary I/O object or file path to read from. For example, it could be a file path as a string or a file reader opened in binary mode with ``open('my_file.ttl', 'rb')``. /// :param input: The binary I/O object or file path to read from. For example, it could be a file path as a string or a file reader opened in binary mode with ``open('my_file.ttl', 'rb')``.
/// :type input: io(bytes) or io(str) or str /// :type input: io(bytes) or io(str) or str or pathlib.Path
/// :param mime_type: the MIME type of the RDF serialization. /// :param mime_type: the MIME type of the RDF serialization.
/// :type mime_type: str /// :type mime_type: str
/// :param base_iri: the base IRI used to resolve the relative IRIs in the file or :py:const:`None` if relative IRI resolution should not be done. /// :param base_iri: the base IRI used to resolve the relative IRIs in the file or :py:const:`None` if relative IRI resolution should not be done.
@ -55,8 +56,8 @@ pub fn parse(
base_iri: Option<&str>, base_iri: Option<&str>,
py: Python<'_>, py: Python<'_>,
) -> PyResult<PyObject> { ) -> PyResult<PyObject> {
let input = if let Ok(path) = input.extract::<&str>(py) { let input = if let Ok(path) = input.extract::<PathBuf>(py) {
PyReadable::from_file(path, py).map_err(map_io_err)? PyReadable::from_file(&path, py).map_err(map_io_err)?
} else { } else {
PyReadable::from_data(input, py) PyReadable::from_data(input, py)
}; };
@ -106,7 +107,7 @@ pub fn parse(
/// :param input: the RDF triples and quads to serialize. /// :param input: the RDF triples and quads to serialize.
/// :type input: iterable(Triple) or iterable(Quad) /// :type input: iterable(Triple) or iterable(Quad)
/// :param output: The binary I/O object or file path to write to. For example, it could be a file path as a string or a file writer opened in binary mode with ``open('my_file.ttl', 'wb')``. /// :param output: The binary I/O object or file path to write to. For example, it could be a file path as a string or a file writer opened in binary mode with ``open('my_file.ttl', 'wb')``.
/// :type output: io(bytes) or str /// :type output: io(bytes) or str or pathlib.Path
/// :param mime_type: the MIME type of the RDF serialization. /// :param mime_type: the MIME type of the RDF serialization.
/// :type mime_type: str /// :type mime_type: str
/// :rtype: None /// :rtype: None
@ -119,8 +120,8 @@ pub fn parse(
/// b'<http://example.com> <http://example.com/p> "1" .\n' /// b'<http://example.com> <http://example.com/p> "1" .\n'
#[pyfunction] #[pyfunction]
pub fn serialize(input: &PyAny, output: PyObject, mime_type: &str, py: Python<'_>) -> PyResult<()> { pub fn serialize(input: &PyAny, output: PyObject, mime_type: &str, py: Python<'_>) -> PyResult<()> {
let output = if let Ok(path) = output.extract::<&str>(py) { let output = if let Ok(path) = output.extract::<PathBuf>(py) {
PyWritable::from_file(path, py).map_err(map_io_err)? PyWritable::from_file(&path, py).map_err(map_io_err)?
} else { } else {
PyWritable::from_data(output) PyWritable::from_data(output)
}; };
@ -198,7 +199,7 @@ pub enum PyReadable {
} }
impl PyReadable { impl PyReadable {
pub fn from_file(file: &str, py: Python<'_>) -> io::Result<Self> { pub fn from_file(file: &Path, py: Python<'_>) -> io::Result<Self> {
Ok(Self::File(py.allow_threads(|| File::open(file))?)) Ok(Self::File(py.allow_threads(|| File::open(file))?))
} }
@ -229,7 +230,7 @@ pub enum PyWritable {
} }
impl PyWritable { impl PyWritable {
pub fn from_file(file: &str, py: Python<'_>) -> io::Result<Self> { pub fn from_file(file: &Path, py: Python<'_>) -> io::Result<Self> {
Ok(Self::File(BufWriter::new( Ok(Self::File(BufWriter::new(
py.allow_threads(|| File::create(file))?, py.allow_threads(|| File::create(file))?,
))) )))

@ -9,6 +9,7 @@ use oxigraph::sparql::Update;
use oxigraph::store::{self, LoaderError, SerializerError, StorageError, Store}; use oxigraph::store::{self, LoaderError, SerializerError, StorageError, Store};
use pyo3::exceptions::{PyIOError, PyRuntimeError, PyValueError}; use pyo3::exceptions::{PyIOError, PyRuntimeError, PyValueError};
use pyo3::prelude::*; use pyo3::prelude::*;
use std::path::PathBuf;
/// RDF store. /// RDF store.
/// ///
@ -26,7 +27,7 @@ use pyo3::prelude::*;
/// :param path: the path of the directory in which the store should read and write its data. If the directory does not exist, it is created. /// :param path: the path of the directory in which the store should read and write its data. If the directory does not exist, it is created.
/// If no directory is provided a temporary one is created and removed when the Python garbage collector removes the store. /// If no directory is provided a temporary one is created and removed when the Python garbage collector removes the store.
/// In this case, the store data are kept in memory and never written on disk. /// In this case, the store data are kept in memory and never written on disk.
/// :type path: str or None, optional /// :type path: str or pathlib.Path or None, optional
/// :raises IOError: if the target directory contains invalid data or could not be accessed. /// :raises IOError: if the target directory contains invalid data or could not be accessed.
/// ///
/// The :py:func:`str` function provides a serialization of the store in NQuads: /// The :py:func:`str` function provides a serialization of the store in NQuads:
@ -45,7 +46,7 @@ pub struct PyStore {
impl PyStore { impl PyStore {
#[new] #[new]
#[pyo3(signature = (path = None))] #[pyo3(signature = (path = None))]
fn new(path: Option<&str>, py: Python<'_>) -> PyResult<Self> { fn new(path: Option<PathBuf>, py: Python<'_>) -> PyResult<Self> {
py.allow_threads(|| { py.allow_threads(|| {
Ok(Self { Ok(Self {
inner: if let Some(path) = path { inner: if let Some(path) = path {
@ -357,7 +358,7 @@ impl PyStore {
/// and ``application/xml`` for `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_. /// and ``application/xml`` for `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_.
/// ///
/// :param input: The binary I/O object or file path to read from. For example, it could be a file path as a string or a file reader opened in binary mode with ``open('my_file.ttl', 'rb')``. /// :param input: The binary I/O object or file path to read from. For example, it could be a file path as a string or a file reader opened in binary mode with ``open('my_file.ttl', 'rb')``.
/// :type input: io(bytes) or io(str) or str /// :type input: io(bytes) or io(str) or str or pathlib.Path
/// :param mime_type: the MIME type of the RDF serialization. /// :param mime_type: the MIME type of the RDF serialization.
/// :type mime_type: str /// :type mime_type: str
/// :param base_iri: the base IRI used to resolve the relative IRIs in the file or :py:const:`None` if relative IRI resolution should not be done. /// :param base_iri: the base IRI used to resolve the relative IRIs in the file or :py:const:`None` if relative IRI resolution should not be done.
@ -387,8 +388,8 @@ impl PyStore {
} else { } else {
None None
}; };
let input = if let Ok(path) = input.extract::<&str>(py) { let input = if let Ok(path) = input.extract::<PathBuf>(py) {
PyReadable::from_file(path, py).map_err(map_io_err)? PyReadable::from_file(&path, py).map_err(map_io_err)?
} else { } else {
PyReadable::from_data(input, py) PyReadable::from_data(input, py)
}; };
@ -439,7 +440,7 @@ impl PyStore {
/// and ``application/xml`` for `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_. /// and ``application/xml`` for `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_.
/// ///
/// :param input: The binary I/O object or file path to read from. For example, it could be a file path as a string or a file reader opened in binary mode with ``open('my_file.ttl', 'rb')``. /// :param input: The binary I/O object or file path to read from. For example, it could be a file path as a string or a file reader opened in binary mode with ``open('my_file.ttl', 'rb')``.
/// :type input: io(bytes) or io(str) or str /// :type input: io(bytes) or io(str) or str or pathlib.Path
/// :param mime_type: the MIME type of the RDF serialization. /// :param mime_type: the MIME type of the RDF serialization.
/// :type mime_type: str /// :type mime_type: str
/// :param base_iri: the base IRI used to resolve the relative IRIs in the file or :py:const:`None` if relative IRI resolution should not be done. /// :param base_iri: the base IRI used to resolve the relative IRIs in the file or :py:const:`None` if relative IRI resolution should not be done.
@ -469,8 +470,8 @@ impl PyStore {
} else { } else {
None None
}; };
let input = if let Ok(path) = input.extract::<&str>(py) { let input = if let Ok(path) = input.extract::<PathBuf>(py) {
PyReadable::from_file(path, py).map_err(map_io_err)? PyReadable::from_file(&path, py).map_err(map_io_err)?
} else { } else {
PyReadable::from_data(input, py) PyReadable::from_data(input, py)
}; };
@ -518,7 +519,7 @@ impl PyStore {
/// and ``application/xml`` for `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_. /// and ``application/xml`` for `RDF/XML <https://www.w3.org/TR/rdf-syntax-grammar/>`_.
/// ///
/// :param output: The binary I/O object or file path to write to. For example, it could be a file path as a string or a file writer opened in binary mode with ``open('my_file.ttl', 'wb')``. /// :param output: The binary I/O object or file path to write to. For example, it could be a file path as a string or a file writer opened in binary mode with ``open('my_file.ttl', 'wb')``.
/// :type output: io(bytes) or str /// :type output: io(bytes) or str or pathlib.Path
/// :param mime_type: the MIME type of the RDF serialization. /// :param mime_type: the MIME type of the RDF serialization.
/// :type mime_type: str /// :type mime_type: str
/// :param from_graph: if a triple based format is requested, the store graph from which dump the triples. By default, the default graph is used. /// :param from_graph: if a triple based format is requested, the store graph from which dump the triples. By default, the default graph is used.
@ -541,8 +542,8 @@ impl PyStore {
from_graph: Option<&PyAny>, from_graph: Option<&PyAny>,
py: Python<'_>, py: Python<'_>,
) -> PyResult<()> { ) -> PyResult<()> {
let output = if let Ok(path) = output.extract::<&str>(py) { let output = if let Ok(path) = output.extract::<PathBuf>(py) {
PyWritable::from_file(path, py).map_err(map_io_err)? PyWritable::from_file(&path, py).map_err(map_io_err)?
} else { } else {
PyWritable::from_data(output) PyWritable::from_data(output)
}; };

@ -266,11 +266,11 @@ class TestStore(unittest.TestCase):
def test_load_file(self) -> None: def test_load_file(self) -> None:
with NamedTemporaryFile(delete=False) as fp: with NamedTemporaryFile(delete=False) as fp:
file_name = fp.name file_name = Path(fp.name)
fp.write(b"<http://foo> <http://bar> <http://baz> <http://graph>.") fp.write(b"<http://foo> <http://bar> <http://baz> <http://graph>.")
store = Store() store = Store()
store.load(file_name, mime_type="application/n-quads") store.load(file_name, mime_type="application/n-quads")
Path(file_name).unlink() file_name.unlink()
self.assertEqual(set(store), {Quad(foo, bar, baz, graph)}) self.assertEqual(set(store), {Quad(foo, bar, baz, graph)})
def test_load_with_io_error(self) -> None: def test_load_with_io_error(self) -> None:
@ -311,12 +311,12 @@ class TestStore(unittest.TestCase):
def test_dump_file(self) -> None: def test_dump_file(self) -> None:
with NamedTemporaryFile(delete=False) as fp: with NamedTemporaryFile(delete=False) as fp:
file_name = fp.name file_name = Path(fp.name)
store = Store() store = Store()
store.add(Quad(foo, bar, baz, graph)) store.add(Quad(foo, bar, baz, graph))
store.dump(file_name, "application/n-quads") store.dump(file_name, "application/n-quads")
self.assertEqual( self.assertEqual(
Path(file_name).read_text(), file_name.read_text(),
"<http://foo> <http://bar> <http://baz> <http://graph> .\n", "<http://foo> <http://bar> <http://baz> <http://graph> .\n",
) )

Loading…
Cancel
Save