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, PyAny, PyErr, PyObject, PyResult, Python};
7use std::borrow::Cow;
8use std::ffi::OsString;
9use std::path::{Path, PathBuf};
10
11// See osstr.rs for why there's no FromPyObject impl for &Path
12
13impl FromPyObject<'_> for PathBuf {
14    fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
15        // We use os.fspath to get the underlying path as bytes or str
16        let path = unsafe { ffi::PyOS_FSPath(ob.as_ptr()).assume_owned_or_err(ob.py())? };
17        Ok(path.extract::<OsString>()?.into())
18    }
19}
20
21impl<'py> IntoPyObject<'py> for &Path {
22    type Target = PyAny;
23    type Output = Bound<'py, Self::Target>;
24    type Error = PyErr;
25
26    #[inline]
27    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
28        static PY_PATH: GILOnceCell<PyObject> = GILOnceCell::new();
29        PY_PATH
30            .import(py, "pathlib", "Path")?
31            .call((self.as_os_str(),), None)
32    }
33}
34
35impl<'py> IntoPyObject<'py> for &&Path {
36    type Target = PyAny;
37    type Output = Bound<'py, Self::Target>;
38    type Error = PyErr;
39
40    #[inline]
41    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
42        (*self).into_pyobject(py)
43    }
44}
45
46impl<'py> IntoPyObject<'py> for Cow<'_, Path> {
47    type Target = PyAny;
48    type Output = Bound<'py, Self::Target>;
49    type Error = PyErr;
50
51    #[inline]
52    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
53        (*self).into_pyobject(py)
54    }
55}
56
57impl<'py> IntoPyObject<'py> for &Cow<'_, Path> {
58    type Target = PyAny;
59    type Output = Bound<'py, Self::Target>;
60    type Error = PyErr;
61
62    #[inline]
63    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
64        (&**self).into_pyobject(py)
65    }
66}
67
68impl<'py> IntoPyObject<'py> for PathBuf {
69    type Target = PyAny;
70    type Output = Bound<'py, Self::Target>;
71    type Error = PyErr;
72
73    #[inline]
74    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
75        (&self).into_pyobject(py)
76    }
77}
78
79impl<'py> IntoPyObject<'py> for &PathBuf {
80    type Target = PyAny;
81    type Output = Bound<'py, Self::Target>;
82    type Error = PyErr;
83
84    #[inline]
85    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
86        (&**self).into_pyobject(py)
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use crate::types::{PyAnyMethods, PyString};
93    use crate::{IntoPyObject, IntoPyObjectExt, Python};
94    use std::borrow::Cow;
95    use std::fmt::Debug;
96    use std::path::{Path, PathBuf};
97
98    #[test]
99    #[cfg(not(windows))]
100    fn test_non_utf8_conversion() {
101        Python::with_gil(|py| {
102            use std::ffi::OsStr;
103            #[cfg(not(target_os = "wasi"))]
104            use std::os::unix::ffi::OsStrExt;
105            #[cfg(target_os = "wasi")]
106            use std::os::wasi::ffi::OsStrExt;
107
108            // this is not valid UTF-8
109            let payload = &[250, 251, 252, 253, 254, 255, 0, 255];
110            let path = Path::new(OsStr::from_bytes(payload));
111
112            // do a roundtrip into Pythonland and back and compare
113            let py_str = path.into_pyobject(py).unwrap();
114            let path_2: PathBuf = py_str.extract().unwrap();
115            assert_eq!(path, path_2);
116        });
117    }
118
119    #[test]
120    fn test_intopyobject_roundtrip() {
121        Python::with_gil(|py| {
122            fn test_roundtrip<'py, T>(py: Python<'py>, obj: T)
123            where
124                T: IntoPyObject<'py> + AsRef<Path> + Debug + Clone,
125                T::Error: Debug,
126            {
127                let pyobject = obj.clone().into_bound_py_any(py).unwrap();
128                let roundtripped_obj: PathBuf = pyobject.extract().unwrap();
129                assert_eq!(obj.as_ref(), roundtripped_obj.as_path());
130            }
131            let path = Path::new("Hello\0\nšŸ");
132            test_roundtrip::<&Path>(py, path);
133            test_roundtrip::<Cow<'_, Path>>(py, Cow::Borrowed(path));
134            test_roundtrip::<Cow<'_, Path>>(py, Cow::Owned(path.to_path_buf()));
135            test_roundtrip::<PathBuf>(py, path.to_path_buf());
136        });
137    }
138
139    #[test]
140    fn test_from_pystring() {
141        Python::with_gil(|py| {
142            let path = "Hello\0\nšŸ";
143            let pystring = PyString::new(py, path);
144            let roundtrip: PathBuf = pystring.extract().unwrap();
145            assert_eq!(roundtrip, Path::new(path));
146        });
147    }
148}
āš ļø Internal Docs āš ļø Not Public API šŸ‘‰ Official Docs Here