1use std::fmt::Write;
4use std::os::raw::c_int;
5use std::ptr::NonNull;
6
7use bytes::{Bytes, BytesMut};
8use pyo3::buffer::PyBuffer;
9use pyo3::exceptions::{PyIndexError, PyValueError};
10use pyo3::prelude::*;
11use pyo3::types::{PyDict, PySlice, PyTuple};
12use pyo3::{ffi, IntoPyObjectExt};
13
14#[pyclass(name = "Bytes", subclass, frozen, sequence, weakref)]
36#[derive(Hash, PartialEq, PartialOrd, Eq, Ord)]
37pub struct PyBytes(Bytes);
38
39impl AsRef<Bytes> for PyBytes {
40 fn as_ref(&self) -> &Bytes {
41 &self.0
42 }
43}
44
45impl AsRef<[u8]> for PyBytes {
46 fn as_ref(&self) -> &[u8] {
47 self.0.as_ref()
48 }
49}
50
51impl PyBytes {
52 pub fn new(buffer: Bytes) -> Self {
54 Self(buffer)
55 }
56
57 pub fn into_inner(self) -> Bytes {
59 self.0
60 }
61
62 pub fn as_slice(&self) -> &[u8] {
64 self.as_ref()
65 }
66
67 fn slice(&self, slice: &Bound<'_, PySlice>) -> PyResult<PyBytes> {
77 let bytes_length = self.0.len() as isize;
78 let (start, stop, step) = {
79 let slice_indices = slice.indices(bytes_length)?;
80 (slice_indices.start, slice_indices.stop, slice_indices.step)
81 };
82
83 let new_capacity = if (step > 0 && stop > start) || (step < 0 && stop < start) {
84 (((stop - start).abs() + step.abs() - 1) / step.abs()) as usize
85 } else {
86 0
87 };
88
89 if new_capacity == 0 {
90 return Ok(PyBytes(Bytes::new()));
91 }
92 if step == 1 {
93 if start < 0 && stop >= bytes_length {
95 let out = self.0.slice(..);
96 let py_bytes = PyBytes(out);
97 return Ok(py_bytes);
98 }
99
100 if start >= 0 && stop <= bytes_length && start < stop {
101 let out = self.0.slice(start as usize..stop as usize);
102 let py_bytes = PyBytes(out);
103 return Ok(py_bytes);
104 }
105 }
107 if step > 0 {
108 let mut new_buf = BytesMut::with_capacity(new_capacity);
110 new_buf.extend(
111 (start..stop)
112 .step_by(step as usize)
113 .map(|i| self.0[i as usize]),
114 );
115 Ok(PyBytes(new_buf.freeze()))
116 } else {
117 let mut new_buf = BytesMut::with_capacity(new_capacity);
119 new_buf.extend(
120 (stop + 1..=start)
121 .rev()
122 .step_by((-step) as usize)
123 .map(|i| self.0[i as usize]),
124 );
125 Ok(PyBytes(new_buf.freeze()))
126 }
127 }
128}
129
130impl From<PyBytes> for Bytes {
131 fn from(value: PyBytes) -> Self {
132 value.0
133 }
134}
135
136impl From<Vec<u8>> for PyBytes {
137 fn from(value: Vec<u8>) -> Self {
138 PyBytes(value.into())
139 }
140}
141
142impl From<Bytes> for PyBytes {
143 fn from(value: Bytes) -> Self {
144 PyBytes(value)
145 }
146}
147
148impl From<BytesMut> for PyBytes {
149 fn from(value: BytesMut) -> Self {
150 PyBytes(value.into())
151 }
152}
153
154#[pymethods]
155impl PyBytes {
156 #[new]
159 #[pyo3(signature = (buf = PyBytes(Bytes::new())), text_signature = "(buf = b'')")]
160 fn py_new(buf: PyBytes) -> Self {
161 buf
162 }
163
164 fn __getnewargs_ex__<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
165 let py_bytes = self.to_bytes(py);
166 let args = PyTuple::new(py, vec![py_bytes])?.into_bound_py_any(py)?;
167 let kwargs = PyDict::new(py).into_bound_py_any(py)?;
168 PyTuple::new(py, [args, kwargs])
169 }
170
171 fn __len__(&self) -> usize {
173 self.0.len()
174 }
175
176 fn __repr__(&self) -> String {
177 format!("{self:?}")
178 }
179
180 fn __add__(&self, other: PyBytes) -> PyBytes {
181 let total_length = self.0.len() + other.0.len();
182 let mut new_buffer = BytesMut::with_capacity(total_length);
183 new_buffer.extend_from_slice(&self.0);
184 new_buffer.extend_from_slice(&other.0);
185 new_buffer.into()
186 }
187
188 fn __contains__(&self, item: PyBytes) -> bool {
189 self.0
190 .windows(item.0.len())
191 .any(|window| window == item.as_slice())
192 }
193
194 fn __eq__(&self, other: PyBytes) -> bool {
195 self.0.as_ref() == other.0.as_ref()
196 }
197
198 fn __getitem__<'py>(
199 &self,
200 py: Python<'py>,
201 key: BytesGetItemKey<'py>,
202 ) -> PyResult<Bound<'py, PyAny>> {
203 match key {
204 BytesGetItemKey::Int(mut index) => {
205 if index < 0 {
206 index += self.0.len() as isize;
207 }
208 if index < 0 {
209 return Err(PyIndexError::new_err("Index out of range"));
210 }
211 self.0
212 .get(index as usize)
213 .ok_or(PyIndexError::new_err("Index out of range"))?
214 .into_bound_py_any(py)
215 }
216 BytesGetItemKey::Slice(slice) => {
217 let s = self.slice(&slice)?;
218 s.into_bound_py_any(py)
219 }
220 }
221 }
222
223 fn __mul__(&self, value: usize) -> PyBytes {
224 let mut out_buf = BytesMut::with_capacity(self.0.len() * value);
225 (0..value).for_each(|_| out_buf.extend_from_slice(self.0.as_ref()));
226 out_buf.into()
227 }
228
229 #[allow(unsafe_code)]
232 unsafe fn __getbuffer__(
233 slf: PyRef<Self>,
234 view: *mut ffi::Py_buffer,
235 flags: c_int,
236 ) -> PyResult<()> {
237 let bytes = slf.0.as_ref();
238 let ret = ffi::PyBuffer_FillInfo(
239 view,
240 slf.as_ptr() as *mut _,
241 bytes.as_ptr() as *mut _,
242 bytes.len().try_into().unwrap(),
243 1, flags,
245 );
246 if ret == -1 {
247 return Err(PyErr::fetch(slf.py()));
248 }
249 Ok(())
250 }
251
252 #[allow(unsafe_code)]
258 unsafe fn __releasebuffer__(&self, _view: *mut ffi::Py_buffer) {}
259
260 #[pyo3(signature = (prefix, /))]
263 fn removeprefix(&self, prefix: PyBytes) -> PyBytes {
264 if self.0.starts_with(prefix.as_ref()) {
265 self.0.slice(prefix.0.len()..).into()
266 } else {
267 self.0.clone().into()
268 }
269 }
270
271 #[pyo3(signature = (suffix, /))]
274 fn removesuffix(&self, suffix: PyBytes) -> PyBytes {
275 if self.0.ends_with(suffix.as_ref()) {
276 self.0.slice(0..self.0.len() - suffix.0.len()).into()
277 } else {
278 self.0.clone().into()
279 }
280 }
281
282 fn isalnum(&self) -> bool {
287 if self.0.is_empty() {
288 return false;
289 }
290
291 for c in self.0.as_ref() {
292 if !c.is_ascii_alphanumeric() {
293 return false;
294 }
295 }
296 true
297 }
298
299 fn isalpha(&self) -> bool {
303 if self.0.is_empty() {
304 return false;
305 }
306
307 for c in self.0.as_ref() {
308 if !c.is_ascii_alphabetic() {
309 return false;
310 }
311 }
312 true
313 }
314
315 fn isascii(&self) -> bool {
318 for c in self.0.as_ref() {
319 if !c.is_ascii() {
320 return false;
321 }
322 }
323 true
324 }
325
326 fn isdigit(&self) -> bool {
330 if self.0.is_empty() {
331 return false;
332 }
333
334 for c in self.0.as_ref() {
335 if !c.is_ascii_digit() {
336 return false;
337 }
338 }
339 true
340 }
341
342 fn islower(&self) -> bool {
345 let mut has_lower = false;
346 for c in self.0.as_ref() {
347 if c.is_ascii_uppercase() {
348 return false;
349 }
350 if !has_lower && c.is_ascii_lowercase() {
351 has_lower = true;
352 }
353 }
354
355 has_lower
356 }
357
358 fn isspace(&self) -> bool {
362 if self.0.is_empty() {
363 return false;
364 }
365
366 for c in self.0.as_ref() {
367 if !(c.is_ascii_whitespace() || *c == b'\x0b') {
369 return false;
370 }
371 }
372 true
373 }
374
375 fn isupper(&self) -> bool {
378 let mut has_upper = false;
379 for c in self.0.as_ref() {
380 if c.is_ascii_lowercase() {
381 return false;
382 }
383 if !has_upper && c.is_ascii_uppercase() {
384 has_upper = true;
385 }
386 }
387
388 has_upper
389 }
390
391 fn lower(&self) -> PyBytes {
394 self.0.to_ascii_lowercase().into()
395 }
396
397 fn upper(&self) -> PyBytes {
400 self.0.to_ascii_uppercase().into()
401 }
402
403 fn to_bytes<'py>(&'py self, py: Python<'py>) -> Bound<'py, pyo3::types::PyBytes> {
405 pyo3::types::PyBytes::new(py, &self.0)
406 }
407}
408
409impl<'py> FromPyObject<'py> for PyBytes {
410 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
411 let buffer = ob.extract::<PyBytesWrapper>()?;
412 let bytes = Bytes::from_owner(buffer);
413 Ok(Self(bytes))
414 }
415}
416
417#[derive(Debug)]
422struct PyBytesWrapper(PyBuffer<u8>);
423
424impl AsRef<[u8]> for PyBytesWrapper {
425 #[allow(unsafe_code)]
426 fn as_ref(&self) -> &[u8] {
427 let len = self.0.item_count();
428
429 let ptr = NonNull::new(self.0.buf_ptr() as _).expect("Expected buffer ptr to be non null");
430
431 unsafe { std::slice::from_raw_parts(ptr.as_ptr() as *const u8, len) }
437 }
438}
439
440fn validate_buffer(buf: &PyBuffer<u8>) -> PyResult<()> {
441 if !buf.is_c_contiguous() {
442 return Err(PyValueError::new_err("Buffer is not C contiguous"));
443 }
444
445 if buf.strides().iter().any(|s| *s != 1) {
446 return Err(PyValueError::new_err(format!(
447 "strides other than 1 not supported, got: {:?} ",
448 buf.strides()
449 )));
450 }
451
452 Ok(())
453}
454
455impl<'py> FromPyObject<'py> for PyBytesWrapper {
456 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
457 let buffer = ob.extract::<PyBuffer<u8>>()?;
458 validate_buffer(&buffer)?;
459 Ok(Self(buffer))
460 }
461}
462
463impl std::fmt::Debug for PyBytes {
470 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471 f.write_str("Bytes(b\"")?;
472 for &byte in self.0.as_ref() {
473 match byte {
474 b'\\' => f.write_str(r"\\")?,
476 b'"' => f.write_str("\\\"")?,
477 b'\n' => f.write_str(r"\n")?,
478 b'\r' => f.write_str(r"\r")?,
479 b'\t' => f.write_str(r"\t")?,
480 0x20..=0x7E => f.write_char(byte as char)?,
482 _ => write!(f, "\\x{byte:02x}")?,
483 }
484 }
485 f.write_str("\")")?;
486 Ok(())
487 }
488}
489
490#[derive(FromPyObject)]
492enum BytesGetItemKey<'py> {
493 Int(isize),
495 Slice(Bound<'py, PySlice>),
497}