lightmotif_py/
pyfile.rs

1use std::io::Error as IoError;
2use std::io::Read;
3use std::sync::Mutex;
4
5use pyo3::exceptions::PyOSError;
6use pyo3::exceptions::PyTypeError;
7use pyo3::prelude::*;
8use pyo3::types::PyBytes;
9
10// ---------------------------------------------------------------------------
11
12#[macro_export]
13macro_rules! transmute_file_error {
14    ($self:ident, $e:ident, $msg:expr, $py:expr) => {{
15        // Attempt to transmute the Python OSError to an actual
16        // Rust `std::io::Error` using `from_raw_os_error`.
17        if $e.is_instance_of::<PyOSError>($py) {
18            if let Ok(code) = &$e.value($py).getattr("errno") {
19                if let Ok(n) = code.extract::<i32>() {
20                    return Err(IoError::from_raw_os_error(n));
21                }
22            }
23        }
24
25        // if the conversion is not possible for any reason we fail
26        // silently, wrapping the Python error, and returning a
27        // generic Rust error instead.
28        $e.restore($py);
29        Err(IoError::new(std::io::ErrorKind::Other, $msg))
30    }};
31}
32
33// ---------------------------------------------------------------------------
34
35/// A wrapper for a Python file that can outlive the GIL.
36pub struct PyFileRead {
37    file: Mutex<PyObject>,
38}
39
40impl PyFileRead {
41    pub fn from_ref(file: &Bound<PyAny>) -> PyResult<PyFileRead> {
42        let res = file.call_method1("read", (0,))?;
43        if res.downcast::<PyBytes>().is_ok() {
44            Ok(PyFileRead {
45                file: Mutex::new(file.clone().unbind()),
46            })
47        } else {
48            let ty = res.get_type().name()?.to_string();
49            Err(PyTypeError::new_err(format!(
50                "expected bytes, found {}",
51                ty
52            )))
53        }
54    }
55}
56
57impl Read for PyFileRead {
58    fn read(&mut self, buf: &mut [u8]) -> Result<usize, IoError> {
59        Python::with_gil(|py| {
60            let file = self.file.lock().expect("failed to lock file");
61            match file.call_method1(py, pyo3::intern!(py, "read"), (buf.len(),)) {
62                Ok(obj) => {
63                    // Check `fh.read` returned bytes, else raise a `TypeError`.
64                    if let Ok(bytes) = obj.downcast_bound::<PyBytes>(py) {
65                        let b = bytes.as_bytes();
66                        (&mut buf[..b.len()]).copy_from_slice(b);
67                        Ok(b.len())
68                    } else {
69                        let ty = obj.bind(py).get_type().name()?.to_string();
70                        let msg = format!("expected bytes, found {}", ty);
71                        PyTypeError::new_err(msg).restore(py);
72                        Err(IoError::new(
73                            std::io::ErrorKind::Other,
74                            "fh.read did not return bytes",
75                        ))
76                    }
77                }
78                Err(e) => transmute_file_error!(self, e, "read method failed", py),
79            }
80        })
81    }
82}