Skip to main content

pyo3/conversions/std/
string.rs

1#[cfg(feature = "experimental-inspect")]
2use crate::inspect::types::TypeInfo;
3#[cfg(feature = "experimental-inspect")]
4use crate::inspect::PyStaticExpr;
5#[cfg(feature = "experimental-inspect")]
6use crate::type_object::PyTypeInfo;
7use crate::{
8    conversion::IntoPyObject, instance::Bound, types::PyString, Borrowed, FromPyObject, PyAny,
9    PyErr, Python,
10};
11use std::{borrow::Cow, convert::Infallible};
12
13impl<'py> IntoPyObject<'py> for &str {
14    type Target = PyString;
15    type Output = Bound<'py, Self::Target>;
16    type Error = Infallible;
17
18    #[cfg(feature = "experimental-inspect")]
19    const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
20
21    #[inline]
22    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
23        Ok(PyString::new(py, self))
24    }
25
26    #[cfg(feature = "experimental-inspect")]
27    fn type_output() -> TypeInfo {
28        <String>::type_output()
29    }
30}
31
32impl<'py> IntoPyObject<'py> for &&str {
33    type Target = PyString;
34    type Output = Bound<'py, Self::Target>;
35    type Error = Infallible;
36
37    #[cfg(feature = "experimental-inspect")]
38    const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
39
40    #[inline]
41    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
42        (*self).into_pyobject(py)
43    }
44
45    #[cfg(feature = "experimental-inspect")]
46    fn type_output() -> TypeInfo {
47        <String>::type_output()
48    }
49}
50
51impl<'py> IntoPyObject<'py> for Cow<'_, str> {
52    type Target = PyString;
53    type Output = Bound<'py, Self::Target>;
54    type Error = Infallible;
55
56    #[cfg(feature = "experimental-inspect")]
57    const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
58
59    #[inline]
60    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
61        (*self).into_pyobject(py)
62    }
63
64    #[cfg(feature = "experimental-inspect")]
65    fn type_output() -> TypeInfo {
66        <String>::type_output()
67    }
68}
69
70impl<'py> IntoPyObject<'py> for &Cow<'_, str> {
71    type Target = PyString;
72    type Output = Bound<'py, Self::Target>;
73    type Error = Infallible;
74
75    #[cfg(feature = "experimental-inspect")]
76    const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
77
78    #[inline]
79    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
80        (&**self).into_pyobject(py)
81    }
82
83    #[cfg(feature = "experimental-inspect")]
84    fn type_output() -> TypeInfo {
85        <String>::type_output()
86    }
87}
88
89impl<'py> IntoPyObject<'py> for char {
90    type Target = PyString;
91    type Output = Bound<'py, Self::Target>;
92    type Error = Infallible;
93
94    #[cfg(feature = "experimental-inspect")]
95    const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
96
97    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
98        let mut bytes = [0u8; 4];
99        Ok(PyString::new(py, self.encode_utf8(&mut bytes)))
100    }
101
102    #[cfg(feature = "experimental-inspect")]
103    fn type_output() -> TypeInfo {
104        <String>::type_output()
105    }
106}
107
108impl<'py> IntoPyObject<'py> for &char {
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: PyStaticExpr = String::OUTPUT_TYPE;
115
116    #[inline]
117    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
118        (*self).into_pyobject(py)
119    }
120
121    #[cfg(feature = "experimental-inspect")]
122    fn type_output() -> TypeInfo {
123        <String>::type_output()
124    }
125}
126
127impl<'py> IntoPyObject<'py> for String {
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: PyStaticExpr = PyString::TYPE_HINT;
134
135    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
136        Ok(PyString::new(py, &self))
137    }
138
139    #[cfg(feature = "experimental-inspect")]
140    fn type_output() -> TypeInfo {
141        TypeInfo::builtin("str")
142    }
143}
144
145impl<'py> IntoPyObject<'py> for &String {
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 = String::OUTPUT_TYPE;
152
153    #[inline]
154    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
155        Ok(PyString::new(py, self))
156    }
157
158    #[cfg(feature = "experimental-inspect")]
159    fn type_output() -> TypeInfo {
160        <String>::type_output()
161    }
162}
163
164#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
165impl<'a> crate::conversion::FromPyObject<'a, '_> for &'a str {
166    type Error = PyErr;
167
168    #[cfg(feature = "experimental-inspect")]
169    const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
170
171    fn extract(ob: crate::Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
172        ob.cast::<PyString>()?.to_str()
173    }
174
175    #[cfg(feature = "experimental-inspect")]
176    fn type_input() -> TypeInfo {
177        <String as crate::FromPyObject>::type_input()
178    }
179}
180
181impl<'a> crate::conversion::FromPyObject<'a, '_> for Cow<'a, str> {
182    type Error = PyErr;
183
184    #[cfg(feature = "experimental-inspect")]
185    const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
186
187    fn extract(ob: crate::Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
188        ob.cast::<PyString>()?.to_cow()
189    }
190
191    #[cfg(feature = "experimental-inspect")]
192    fn type_input() -> TypeInfo {
193        <String as crate::FromPyObject>::type_input()
194    }
195}
196
197/// Allows extracting strings from Python objects.
198/// Accepts Python `str` and `unicode` objects.
199impl FromPyObject<'_, '_> for String {
200    type Error = PyErr;
201
202    #[cfg(feature = "experimental-inspect")]
203    const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
204
205    fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
206        obj.cast::<PyString>()?.to_cow().map(Cow::into_owned)
207    }
208
209    #[cfg(feature = "experimental-inspect")]
210    fn type_input() -> TypeInfo {
211        Self::type_output()
212    }
213}
214
215impl FromPyObject<'_, '_> for char {
216    type Error = PyErr;
217
218    #[cfg(feature = "experimental-inspect")]
219    const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
220
221    fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
222        let s = obj.cast::<PyString>()?.to_cow()?;
223        let mut iter = s.chars();
224        if let (Some(ch), None) = (iter.next(), iter.next()) {
225            Ok(ch)
226        } else {
227            Err(crate::exceptions::PyValueError::new_err(
228                "expected a string of length 1",
229            ))
230        }
231    }
232
233    #[cfg(feature = "experimental-inspect")]
234    fn type_input() -> TypeInfo {
235        <String>::type_input()
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use crate::types::any::PyAnyMethods;
242    use crate::{IntoPyObject, Python};
243    use std::borrow::Cow;
244
245    #[test]
246    fn test_cow_into_pyobject() {
247        Python::attach(|py| {
248            let s = "Hello Python";
249            let py_string = Cow::Borrowed(s).into_pyobject(py).unwrap();
250            assert_eq!(s, py_string.extract::<Cow<'_, str>>().unwrap());
251            let py_string = Cow::<str>::Owned(s.into()).into_pyobject(py).unwrap();
252            assert_eq!(s, py_string.extract::<Cow<'_, str>>().unwrap());
253        })
254    }
255
256    #[test]
257    fn test_non_bmp() {
258        Python::attach(|py| {
259            let s = "\u{1F30F}";
260            let py_string = s.into_pyobject(py).unwrap();
261            assert_eq!(s, py_string.extract::<String>().unwrap());
262        })
263    }
264
265    #[test]
266    fn test_extract_str() {
267        Python::attach(|py| {
268            let s = "Hello Python";
269            let py_string = s.into_pyobject(py).unwrap();
270
271            let s2: Cow<'_, str> = py_string.extract().unwrap();
272            assert_eq!(s, s2);
273        })
274    }
275
276    #[test]
277    fn test_extract_char() {
278        Python::attach(|py| {
279            let ch = '😃';
280            let py_string = ch.into_pyobject(py).unwrap();
281            let ch2: char = py_string.extract().unwrap();
282            assert_eq!(ch, ch2);
283        })
284    }
285
286    #[test]
287    fn test_extract_char_err() {
288        Python::attach(|py| {
289            let s = "Hello Python";
290            let py_string = s.into_pyobject(py).unwrap();
291            let err: crate::PyResult<char> = py_string.extract();
292            assert!(err
293                .unwrap_err()
294                .to_string()
295                .contains("expected a string of length 1"));
296        })
297    }
298
299    #[test]
300    fn test_string_into_pyobject() {
301        Python::attach(|py| {
302            let s = "Hello Python";
303            let s2 = s.to_owned();
304            let s3 = &s2;
305            assert_eq!(
306                s,
307                s3.into_pyobject(py)
308                    .unwrap()
309                    .extract::<Cow<'_, str>>()
310                    .unwrap()
311            );
312            assert_eq!(
313                s,
314                s2.into_pyobject(py)
315                    .unwrap()
316                    .extract::<Cow<'_, str>>()
317                    .unwrap()
318            );
319            assert_eq!(
320                s,
321                s.into_pyobject(py)
322                    .unwrap()
323                    .extract::<Cow<'_, str>>()
324                    .unwrap()
325            );
326        })
327    }
328}