Skip to main content

pyo3/conversions/
bytes.rs

1#![cfg(feature = "bytes")]
2
3//! Conversions to and from [bytes](https://docs.rs/bytes/latest/bytes/)'s [`Bytes`].
4//!
5//! This is useful for efficiently converting Python's `bytes` types efficiently.
6//! While `bytes` will be directly borrowed, converting from `bytearray` will result in a copy.
7//!
8//! When converting `Bytes` back into Python, this will do a copy, just like `&[u8]` and `Vec<u8>`.
9//!
10//! # When to use `Bytes`
11//!
12//! Unless you specifically need [`Bytes`] for ref-counted ownership and sharing,
13//! you may find that using `&[u8]`, `Vec<u8>`, [`Bound<PyBytes>`], or [`PyBackedBytes`]
14//! is simpler for most use cases.
15//!
16//! # Setup
17//!
18//! To use this feature, add in your **`Cargo.toml`**:
19//!
20//! ```toml
21//! [dependencies]
22//! bytes = "1.10"
23#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"),  "\", features = [\"bytes\"] }")]
24//! ```
25//!
26//! Note that you must use compatible versions of bytes and PyO3.
27//!
28//! # Example
29//!
30//! Rust code to create functions which return `Bytes` or take `Bytes` as arguments:
31//!
32//! ```rust,no_run
33//! use pyo3::prelude::*;
34//! use bytes::Bytes;
35//!
36//! #[pyfunction]
37//! fn get_message_bytes() -> Bytes {
38//!     Bytes::from_static(b"Hello Python!")
39//! }
40//!
41//! #[pyfunction]
42//! fn num_bytes(bytes: Bytes) -> usize {
43//!     bytes.len()
44//! }
45//!
46//! #[pymodule]
47//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
48//!     m.add_function(wrap_pyfunction!(get_message_bytes, m)?)?;
49//!     m.add_function(wrap_pyfunction!(num_bytes, m)?)?;
50//!     Ok(())
51//! }
52//! ```
53//!
54//! Python code that calls these functions:
55//!
56//! ```python
57//! from my_module import get_message_bytes, num_bytes
58//!
59//! message = get_message_bytes()
60//! assert message == b"Hello Python!"
61//!
62//! size = num_bytes(message)
63//! assert size == 13
64//! ```
65use bytes::Bytes;
66
67use crate::conversion::IntoPyObject;
68#[cfg(feature = "experimental-inspect")]
69use crate::inspect::PyStaticExpr;
70use crate::instance::Bound;
71#[allow(unused_imports, reason = "used to build docs")]
72use crate::platform::prelude::*;
73use crate::pybacked::PyBackedBytes;
74use crate::types::PyBytes;
75#[cfg(feature = "experimental-inspect")]
76use crate::PyTypeInfo;
77use crate::{Borrowed, CastError, FromPyObject, PyAny, PyErr, Python};
78
79impl<'a, 'py> FromPyObject<'a, 'py> for Bytes {
80    type Error = CastError<'a, 'py>;
81
82    #[cfg(feature = "experimental-inspect")]
83    const INPUT_TYPE: PyStaticExpr = PyBackedBytes::INPUT_TYPE;
84
85    fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
86        Ok(Bytes::from_owner(obj.extract::<PyBackedBytes>()?))
87    }
88}
89
90impl<'py> IntoPyObject<'py> for Bytes {
91    type Target = PyBytes;
92    type Output = Bound<'py, Self::Target>;
93    type Error = PyErr;
94
95    #[cfg(feature = "experimental-inspect")]
96    const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
97
98    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
99        Ok(PyBytes::new(py, &self))
100    }
101}
102
103impl<'py> IntoPyObject<'py> for &Bytes {
104    type Target = PyBytes;
105    type Output = Bound<'py, Self::Target>;
106    type Error = PyErr;
107
108    #[cfg(feature = "experimental-inspect")]
109    const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
110
111    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
112        Ok(PyBytes::new(py, self))
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use crate::types::{PyAnyMethods, PyByteArray, PyByteArrayMethods, PyBytes};
120    use crate::Python;
121
122    #[test]
123    fn test_bytes() {
124        Python::attach(|py| {
125            let py_bytes = PyBytes::new(py, b"foobar");
126            let bytes: Bytes = py_bytes.extract().unwrap();
127            assert_eq!(&*bytes, b"foobar");
128
129            let bytes = Bytes::from_static(b"foobar").into_pyobject(py).unwrap();
130            assert!(bytes.is_instance_of::<PyBytes>());
131        });
132    }
133
134    #[test]
135    fn test_bytearray() {
136        Python::attach(|py| {
137            let py_bytearray = PyByteArray::new(py, b"foobar");
138            let bytes: Bytes = py_bytearray.extract().unwrap();
139            assert_eq!(&*bytes, b"foobar");
140
141            // Editing the bytearray should not change extracted Bytes
142            // SAFETY: the slice is dropped within this statement without running any Python code,
143            // and `bytes` holds a copy of the data, so the buffer is not aliased
144            unsafe { py_bytearray.as_bytes_mut()[0] = b'x' };
145            assert_eq!(&bytes, "foobar");
146            assert_eq!(&py_bytearray.extract::<Vec<u8>>().unwrap(), b"xoobar");
147        });
148    }
149}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here