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