pyo3/conversions/std/
string.rs

1use std::borrow::Cow;
2
3#[cfg(feature = "experimental-inspect")]
4use crate::inspect::types::TypeInfo;
5use crate::{
6    instance::Bound,
7    types::{any::PyAnyMethods, string::PyStringMethods, PyString},
8    FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject,
9};
10
11/// Converts a Rust `str` to a Python object.
12/// See `PyString::new` for details on the conversion.
13impl ToPyObject for str {
14    #[inline]
15    fn to_object(&self, py: Python<'_>) -> PyObject {
16        PyString::new_bound(py, self).into()
17    }
18}
19
20impl<'a> IntoPy<PyObject> for &'a str {
21    #[inline]
22    fn into_py(self, py: Python<'_>) -> PyObject {
23        PyString::new_bound(py, self).into()
24    }
25
26    #[cfg(feature = "experimental-inspect")]
27    fn type_output() -> TypeInfo {
28        <String>::type_output()
29    }
30}
31
32impl<'a> IntoPy<Py<PyString>> for &'a str {
33    #[inline]
34    fn into_py(self, py: Python<'_>) -> Py<PyString> {
35        PyString::new_bound(py, self).into()
36    }
37
38    #[cfg(feature = "experimental-inspect")]
39    fn type_output() -> TypeInfo {
40        <String>::type_output()
41    }
42}
43
44/// Converts a Rust `Cow<'_, str>` to a Python object.
45/// See `PyString::new` for details on the conversion.
46impl ToPyObject for Cow<'_, str> {
47    #[inline]
48    fn to_object(&self, py: Python<'_>) -> PyObject {
49        PyString::new_bound(py, self).into()
50    }
51}
52
53impl IntoPy<PyObject> for Cow<'_, str> {
54    #[inline]
55    fn into_py(self, py: Python<'_>) -> PyObject {
56        self.to_object(py)
57    }
58
59    #[cfg(feature = "experimental-inspect")]
60    fn type_output() -> TypeInfo {
61        <String>::type_output()
62    }
63}
64
65/// Converts a Rust `String` to a Python object.
66/// See `PyString::new` for details on the conversion.
67impl ToPyObject for String {
68    #[inline]
69    fn to_object(&self, py: Python<'_>) -> PyObject {
70        PyString::new_bound(py, self).into()
71    }
72}
73
74impl ToPyObject for char {
75    fn to_object(&self, py: Python<'_>) -> PyObject {
76        self.into_py(py)
77    }
78}
79
80impl IntoPy<PyObject> for char {
81    fn into_py(self, py: Python<'_>) -> PyObject {
82        let mut bytes = [0u8; 4];
83        PyString::new_bound(py, self.encode_utf8(&mut bytes)).into()
84    }
85
86    #[cfg(feature = "experimental-inspect")]
87    fn type_output() -> TypeInfo {
88        <String>::type_output()
89    }
90}
91
92impl IntoPy<PyObject> for String {
93    fn into_py(self, py: Python<'_>) -> PyObject {
94        PyString::new_bound(py, &self).into()
95    }
96
97    #[cfg(feature = "experimental-inspect")]
98    fn type_output() -> TypeInfo {
99        TypeInfo::builtin("str")
100    }
101}
102
103impl<'a> IntoPy<PyObject> for &'a String {
104    #[inline]
105    fn into_py(self, py: Python<'_>) -> PyObject {
106        PyString::new_bound(py, self).into()
107    }
108
109    #[cfg(feature = "experimental-inspect")]
110    fn type_output() -> TypeInfo {
111        <String>::type_output()
112    }
113}
114
115/// Allows extracting strings from Python objects.
116/// Accepts Python `str` objects.
117#[cfg(feature = "gil-refs")]
118impl<'py> FromPyObject<'py> for &'py str {
119    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
120        ob.clone().into_gil_ref().downcast::<PyString>()?.to_str()
121    }
122
123    #[cfg(feature = "experimental-inspect")]
124    fn type_input() -> TypeInfo {
125        <String as crate::FromPyObject>::type_input()
126    }
127}
128
129#[cfg(all(not(feature = "gil-refs"), any(Py_3_10, not(Py_LIMITED_API))))]
130impl<'a> crate::conversion::FromPyObjectBound<'a, '_> for &'a str {
131    fn from_py_object_bound(ob: crate::Borrowed<'a, '_, PyAny>) -> PyResult<Self> {
132        ob.downcast::<PyString>()?.to_str()
133    }
134
135    #[cfg(feature = "experimental-inspect")]
136    fn type_input() -> TypeInfo {
137        <String as crate::FromPyObject>::type_input()
138    }
139}
140
141#[cfg(feature = "gil-refs")]
142impl<'py> FromPyObject<'py> for Cow<'py, str> {
143    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
144        ob.extract().map(Cow::Owned)
145    }
146
147    #[cfg(feature = "experimental-inspect")]
148    fn type_input() -> TypeInfo {
149        <String as crate::FromPyObject>::type_input()
150    }
151}
152
153#[cfg(not(feature = "gil-refs"))]
154impl<'a> crate::conversion::FromPyObjectBound<'a, '_> for Cow<'a, str> {
155    fn from_py_object_bound(ob: crate::Borrowed<'a, '_, PyAny>) -> PyResult<Self> {
156        ob.downcast::<PyString>()?.to_cow()
157    }
158
159    #[cfg(feature = "experimental-inspect")]
160    fn type_input() -> TypeInfo {
161        <String as crate::FromPyObject>::type_input()
162    }
163}
164
165/// Allows extracting strings from Python objects.
166/// Accepts Python `str` and `unicode` objects.
167impl FromPyObject<'_> for String {
168    fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
169        obj.downcast::<PyString>()?.to_cow().map(Cow::into_owned)
170    }
171
172    #[cfg(feature = "experimental-inspect")]
173    fn type_input() -> TypeInfo {
174        Self::type_output()
175    }
176}
177
178impl FromPyObject<'_> for char {
179    fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
180        let s = obj.downcast::<PyString>()?.to_cow()?;
181        let mut iter = s.chars();
182        if let (Some(ch), None) = (iter.next(), iter.next()) {
183            Ok(ch)
184        } else {
185            Err(crate::exceptions::PyValueError::new_err(
186                "expected a string of length 1",
187            ))
188        }
189    }
190
191    #[cfg(feature = "experimental-inspect")]
192    fn type_input() -> TypeInfo {
193        <String>::type_input()
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use crate::types::any::PyAnyMethods;
200    use crate::Python;
201    use crate::{IntoPy, PyObject, ToPyObject};
202    use std::borrow::Cow;
203
204    #[test]
205    fn test_cow_into_py() {
206        Python::with_gil(|py| {
207            let s = "Hello Python";
208            let py_string: PyObject = Cow::Borrowed(s).into_py(py);
209            assert_eq!(s, py_string.extract::<Cow<'_, str>>(py).unwrap());
210            let py_string: PyObject = Cow::<str>::Owned(s.into()).into_py(py);
211            assert_eq!(s, py_string.extract::<Cow<'_, str>>(py).unwrap());
212        })
213    }
214
215    #[test]
216    fn test_cow_to_object() {
217        Python::with_gil(|py| {
218            let s = "Hello Python";
219            let py_string = Cow::Borrowed(s).to_object(py);
220            assert_eq!(s, py_string.extract::<Cow<'_, str>>(py).unwrap());
221            let py_string = Cow::<str>::Owned(s.into()).to_object(py);
222            assert_eq!(s, py_string.extract::<Cow<'_, str>>(py).unwrap());
223        })
224    }
225
226    #[test]
227    fn test_non_bmp() {
228        Python::with_gil(|py| {
229            let s = "\u{1F30F}";
230            let py_string = s.to_object(py);
231            assert_eq!(s, py_string.extract::<String>(py).unwrap());
232        })
233    }
234
235    #[test]
236    fn test_extract_str() {
237        Python::with_gil(|py| {
238            let s = "Hello Python";
239            let py_string = s.to_object(py);
240
241            let s2: Cow<'_, str> = py_string.bind(py).extract().unwrap();
242            assert_eq!(s, s2);
243        })
244    }
245
246    #[test]
247    fn test_extract_char() {
248        Python::with_gil(|py| {
249            let ch = '😃';
250            let py_string = ch.to_object(py);
251            let ch2: char = py_string.bind(py).extract().unwrap();
252            assert_eq!(ch, ch2);
253        })
254    }
255
256    #[test]
257    fn test_extract_char_err() {
258        Python::with_gil(|py| {
259            let s = "Hello Python";
260            let py_string = s.to_object(py);
261            let err: crate::PyResult<char> = py_string.bind(py).extract();
262            assert!(err
263                .unwrap_err()
264                .to_string()
265                .contains("expected a string of length 1"));
266        })
267    }
268
269    #[test]
270    fn test_string_into_py() {
271        Python::with_gil(|py| {
272            let s = "Hello Python";
273            let s2 = s.to_owned();
274            let s3 = &s2;
275            assert_eq!(
276                s,
277                IntoPy::<PyObject>::into_py(s3, py)
278                    .extract::<Cow<'_, str>>(py)
279                    .unwrap()
280            );
281            assert_eq!(
282                s,
283                IntoPy::<PyObject>::into_py(s2, py)
284                    .extract::<Cow<'_, str>>(py)
285                    .unwrap()
286            );
287            assert_eq!(
288                s,
289                IntoPy::<PyObject>::into_py(s, py)
290                    .extract::<Cow<'_, str>>(py)
291                    .unwrap()
292            );
293        })
294    }
295}