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