pyo3/conversions/std/
path.rs

1use crate::conversion::IntoPyObject;
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::instance::Bound;
4use crate::types::any::PyAnyMethods;
5use crate::types::PyString;
6use crate::{ffi, FromPyObject, PyAny, PyObject, PyResult, Python};
7#[allow(deprecated)]
8use crate::{IntoPy, ToPyObject};
9use std::borrow::Cow;
10use std::convert::Infallible;
11use std::ffi::OsString;
12use std::path::{Path, PathBuf};
13
14#[allow(deprecated)]
15impl ToPyObject for Path {
16    #[inline]
17    fn to_object(&self, py: Python<'_>) -> PyObject {
18        self.into_pyobject(py).unwrap().into_any().unbind()
19    }
20}
21
22// See osstr.rs for why there's no FromPyObject impl for &Path
23
24impl FromPyObject<'_> for PathBuf {
25    fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
26        // We use os.fspath to get the underlying path as bytes or str
27        let path = unsafe { ffi::PyOS_FSPath(ob.as_ptr()).assume_owned_or_err(ob.py())? };
28        Ok(path.extract::<OsString>()?.into())
29    }
30}
31
32#[allow(deprecated)]
33impl IntoPy<PyObject> for &Path {
34    #[inline]
35    fn into_py(self, py: Python<'_>) -> PyObject {
36        self.into_pyobject(py).unwrap().into_any().unbind()
37    }
38}
39
40impl<'py> IntoPyObject<'py> for &Path {
41    type Target = PyString;
42    type Output = Bound<'py, Self::Target>;
43    type Error = Infallible;
44
45    #[inline]
46    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
47        self.as_os_str().into_pyobject(py)
48    }
49}
50
51impl<'py> IntoPyObject<'py> for &&Path {
52    type Target = PyString;
53    type Output = Bound<'py, Self::Target>;
54    type Error = Infallible;
55
56    #[inline]
57    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
58        (*self).into_pyobject(py)
59    }
60}
61
62#[allow(deprecated)]
63impl ToPyObject for Cow<'_, Path> {
64    #[inline]
65    fn to_object(&self, py: Python<'_>) -> PyObject {
66        self.into_pyobject(py).unwrap().into_any().unbind()
67    }
68}
69
70#[allow(deprecated)]
71impl IntoPy<PyObject> for Cow<'_, Path> {
72    #[inline]
73    fn into_py(self, py: Python<'_>) -> PyObject {
74        self.into_pyobject(py).unwrap().into_any().unbind()
75    }
76}
77
78impl<'py> IntoPyObject<'py> for Cow<'_, Path> {
79    type Target = PyString;
80    type Output = Bound<'py, Self::Target>;
81    type Error = Infallible;
82
83    #[inline]
84    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
85        self.as_os_str().into_pyobject(py)
86    }
87}
88
89impl<'py> IntoPyObject<'py> for &Cow<'_, Path> {
90    type Target = PyString;
91    type Output = Bound<'py, Self::Target>;
92    type Error = Infallible;
93
94    #[inline]
95    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
96        self.as_os_str().into_pyobject(py)
97    }
98}
99
100#[allow(deprecated)]
101impl ToPyObject for PathBuf {
102    #[inline]
103    fn to_object(&self, py: Python<'_>) -> PyObject {
104        self.into_pyobject(py).unwrap().into_any().unbind()
105    }
106}
107
108#[allow(deprecated)]
109impl IntoPy<PyObject> for PathBuf {
110    #[inline]
111    fn into_py(self, py: Python<'_>) -> PyObject {
112        self.into_pyobject(py).unwrap().into_any().unbind()
113    }
114}
115
116impl<'py> IntoPyObject<'py> for PathBuf {
117    type Target = PyString;
118    type Output = Bound<'py, Self::Target>;
119    type Error = Infallible;
120
121    #[inline]
122    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
123        self.as_os_str().into_pyobject(py)
124    }
125}
126
127#[allow(deprecated)]
128impl IntoPy<PyObject> for &PathBuf {
129    #[inline]
130    fn into_py(self, py: Python<'_>) -> PyObject {
131        self.into_pyobject(py).unwrap().into_any().unbind()
132    }
133}
134
135impl<'py> IntoPyObject<'py> for &PathBuf {
136    type Target = PyString;
137    type Output = Bound<'py, Self::Target>;
138    type Error = Infallible;
139
140    #[inline]
141    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
142        self.as_os_str().into_pyobject(py)
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use crate::types::{PyAnyMethods, PyString, PyStringMethods};
149    use crate::{BoundObject, IntoPyObject, Python};
150    use std::borrow::Cow;
151    use std::fmt::Debug;
152    use std::path::{Path, PathBuf};
153
154    #[test]
155    #[cfg(not(windows))]
156    fn test_non_utf8_conversion() {
157        Python::with_gil(|py| {
158            use std::ffi::OsStr;
159            #[cfg(not(target_os = "wasi"))]
160            use std::os::unix::ffi::OsStrExt;
161            #[cfg(target_os = "wasi")]
162            use std::os::wasi::ffi::OsStrExt;
163
164            // this is not valid UTF-8
165            let payload = &[250, 251, 252, 253, 254, 255, 0, 255];
166            let path = Path::new(OsStr::from_bytes(payload));
167
168            // do a roundtrip into Pythonland and back and compare
169            let py_str = path.into_pyobject(py).unwrap();
170            let path_2: PathBuf = py_str.extract().unwrap();
171            assert_eq!(path, path_2);
172        });
173    }
174
175    #[test]
176    fn test_intopyobject_roundtrip() {
177        Python::with_gil(|py| {
178            fn test_roundtrip<'py, T>(py: Python<'py>, obj: T)
179            where
180                T: IntoPyObject<'py> + AsRef<Path> + Debug + Clone,
181                T::Error: Debug,
182            {
183                let pyobject = obj.clone().into_pyobject(py).unwrap().into_any();
184                let pystring = pyobject.as_borrowed().downcast::<PyString>().unwrap();
185                assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy());
186                let roundtripped_obj: PathBuf = pystring.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}