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