Skip to content

Commit 47851c0

Browse files
Merge pull request #57 from triblespace/codex/add-test-for-pybytes-as-memoryview
test(pyo3): add memoryview roundtrip
2 parents 14b293b + 5e8f92c commit 47851c0

File tree

7 files changed

+45
-12
lines changed

7 files changed

+45
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
in a dedicated AGENTS section
6161
- add tests for weak reference upgrade/downgrade and Kani proofs for view helpers
6262
- add Kani proofs covering `Bytes::try_unwrap_owner` and `WeakBytes` upgrade semantics
63-
- add examples for quick start and PyBytes usage
63+
- add examples for quick start and PyAnyBytes usage
6464
- add example showing how to wrap Python `bytes` into `Bytes`
6565
- summarize built-in `ByteSource`s and show how to extend them
6666
- added tests verifying `WeakView` upgrade and drop semantics
@@ -100,6 +100,9 @@
100100
- implemented `bytes::Buf` for `Bytes` and `From<Bytes>` for `bytes::Bytes` for
101101
seamless integration with Tokio and other libraries
102102
- implemented `ExactSizeIterator` and `FusedIterator` for `BytesIterOffsets`
103+
- added test exposing `PyAnyBytes` as a read-only `memoryview`
104+
- renamed `PyBytes` wrapper to `PyAnyBytes` to avoid confusion
105+
- renamed `py_anybytes` module to `pyanybytes` for consistency
103106

104107
## 0.19.3 - 2025-05-30
105108
- implemented `Error` for `ViewError`

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ name = "from_python"
4343
required-features = ["pyo3"]
4444

4545
[[example]]
46-
name = "pybytes"
46+
name = "pyanybytes"
4747
required-features = ["pyo3"]
4848

4949
[[example]]

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ Other optional features provide additional integrations:
138138
- `ownedbytes` &ndash; adds compatibility with [`ownedbytes`](https://crates.io/crates/ownedbytes) and implements its `StableDeref` trait.
139139
- `mmap` &ndash; enables memory-mapped file handling via the `memmap2` crate.
140140
- `zerocopy` &ndash; exposes the [`view`](src/view.rs) module for typed zero-copy access and allows using `zerocopy` types as sources.
141-
- `pyo3` &ndash; builds the [`pybytes`](src/pybytes.rs) module to provide Python bindings for `Bytes`.
141+
- `pyo3` &ndash; builds the [`pyanybytes`](src/pyanybytes.rs) module to provide Python bindings for `Bytes`.
142142
- `winnow` &ndash; implements the [`Stream`](https://docs.rs/winnow/) traits for `Bytes` and offers parsers (`view`, `view_elems(count)`) that return typed `View`s.
143143

144144
Enabling the `pyo3` feature requires the Python development headers and libraries
@@ -149,7 +149,7 @@ needs these libraries installed; otherwise disable the feature during testing.
149149

150150
- [`examples/quick_start.rs`](examples/quick_start.rs) – the quick start shown above
151151
- [`examples/try_unwrap_owner.rs`](examples/try_unwrap_owner.rs) – reclaim the owner when uniquely referenced
152-
- [`examples/pybytes.rs`](examples/pybytes.rs) – demonstrates the `pyo3` feature using `PyBytes`
152+
- [`examples/pyanybytes.rs`](examples/pyanybytes.rs) – demonstrates the `pyo3` feature using `PyAnyBytes`
153153
- [`examples/from_python.rs`](examples/from_python.rs) – wrap a Python `bytes` object into `Bytes`
154154
- [`examples/python_winnow.rs`](examples/python_winnow.rs) – parse Python bytes with winnow
155155
- [`examples/python_winnow_view.rs`](examples/python_winnow_view.rs) – parse structured data from Python bytes using winnow's `view`
@@ -184,7 +184,7 @@ development iterations.
184184
- [`ByteSource`](src/bytes.rs) &ndash; trait for objects that can provide bytes.
185185
- [`ByteOwner`](src/bytes.rs) &ndash; keeps backing storage alive.
186186
- [`view` module](src/view.rs) &ndash; typed zero-copy access to bytes.
187-
- [`pybytes` module](src/pybytes.rs) &ndash; Python bindings.
187+
- [`pyanybytes` module](src/pyanybytes.rs) &ndash; Python bindings.
188188

189189
## Acknowledgements
190190
This library started as a fork of the minibyte library in facebooks [sapling scm](https://github.com/facebook/sapling).
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use anybytes::{Bytes, PyBytes};
1+
use anybytes::{Bytes, PyAnyBytes};
22
use pyo3::prelude::*;
33

44
fn main() -> PyResult<()> {
55
Python::with_gil(|py| {
66
let bytes = Bytes::from(vec![1u8, 2, 3, 4]);
7-
let wrapped = Py::new(py, PyBytes::new(bytes))?;
7+
let wrapped = Py::new(py, PyAnyBytes::new(bytes))?;
88

99
let builtins = PyModule::import(py, "builtins")?;
1010
let memoryview = builtins.getattr("memoryview")?.call1((wrapped.bind(py),))?;

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub mod view;
2121

2222
#[cfg(feature = "pyo3")]
2323
/// Python bindings for [`Bytes`].
24-
pub mod pybytes;
24+
pub mod pyanybytes;
2525

2626
#[cfg(feature = "winnow")]
2727
/// Integration with the `winnow` parser library.
@@ -37,7 +37,7 @@ pub use crate::bytes::ByteSource;
3737
pub use crate::bytes::Bytes;
3838
pub use crate::bytes::WeakBytes;
3939
#[cfg(feature = "pyo3")]
40-
pub use crate::pybytes::PyBytes;
40+
pub use crate::pyanybytes::PyAnyBytes;
4141
#[cfg(feature = "zerocopy")]
4242
pub use crate::view::View;
4343

src/pybytes.rs renamed to src/pyanybytes.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ use crate::Bytes;
1313

1414
/// Python wrapper around [`Bytes`].
1515
#[pyclass(name = "Bytes")]
16-
pub struct PyBytes {
16+
pub struct PyAnyBytes {
1717
bytes: Bytes,
1818
}
1919

2020
#[pymethods]
21-
impl PyBytes {
21+
impl PyAnyBytes {
2222
/// Exposes the bytes to Python's buffer protocol.
2323
///
2424
/// # Safety
@@ -45,7 +45,7 @@ impl PyBytes {
4545
}
4646
}
4747

48-
impl PyBytes {
48+
impl PyAnyBytes {
4949
/// Wrap a [`Bytes`] instance for Python exposure.
5050
pub fn new(bytes: Bytes) -> Self {
5151
Self { bytes }

src/tests.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,3 +560,33 @@ fn test_area_alignment_padding() {
560560
expected.extend_from_slice(&0x0506u16.to_ne_bytes());
561561
assert_eq!(all.as_ref(), expected.as_slice());
562562
}
563+
564+
#[cfg(feature = "pyo3")]
565+
#[test]
566+
fn test_pyanybytes_memoryview() {
567+
use crate::{Bytes, PyAnyBytes};
568+
use pyo3::types::{PyAnyMethods, PyMemoryView};
569+
use pyo3::{Py, Python};
570+
571+
pyo3::prepare_freethreaded_python();
572+
Python::with_gil(|py| {
573+
let data = b"memoryview";
574+
let bytes = PyAnyBytes::new(Bytes::from(data.to_vec()));
575+
let py_obj = Py::new(py, bytes).expect("PyAnyBytes");
576+
let view = PyMemoryView::from(py_obj.bind(py).as_any()).expect("memoryview");
577+
578+
let mv_bytes: Vec<u8> = view
579+
.call_method0("tobytes")
580+
.expect("tobytes")
581+
.extract()
582+
.expect("extract bytes");
583+
assert_eq!(mv_bytes.as_slice(), data);
584+
585+
let readonly: bool = view
586+
.getattr("readonly")
587+
.expect("readonly attr")
588+
.extract()
589+
.expect("extract bool");
590+
assert!(readonly);
591+
});
592+
}

0 commit comments

Comments
 (0)