1#![allow(clippy::undocumented_unsafe_blocks)]
3
4#[cfg(feature = "experimental-inspect")]
7use crate::inspect::PyStaticExpr;
8use crate::platform::prelude::*;
9#[cfg(feature = "experimental-inspect")]
10use crate::type_hint_union;
11use crate::{
12 types::{
13 bytearray::PyByteArrayMethods, bytes::PyBytesMethods, string::PyStringMethods, PyByteArray,
14 PyBytes, PyString, PyTuple,
15 },
16 Borrowed, Bound, CastError, FromPyObject, IntoPyObject, Py, PyAny, PyErr, PyTypeInfo, Python,
17};
18use alloc::sync::Arc;
19use core::{borrow::Borrow, convert::Infallible, ops::Deref, ptr::NonNull};
20
21#[cfg_attr(feature = "py-clone", derive(Clone))]
28pub struct PyBackedStr {
29 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
30 storage: Py<PyString>,
31 #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
32 storage: Py<PyBytes>,
33 data: NonNull<str>,
34}
35
36impl PyBackedStr {
37 #[inline]
41 pub fn clone_ref(&self, py: Python<'_>) -> Self {
42 Self {
43 storage: self.storage.clone_ref(py),
44 data: self.data,
45 }
46 }
47
48 #[inline]
50 pub fn as_str(&self) -> &str {
51 unsafe { self.data.as_ref() }
53 }
54
55 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
59 #[inline]
60 pub fn as_py_str(&self) -> &Py<PyString> {
61 &self.storage
62 }
63}
64
65impl Deref for PyBackedStr {
66 type Target = str;
67 #[inline]
68 fn deref(&self) -> &str {
69 self.as_str()
70 }
71}
72
73impl AsRef<str> for PyBackedStr {
74 #[inline]
75 fn as_ref(&self) -> &str {
76 self
77 }
78}
79
80impl AsRef<[u8]> for PyBackedStr {
81 #[inline]
82 fn as_ref(&self) -> &[u8] {
83 self.as_bytes()
84 }
85}
86
87impl Borrow<str> for PyBackedStr {
88 #[inline]
89 fn borrow(&self) -> &str {
90 self
91 }
92}
93
94unsafe impl Send for PyBackedStr {}
97unsafe impl Sync for PyBackedStr {}
98
99impl core::fmt::Display for PyBackedStr {
100 #[inline]
101 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102 self.deref().fmt(f)
103 }
104}
105
106impl_traits!(PyBackedStr, str);
107
108impl TryFrom<Bound<'_, PyString>> for PyBackedStr {
109 type Error = PyErr;
110 fn try_from(py_string: Bound<'_, PyString>) -> Result<Self, Self::Error> {
111 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
112 {
113 let s = py_string.to_str()?;
114 let data = NonNull::from(s);
115 Ok(Self {
116 storage: py_string.unbind(),
117 data,
118 })
119 }
120 #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
121 {
122 let bytes = py_string.encode_utf8()?;
123 let s = unsafe { core::str::from_utf8_unchecked(bytes.as_bytes()) };
124 let data = NonNull::from(s);
125 Ok(Self {
126 storage: bytes.unbind(),
127 data,
128 })
129 }
130 }
131}
132
133impl FromPyObject<'_, '_> for PyBackedStr {
134 type Error = PyErr;
135
136 #[cfg(feature = "experimental-inspect")]
137 const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
138
139 #[inline]
140 fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
141 let py_string = obj.cast::<PyString>()?.to_owned();
142 Self::try_from(py_string)
143 }
144}
145
146impl<'py> IntoPyObject<'py> for PyBackedStr {
147 type Target = PyString;
148 type Output = Bound<'py, Self::Target>;
149 type Error = Infallible;
150
151 #[cfg(feature = "experimental-inspect")]
152 const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
153
154 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
155 #[inline]
156 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
157 Ok(self.storage.into_bound(py))
158 }
159
160 #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
161 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
162 Ok(PyString::new(py, &self))
163 }
164}
165
166impl<'py> IntoPyObject<'py> for &PyBackedStr {
167 type Target = PyString;
168 type Output = Bound<'py, Self::Target>;
169 type Error = Infallible;
170
171 #[cfg(feature = "experimental-inspect")]
172 const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
173
174 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
175 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
176 Ok(self.storage.bind(py).to_owned())
177 }
178
179 #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
180 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
181 Ok(PyString::new(py, self))
182 }
183}
184
185#[cfg_attr(feature = "py-clone", derive(Clone))]
189pub struct PyBackedBytes {
190 storage: PyBackedBytesStorage,
191 data: NonNull<[u8]>,
192}
193
194#[cfg_attr(feature = "py-clone", derive(Clone))]
195enum PyBackedBytesStorage {
196 Python(Py<PyBytes>),
197 Rust(Arc<[u8]>),
198}
199
200impl PyBackedBytes {
201 pub fn clone_ref(&self, py: Python<'_>) -> Self {
205 Self {
206 storage: match &self.storage {
207 PyBackedBytesStorage::Python(bytes) => {
208 PyBackedBytesStorage::Python(bytes.clone_ref(py))
209 }
210 PyBackedBytesStorage::Rust(bytes) => PyBackedBytesStorage::Rust(bytes.clone()),
211 },
212 data: self.data,
213 }
214 }
215}
216
217impl Deref for PyBackedBytes {
218 type Target = [u8];
219 fn deref(&self) -> &[u8] {
220 unsafe { self.data.as_ref() }
222 }
223}
224
225impl AsRef<[u8]> for PyBackedBytes {
226 fn as_ref(&self) -> &[u8] {
227 self
228 }
229}
230
231unsafe impl Send for PyBackedBytes {}
234unsafe impl Sync for PyBackedBytes {}
235
236impl<const N: usize> PartialEq<[u8; N]> for PyBackedBytes {
237 fn eq(&self, other: &[u8; N]) -> bool {
238 self.deref() == other
239 }
240}
241
242impl<const N: usize> PartialEq<PyBackedBytes> for [u8; N] {
243 fn eq(&self, other: &PyBackedBytes) -> bool {
244 self == other.deref()
245 }
246}
247
248impl<const N: usize> PartialEq<&[u8; N]> for PyBackedBytes {
249 fn eq(&self, other: &&[u8; N]) -> bool {
250 self.deref() == *other
251 }
252}
253
254impl<const N: usize> PartialEq<PyBackedBytes> for &[u8; N] {
255 fn eq(&self, other: &PyBackedBytes) -> bool {
256 self == &other.deref()
257 }
258}
259
260impl_traits!(PyBackedBytes, [u8]);
261
262impl From<Bound<'_, PyBytes>> for PyBackedBytes {
263 fn from(py_bytes: Bound<'_, PyBytes>) -> Self {
264 let b = py_bytes.as_bytes();
265 let data = NonNull::from(b);
266 Self {
267 storage: PyBackedBytesStorage::Python(py_bytes.to_owned().unbind()),
268 data,
269 }
270 }
271}
272
273impl From<Bound<'_, PyByteArray>> for PyBackedBytes {
274 fn from(py_bytearray: Bound<'_, PyByteArray>) -> Self {
275 let s = Arc::<[u8]>::from(py_bytearray.to_vec());
276 let data = NonNull::from(s.as_ref());
277 Self {
278 storage: PyBackedBytesStorage::Rust(s),
279 data,
280 }
281 }
282}
283
284impl<'a, 'py> FromPyObject<'a, 'py> for PyBackedBytes {
285 type Error = CastError<'a, 'py>;
286
287 #[cfg(feature = "experimental-inspect")]
288 const INPUT_TYPE: PyStaticExpr = type_hint_union!(PyBytes::TYPE_HINT, PyByteArray::TYPE_HINT);
289
290 fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
291 if let Ok(bytes) = obj.cast::<PyBytes>() {
292 Ok(Self::from(bytes.to_owned()))
293 } else if let Ok(bytearray) = obj.cast::<PyByteArray>() {
294 Ok(Self::from(bytearray.to_owned()))
295 } else {
296 Err(CastError::new(
297 obj,
298 PyTuple::new(
299 obj.py(),
300 [
301 PyBytes::type_object(obj.py()),
302 PyByteArray::type_object(obj.py()),
303 ],
304 )
305 .unwrap()
306 .into_any(),
307 ))
308 }
309 }
310}
311
312impl<'py> IntoPyObject<'py> for PyBackedBytes {
313 type Target = PyBytes;
314 type Output = Bound<'py, Self::Target>;
315 type Error = Infallible;
316
317 #[cfg(feature = "experimental-inspect")]
318 const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
319
320 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
321 match self.storage {
322 PyBackedBytesStorage::Python(bytes) => Ok(bytes.into_bound(py)),
323 PyBackedBytesStorage::Rust(bytes) => Ok(PyBytes::new(py, &bytes)),
324 }
325 }
326}
327
328impl<'py> IntoPyObject<'py> for &PyBackedBytes {
329 type Target = PyBytes;
330 type Output = Bound<'py, Self::Target>;
331 type Error = Infallible;
332
333 #[cfg(feature = "experimental-inspect")]
334 const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
335
336 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
337 match &self.storage {
338 PyBackedBytesStorage::Python(bytes) => Ok(bytes.bind(py).clone()),
339 PyBackedBytesStorage::Rust(bytes) => Ok(PyBytes::new(py, bytes)),
340 }
341 }
342}
343
344macro_rules! impl_traits {
345 ($slf:ty, $equiv:ty) => {
346 impl core::fmt::Debug for $slf {
347 #[inline]
348 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
349 self.deref().fmt(f)
350 }
351 }
352
353 impl PartialEq for $slf {
354 #[inline]
355 fn eq(&self, other: &Self) -> bool {
356 self.deref() == other.deref()
357 }
358 }
359
360 impl PartialEq<$equiv> for $slf {
361 #[inline]
362 fn eq(&self, other: &$equiv) -> bool {
363 self.deref() == other
364 }
365 }
366
367 impl PartialEq<&$equiv> for $slf {
368 #[inline]
369 fn eq(&self, other: &&$equiv) -> bool {
370 self.deref() == *other
371 }
372 }
373
374 impl PartialEq<$slf> for $equiv {
375 #[inline]
376 fn eq(&self, other: &$slf) -> bool {
377 self == other.deref()
378 }
379 }
380
381 impl PartialEq<$slf> for &$equiv {
382 #[inline]
383 fn eq(&self, other: &$slf) -> bool {
384 self == &other.deref()
385 }
386 }
387
388 impl Eq for $slf {}
389
390 impl PartialOrd for $slf {
391 #[inline]
392 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
393 Some(self.cmp(other))
394 }
395 }
396
397 impl PartialOrd<$equiv> for $slf {
398 #[inline]
399 fn partial_cmp(&self, other: &$equiv) -> Option<core::cmp::Ordering> {
400 self.deref().partial_cmp(other)
401 }
402 }
403
404 impl PartialOrd<$slf> for $equiv {
405 #[inline]
406 fn partial_cmp(&self, other: &$slf) -> Option<core::cmp::Ordering> {
407 self.partial_cmp(other.deref())
408 }
409 }
410
411 impl Ord for $slf {
412 #[inline]
413 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
414 self.deref().cmp(other.deref())
415 }
416 }
417
418 impl core::hash::Hash for $slf {
419 #[inline]
420 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
421 self.deref().hash(state)
422 }
423 }
424 };
425}
426use impl_traits;
427
428#[cfg(test)]
429mod test {
430 use super::*;
431 use crate::impl_::pyclass::{value_of, IsSend, IsSync};
432 use crate::types::PyAnyMethods as _;
433 use crate::{IntoPyObject, Python};
434 use core::hash::{Hash, Hasher};
435 use std::collections::hash_map::DefaultHasher;
436
437 #[test]
438 fn py_backed_str_empty() {
439 Python::attach(|py| {
440 let s = PyString::new(py, "");
441 let py_backed_str = s.extract::<PyBackedStr>().unwrap();
442 assert_eq!(&*py_backed_str, "");
443 });
444 }
445
446 #[test]
447 fn py_backed_str() {
448 Python::attach(|py| {
449 let s = PyString::new(py, "hello");
450 let py_backed_str = s.extract::<PyBackedStr>().unwrap();
451 assert_eq!(&*py_backed_str, "hello");
452 });
453 }
454
455 #[test]
456 fn py_backed_str_try_from() {
457 Python::attach(|py| {
458 let s = PyString::new(py, "hello");
459 let py_backed_str = PyBackedStr::try_from(s).unwrap();
460 assert_eq!(&*py_backed_str, "hello");
461 });
462 }
463
464 #[test]
465 fn py_backed_str_into_pyobject() {
466 Python::attach(|py| {
467 let orig_str = PyString::new(py, "hello");
468 let py_backed_str = orig_str.extract::<PyBackedStr>().unwrap();
469 let new_str = py_backed_str.into_pyobject(py).unwrap();
470 assert_eq!(new_str.extract::<PyBackedStr>().unwrap(), "hello");
471 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
472 assert!(new_str.is(&orig_str));
473 });
474 }
475
476 #[test]
477 fn py_backed_bytes_empty() {
478 Python::attach(|py| {
479 let b = PyBytes::new(py, b"");
480 let py_backed_bytes = b.extract::<PyBackedBytes>().unwrap();
481 assert_eq!(&*py_backed_bytes, b"");
482 });
483 }
484
485 #[test]
486 fn py_backed_bytes() {
487 Python::attach(|py| {
488 let b = PyBytes::new(py, b"abcde");
489 let py_backed_bytes = b.extract::<PyBackedBytes>().unwrap();
490 assert_eq!(&*py_backed_bytes, b"abcde");
491 });
492 }
493
494 #[test]
495 fn py_backed_bytes_from_bytes() {
496 Python::attach(|py| {
497 let b = PyBytes::new(py, b"abcde");
498 let py_backed_bytes = PyBackedBytes::from(b);
499 assert_eq!(&*py_backed_bytes, b"abcde");
500 });
501 }
502
503 #[test]
504 fn py_backed_bytes_from_bytearray() {
505 Python::attach(|py| {
506 let b = PyByteArray::new(py, b"abcde");
507 let py_backed_bytes = PyBackedBytes::from(b);
508 assert_eq!(&*py_backed_bytes, b"abcde");
509 });
510 }
511
512 #[test]
513 fn py_backed_bytes_into_pyobject() {
514 Python::attach(|py| {
515 let orig_bytes = PyBytes::new(py, b"abcde");
516 let py_backed_bytes = PyBackedBytes::from(orig_bytes.clone());
517 assert!((&py_backed_bytes)
518 .into_pyobject(py)
519 .unwrap()
520 .is(&orig_bytes));
521 });
522 }
523
524 #[test]
525 fn rust_backed_bytes_into_pyobject() {
526 Python::attach(|py| {
527 let orig_bytes = PyByteArray::new(py, b"abcde");
528 let rust_backed_bytes = PyBackedBytes::from(orig_bytes);
529 assert!(matches!(
530 rust_backed_bytes.storage,
531 PyBackedBytesStorage::Rust(_)
532 ));
533 let to_object = (&rust_backed_bytes).into_pyobject(py).unwrap();
534 assert!(&to_object.is_exact_instance_of::<PyBytes>());
535 assert_eq!(&to_object.extract::<PyBackedBytes>().unwrap(), b"abcde");
536 });
537 }
538
539 #[test]
540 fn test_backed_types_send_sync() {
541 assert!(value_of!(IsSend, PyBackedStr));
542 assert!(value_of!(IsSync, PyBackedStr));
543
544 assert!(value_of!(IsSend, PyBackedBytes));
545 assert!(value_of!(IsSync, PyBackedBytes));
546 }
547
548 #[cfg(feature = "py-clone")]
549 #[test]
550 fn test_backed_str_clone() {
551 Python::attach(|py| {
552 let s1: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
553 let s2 = s1.clone();
554 assert_eq!(s1, s2);
555
556 drop(s1);
557 assert_eq!(s2, "hello");
558 });
559 }
560
561 #[test]
562 fn test_backed_str_clone_ref() {
563 Python::attach(|py| {
564 let s1: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
565 let s2 = s1.clone_ref(py);
566 assert_eq!(s1, s2);
567 assert!(s1.storage.is(&s2.storage));
568
569 drop(s1);
570 assert_eq!(s2, "hello");
571 });
572 }
573
574 #[test]
575 fn test_backed_str_eq() {
576 Python::attach(|py| {
577 let s1: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
578 let s2: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
579 assert_eq!(s1, "hello");
580 assert_eq!(s1, s2);
581
582 let s3: PyBackedStr = PyString::new(py, "abcde").try_into().unwrap();
583 assert_eq!("abcde", s3);
584 assert_ne!(s1, s3);
585 });
586 }
587
588 #[test]
589 fn test_backed_str_hash() {
590 Python::attach(|py| {
591 let h = {
592 let mut hasher = DefaultHasher::new();
593 "abcde".hash(&mut hasher);
594 hasher.finish()
595 };
596
597 let s1: PyBackedStr = PyString::new(py, "abcde").try_into().unwrap();
598 let h1 = {
599 let mut hasher = DefaultHasher::new();
600 s1.hash(&mut hasher);
601 hasher.finish()
602 };
603
604 assert_eq!(h, h1);
605 });
606 }
607
608 #[test]
609 fn test_backed_str_ord() {
610 Python::attach(|py| {
611 let mut a = vec!["a", "c", "d", "b", "f", "g", "e"];
612 let mut b = a
613 .iter()
614 .map(|s| PyString::new(py, s).try_into().unwrap())
615 .collect::<Vec<PyBackedStr>>();
616
617 a.sort();
618 b.sort();
619
620 assert_eq!(a, b);
621 })
622 }
623
624 #[test]
625 fn test_backed_str_map_key() {
626 Python::attach(|py| {
627 use crate::platform::HashMap;
628
629 let mut map: HashMap<PyBackedStr, usize> = HashMap::new();
630 let s: PyBackedStr = PyString::new(py, "key1").try_into().unwrap();
631
632 map.insert(s, 1);
633
634 assert_eq!(map.get("key1"), Some(&1));
635 });
636 }
637
638 #[test]
639 fn test_backed_str_as_str() {
640 Python::attach(|py| {
641 let s: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
642 assert_eq!(s.as_str(), "hello");
643 });
644 }
645
646 #[test]
647 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
648 fn test_backed_str_as_py_str() {
649 Python::attach(|py| {
650 let s: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
651 let py_str = s.as_py_str().bind(py);
652 assert!(py_str.is(&s.storage));
653 assert_eq!(py_str.to_str().unwrap(), "hello");
654 });
655 }
656
657 #[cfg(feature = "py-clone")]
658 #[test]
659 fn test_backed_bytes_from_bytes_clone() {
660 Python::attach(|py| {
661 let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
662 let b2 = b1.clone();
663 assert_eq!(b1, b2);
664
665 drop(b1);
666 assert_eq!(b2, b"abcde");
667 });
668 }
669
670 #[test]
671 fn test_backed_bytes_from_bytes_clone_ref() {
672 Python::attach(|py| {
673 let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
674 let b2 = b1.clone_ref(py);
675 assert_eq!(b1, b2);
676 let (PyBackedBytesStorage::Python(s1), PyBackedBytesStorage::Python(s2)) =
677 (&b1.storage, &b2.storage)
678 else {
679 panic!("Expected Python-backed bytes");
680 };
681 assert!(s1.is(s2));
682
683 drop(b1);
684 assert_eq!(b2, b"abcde");
685 });
686 }
687
688 #[cfg(feature = "py-clone")]
689 #[test]
690 fn test_backed_bytes_from_bytearray_clone() {
691 Python::attach(|py| {
692 let b1: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
693 let b2 = b1.clone();
694 assert_eq!(b1, b2);
695
696 drop(b1);
697 assert_eq!(b2, b"abcde");
698 });
699 }
700
701 #[test]
702 fn test_backed_bytes_from_bytearray_clone_ref() {
703 Python::attach(|py| {
704 let b1: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
705 let b2 = b1.clone_ref(py);
706 assert_eq!(b1, b2);
707 let (PyBackedBytesStorage::Rust(s1), PyBackedBytesStorage::Rust(s2)) =
708 (&b1.storage, &b2.storage)
709 else {
710 panic!("Expected Rust-backed bytes");
711 };
712 assert!(Arc::ptr_eq(s1, s2));
713
714 drop(b1);
715 assert_eq!(b2, b"abcde");
716 });
717 }
718
719 #[test]
720 fn test_backed_bytes_eq() {
721 Python::attach(|py| {
722 let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
723 let b2: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
724
725 assert_eq!(b1, b"abcde");
726 assert_eq!(b1, b2);
727
728 let b3: PyBackedBytes = PyBytes::new(py, b"hello").into();
729 assert_eq!(b"hello", b3);
730 assert_ne!(b1, b3);
731 });
732 }
733
734 #[test]
735 fn test_backed_bytes_hash() {
736 Python::attach(|py| {
737 let h = {
738 let mut hasher = DefaultHasher::new();
739 b"abcde".hash(&mut hasher);
740 hasher.finish()
741 };
742
743 let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
744 let h1 = {
745 let mut hasher = DefaultHasher::new();
746 b1.hash(&mut hasher);
747 hasher.finish()
748 };
749
750 let b2: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
751 let h2 = {
752 let mut hasher = DefaultHasher::new();
753 b2.hash(&mut hasher);
754 hasher.finish()
755 };
756
757 assert_eq!(h, h1);
758 assert_eq!(h, h2);
759 });
760 }
761
762 #[test]
763 fn test_backed_bytes_ord() {
764 Python::attach(|py| {
765 let mut a = vec![b"a", b"c", b"d", b"b", b"f", b"g", b"e"];
766 let mut b = a
767 .iter()
768 .map(|&b| PyBytes::new(py, b).into())
769 .collect::<Vec<PyBackedBytes>>();
770
771 a.sort();
772 b.sort();
773
774 assert_eq!(a, b);
775 })
776 }
777}