pyo3-filelike-0.2.0/.cargo_vcs_info.json0000644000000001360000000000100135070ustar { "git": { "sha1": "afd8b1d59bea36b30943be77db2096bd3e855047" }, "path_in_vcs": "" }pyo3-filelike-0.2.0/.github/workflows/rust.yml000064400000000000000000000004141046102023000174130ustar 00000000000000name: Rust on: push: pull_request: env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose pyo3-filelike-0.2.0/.gitignore000064400000000000000000000000101046102023000142560ustar 00000000000000/target pyo3-filelike-0.2.0/Cargo.toml0000644000000013560000000000100115120ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "pyo3-filelike" version = "0.2.0" description = "Rust access to Python file-like objects" homepage = "https://github.com/jelmer/pyo3-filelike" readme = "README.md" license = "Apache-2.0" [dependencies.pyo3] version = ">=0.19.0" [dev-dependencies] pyo3-filelike-0.2.0/Cargo.toml.orig000064400000000000000000000004501046102023000151650ustar 00000000000000[package] name = "pyo3-filelike" version = "0.2.0" edition = "2021" description = "Rust access to Python file-like objects" license = "Apache-2.0" homepage = "https://github.com/jelmer/pyo3-filelike" [dependencies] pyo3 = ">=0.19.0" [dev-dependencies] pyo3 = { features = ["auto-initialize"] } pyo3-filelike-0.2.0/README.md000064400000000000000000000005001046102023000135510ustar 00000000000000# Rust compatible wrappers for file-like objects in Python This crate provides implementations of the ``Write``, ``Seek``, ``Read`` and ``AsRawFd`` rust traits on top of file-likb objects in PyO3. ## Example ```rust let f = py3o_filelike::PyBinaryFile::from(o); let mut buf = [0u8; 4]; f.read_exact(&mut buf)?; ``` pyo3-filelike-0.2.0/src/lib.rs000064400000000000000000000126241046102023000142070ustar 00000000000000use pyo3::prelude::*; use std::io::{Read, Write, Seek}; #[cfg(any(unix, target_os = "wasi"))] use std::os::fd::{AsFd, BorrowedFd, RawFd}; #[derive(Debug)] pub struct PyBinaryFile(pyo3::PyObject); impl Clone for PyBinaryFile{ fn clone(&self) -> Self { Python::with_gil(|py| { PyBinaryFile::from(self.0.clone_ref(py)) }) } } impl Read for PyBinaryFile { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { Python::with_gil(|py| { let bytes = self.0.call_method1(py, "read", (buf.len(), ))?; let bytes = bytes.extract::<&[u8]>(py)?; let len = std::cmp::min(buf.len(), bytes.len()); buf[..len].copy_from_slice(&bytes[..len]); Ok(len) }) } } impl Write for PyBinaryFile { fn write(&mut self, buf: &[u8]) -> std::io::Result { Python::with_gil(|py| { let bytes = pyo3::types::PyBytes::new(py, buf); self.0.call_method1(py, "write", (bytes, ))?; Ok(buf.len()) }) } fn flush(&mut self) -> std::io::Result<()> { Python::with_gil(|py| { self.0.call_method0(py, "flush")?; Ok(()) }) } } impl Seek for PyBinaryFile { fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { Python::with_gil(|py| { let (whence, offset) = match pos { std::io::SeekFrom::Start(offset) => (0, offset as i64), std::io::SeekFrom::End(offset) => (2, offset), std::io::SeekFrom::Current(offset) => (1, offset), }; let pos = self.0.call_method1(py, "seek", (offset, whence))?; let pos = pos.extract::(py)?; Ok(pos) }) } } #[cfg(any(unix, target_os = "wasi"))] impl AsFd for PyBinaryFile { fn as_fd(&self) -> BorrowedFd<'_> { Python::with_gil(|py| { let fd = self.0.call_method0(py, "fileno")?; let fd = fd.extract::(py)?; Ok::, PyErr>(unsafe { BorrowedFd::borrow_raw(fd) }) }).unwrap() } } impl From for PyBinaryFile { fn from(obj: pyo3::PyObject) -> Self { PyBinaryFile(obj) } } impl From<&PyAny> for PyBinaryFile { fn from(obj: &PyAny) -> Self { PyBinaryFile(obj.into()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_read() { Python::with_gil(|py| -> PyResult<()> { let io = py.import("io")?; let file = io.call_method1("BytesIO", (&b"hello"[..], ))?; let mut file = PyBinaryFile::from(file); let mut buf = [0u8; 5]; file.read_exact(&mut buf)?; assert_eq!(&buf, b"hello"); Ok(()) }).unwrap(); } #[test] fn test_read_notexact() { Python::with_gil(|py| -> PyResult<()> { let io = py.import("io")?; let file = io.call_method1("BytesIO", (&b"hello"[..], ))?; let mut file = PyBinaryFile::from(file); let mut buf = [0u8; 10]; let n = file.read(&mut buf)?; assert_eq!(n, 5); assert_eq!(&buf[..n], b"hello"); Ok(()) }).unwrap(); } #[test] fn test_read_eof() { Python::with_gil(|py| -> PyResult<()> { let io = py.import("io")?; let file = io.call_method1("BytesIO", (&b"hello"[..], ))?; let mut file = PyBinaryFile::from(file); let mut buf = [0u8; 6]; let err = file.read_exact(&mut buf).unwrap_err(); assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof); Ok(()) }).unwrap(); } #[test] fn test_read_to_end() { Python::with_gil(|py| -> PyResult<()> { let io = py.import("io")?; let file = io.call_method1("BytesIO", (&b"hello"[..], ))?; let mut file = PyBinaryFile::from(file); let mut buf = Vec::new(); file.read_to_end(&mut buf)?; assert_eq!(&buf, b"hello"); Ok(()) }).unwrap(); } #[test] fn test_write() { Python::with_gil(|py| { let io = py.import("io")?; let file = io.call_method1("BytesIO", (&b""[..], ))?; let mut file = PyBinaryFile::from(file); file.write_all(b"hello ")?; file.write_all(b"world")?; assert_eq!(file.0.call_method0(py, "getvalue")?.extract::<&[u8]>(py)?, b"hello world"); Ok::<(), PyErr>(()) }).unwrap(); } #[test] fn test_seek() { Python::with_gil(|py| { let io = py.import("io")?; let file = io.call_method1("BytesIO", (&b"hello"[..], ))?; let mut file = PyBinaryFile::from(file); file.seek(std::io::SeekFrom::Start(1))?; let mut buf = [0u8; 4]; file.read_exact(&mut buf)?; assert_eq!(&buf, b"ello"); Ok::<(), PyErr>(()) }).unwrap(); } #[test] fn test_flush() { Python::with_gil(|py| { let io = py.import("io")?; let file = io.call_method1("BytesIO", (&b""[..], ))?; let mut file = PyBinaryFile::from(file); file.write_all(b"hello")?; file.flush()?; assert_eq!(file.0.call_method0(py, "getvalue")?.extract::<&[u8]>(py)?, b"hello"); Ok::<(), PyErr>(()) }).unwrap(); } }