pyo3/conversions/std/
path.rs

1use crate::conversion::IntoPyObject;
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::instance::Bound;
4use crate::sync::GILOnceCell;
5use crate::types::any::PyAnyMethods;
6use crate::{ffi, FromPyObject, IntoPyObjectExt, PyAny, PyErr, PyObject, PyResult, Python};
7#[allow(deprecated)]
8use crate::{IntoPy, ToPyObject};
9use std::borrow::Cow;
10use std::ffi::OsString;
11use std::path::{Path, PathBuf};
12
13#[allow(deprecated)]
14impl ToPyObject for Path {
15    #[inline]
16    fn to_object(&self, py: Python<'_>) -> PyObject {
17        self.as_os_str().into_py_any(py).unwrap()
18    }
19}
20
21// See osstr.rs for why there's no FromPyObject impl for &Path
22
23impl FromPyObject<'_> for PathBuf {
24    fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
25        // We use os.fspath to get the underlying path as bytes or str
26        let path = unsafe { ffi::PyOS_FSPath(ob.as_ptr()).assume_owned_or_err(ob.py())? };
27        Ok(path.extract::<OsString>()?.into())
28    }
29}
30
31#[allow(deprecated)]
32impl IntoPy<PyObject> for &Path {
33    #[inline]
34    fn into_py(self, py: Python<'_>) -> PyObject {
35        self.to_object(py)
36    }
37}
38
39impl<'py> IntoPyObject<'py> for &Path {
40    type Target = PyAny;
41    type Output = Bound<'py, Self::Target>;
42    type Error = PyErr;
43
44    #[inline]
45    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
46        static PY_PATH: GILOnceCell<PyObject> = GILOnceCell::new();
47        PY_PATH
48            .import(py, "pathlib", "Path")?
49            .call((self.as_os_str(),), None)
50    }
51}
52
53impl<'py> IntoPyObject<'py> for &&Path {
54    type Target = PyAny;
55    type Output = Bound<'py, Self::Target>;
56    type Error = PyErr;
57
58    #[inline]
59    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
60        (*self).into_pyobject(py)
61    }
62}
63
64#[allow(deprecated)]
65impl ToPyObject for Cow<'_, Path> {
66    #[inline]
67    fn to_object(&self, py: Python<'_>) -> PyObject {
68        (**self).to_object(py)
69    }
70}
71
72#[allow(deprecated)]
73impl IntoPy<PyObject> for Cow<'_, Path> {
74    #[inline]
75    fn into_py(self, py: Python<'_>) -> PyObject {
76        (*self).to_object(py)
77    }
78}
79
80impl<'py> IntoPyObject<'py> for Cow<'_, Path> {
81    type Target = PyAny;
82    type Output = Bound<'py, Self::Target>;
83    type Error = PyErr;
84
85    #[inline]
86    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
87        (*self).into_pyobject(py)
88    }
89}
90
91impl<'py> IntoPyObject<'py> for &Cow<'_, Path> {
92    type Target = PyAny;
93    type Output = Bound<'py, Self::Target>;
94    type Error = PyErr;
95
96    #[inline]
97    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
98        (&**self).into_pyobject(py)
99    }
100}
101
102#[allow(deprecated)]
103impl ToPyObject for PathBuf {
104    #[inline]
105    fn to_object(&self, py: Python<'_>) -> PyObject {
106        (**self).to_object(py)
107    }
108}
109
110#[allow(deprecated)]
111impl IntoPy<PyObject> for PathBuf {
112    #[inline]
113    fn into_py(self, py: Python<'_>) -> PyObject {
114        (*self).to_object(py)
115    }
116}
117
118impl<'py> IntoPyObject<'py> for PathBuf {
119    type Target = PyAny;
120    type Output = Bound<'py, Self::Target>;
121    type Error = PyErr;
122
123    #[inline]
124    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
125        (&self).into_pyobject(py)
126    }
127}
128
129#[allow(deprecated)]
130impl IntoPy<PyObject> for &PathBuf {
131    #[inline]
132    fn into_py(self, py: Python<'_>) -> PyObject {
133        (**self).to_object(py)
134    }
135}
136
137impl<'py> IntoPyObject<'py> for &PathBuf {
138    type Target = PyAny;
139    type Output = Bound<'py, Self::Target>;
140    type Error = PyErr;
141
142    #[inline]
143    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
144        (&**self).into_pyobject(py)
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use crate::types::{PyAnyMethods, PyString, PyStringMethods};
151    use crate::{IntoPyObject, IntoPyObjectExt, PyObject, Python};
152    use std::borrow::Cow;
153    use std::fmt::Debug;
154    use std::path::{Path, PathBuf};
155
156    #[test]
157    #[cfg(not(windows))]
158    fn test_non_utf8_conversion() {
159        Python::with_gil(|py| {
160            use std::ffi::OsStr;
161            #[cfg(not(target_os = "wasi"))]
162            use std::os::unix::ffi::OsStrExt;
163            #[cfg(target_os = "wasi")]
164            use std::os::wasi::ffi::OsStrExt;
165
166            // this is not valid UTF-8
167            let payload = &[250, 251, 252, 253, 254, 255, 0, 255];
168            let path = Path::new(OsStr::from_bytes(payload));
169
170            // do a roundtrip into Pythonland and back and compare
171            let py_str = path.into_pyobject(py).unwrap();
172            let path_2: PathBuf = py_str.extract().unwrap();
173            assert_eq!(path, path_2);
174        });
175    }
176
177    #[test]
178    fn test_intopyobject_roundtrip() {
179        Python::with_gil(|py| {
180            fn test_roundtrip<'py, T>(py: Python<'py>, obj: T)
181            where
182                T: IntoPyObject<'py> + AsRef<Path> + Debug + Clone,
183                T::Error: Debug,
184            {
185                let pyobject = obj.clone().into_bound_py_any(py).unwrap();
186                let roundtripped_obj: PathBuf = pyobject.extract().unwrap();
187                assert_eq!(obj.as_ref(), roundtripped_obj.as_path());
188            }
189            let path = Path::new("Hello\0\nšŸ");
190            test_roundtrip::<&Path>(py, path);
191            test_roundtrip::<Cow<'_, Path>>(py, Cow::Borrowed(path));
192            test_roundtrip::<Cow<'_, Path>>(py, Cow::Owned(path.to_path_buf()));
193            test_roundtrip::<PathBuf>(py, path.to_path_buf());
194        });
195    }
196
197    #[test]
198    fn test_from_pystring() {
199        Python::with_gil(|py| {
200            let path = "Hello\0\nšŸ";
201            let pystring = PyString::new(py, path);
202            let roundtrip: PathBuf = pystring.extract().unwrap();
203            assert_eq!(roundtrip, Path::new(path));
204        });
205    }
206
207    #[test]
208    #[allow(deprecated)]
209    fn test_intopy_string() {
210        use crate::IntoPy;
211
212        Python::with_gil(|py| {
213            fn test_roundtrip<T>(py: Python<'_>, obj: T)
214            where
215                T: IntoPy<PyObject> + AsRef<Path> + Debug + Clone,
216            {
217                let pyobject = obj.clone().into_py(py).into_bound(py);
218                let pystring = pyobject.downcast_exact::<PyString>().unwrap();
219                assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy());
220                let roundtripped_obj: PathBuf = pyobject.extract().unwrap();
221                assert_eq!(obj.as_ref(), roundtripped_obj.as_path());
222            }
223            let path = Path::new("Hello\0\nšŸ");
224            test_roundtrip::<&Path>(py, path);
225            test_roundtrip::<Cow<'_, Path>>(py, Cow::Borrowed(path));
226            test_roundtrip::<Cow<'_, Path>>(py, Cow::Owned(path.to_path_buf()));
227            test_roundtrip::<PathBuf>(py, path.to_path_buf());
228        });
229    }
230}
āš ļø Internal Docs āš ļø Not Public API šŸ‘‰ Official Docs Here