pyo3-filelike-0.3.0/.cargo_vcs_info.json0000644000000001360000000000100135100ustar { "git": { "sha1": "1e5a6d8975d782647924b12f5a9843a3f02933d5" }, "path_in_vcs": "" }pyo3-filelike-0.3.0/.github/CODEOWNERS000064400000000000000000000000121046102023000152240ustar 00000000000000* @jelmer pyo3-filelike-0.3.0/.github/FUNDING.yml000064400000000000000000000000171046102023000154530ustar 00000000000000github: jelmer pyo3-filelike-0.3.0/.github/dependabot.yaml000064400000000000000000000010751046102023000166340ustar 00000000000000# Keep GitHub Actions up to date with GitHub's Dependabot... # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem version: 2 updates: - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" rebase-strategy: "disabled" - package-ecosystem: "github-actions" directory: "/" schedule: interval: weekly pyo3-filelike-0.3.0/.github/workflows/publish.yaml000064400000000000000000000010511046102023000202240ustar 00000000000000on: push: tags: - 'v*' # Push events to every tag not containing / workflow_dispatch: name: Publish jobs: publish: name: Publish runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - run: cargo publish --token ${CRATES_TOKEN} env: CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} pyo3-filelike-0.3.0/.github/workflows/rust.yml000064400000000000000000000004141046102023000174140ustar 00000000000000name: Rust on: push: pull_request: env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose pyo3-filelike-0.3.0/.gitignore000064400000000000000000000000131046102023000142620ustar 00000000000000/target *~ pyo3-filelike-0.3.0/Cargo.toml0000644000000016630000000000100115140ustar # 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.3.0" build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Rust access to Python file-like objects" homepage = "https://github.com/jelmer/pyo3-filelike" readme = "README.md" license = "Apache-2.0" [lib] name = "pyo3_filelike" path = "src/lib.rs" [dependencies.pyo3] version = ">=0.20.0" [dev-dependencies.pyo3] version = ">=0.20.0" features = ["auto-initialize"] pyo3-filelike-0.3.0/Cargo.toml.orig000064400000000000000000000004761046102023000151760ustar 00000000000000[package] name = "pyo3-filelike" version = "0.3.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.20.0" [dev-dependencies] pyo3 = { version = ">=0.20.0", features = ["auto-initialize"] } pyo3-filelike-0.3.0/README.md000064400000000000000000000005001046102023000135520ustar 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.3.0/src/lib.rs000064400000000000000000000127361046102023000142140ustar 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_bound(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> for PyBinaryFile { fn from<'a>(obj: Bound<'a, PyAny>) -> Self { PyBinaryFile(obj.unbind()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_read() { Python::with_gil(|py| -> PyResult<()> { let io = py.import_bound("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_bound("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_bound("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_bound("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_bound("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_bound("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_bound("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(); } }