1use crate::{
2 ffi, FromPyObject, FromPyPointer, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject,
3};
4use std::borrow::Cow;
5use std::ffi::OsString;
6use std::path::{Path, PathBuf};
7
8impl ToPyObject for Path {
9 fn to_object(&self, py: Python<'_>) -> PyObject {
10 self.as_os_str().to_object(py)
11 }
12}
13
14// See osstr.rs for why there's no FromPyObject impl for &Path
15
16impl FromPyObject<'_> for PathBuf {
17 fn extract(ob: &PyAny) -> PyResult<Self> {
18 // We use os.fspath to get the underlying path as bytes or str
19 let path: &PyAny = unsafe { PyAny::from_owned_ptr_or_err(ob.py(), ptr:ffi::PyOS_FSPath(path:ob.as_ptr())) }?;
20 Ok(OsString::extract(ob:path)?.into())
21 }
22}
23
24impl<'a> IntoPy<PyObject> for &'a Path {
25 #[inline]
26 fn into_py(self, py: Python<'_>) -> PyObject {
27 self.as_os_str().to_object(py)
28 }
29}
30
31impl<'a> ToPyObject for Cow<'a, Path> {
32 #[inline]
33 fn to_object(&self, py: Python<'_>) -> PyObject {
34 self.as_os_str().to_object(py)
35 }
36}
37
38impl<'a> IntoPy<PyObject> for Cow<'a, Path> {
39 #[inline]
40 fn into_py(self, py: Python<'_>) -> PyObject {
41 self.to_object(py)
42 }
43}
44
45impl ToPyObject for PathBuf {
46 #[inline]
47 fn to_object(&self, py: Python<'_>) -> PyObject {
48 self.as_os_str().to_object(py)
49 }
50}
51
52impl IntoPy<PyObject> for PathBuf {
53 fn into_py(self, py: Python<'_>) -> PyObject {
54 self.into_os_string().to_object(py)
55 }
56}
57
58impl<'a> IntoPy<PyObject> for &'a PathBuf {
59 fn into_py(self, py: Python<'_>) -> PyObject {
60 self.as_os_str().to_object(py)
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use crate::{types::PyString, IntoPy, PyObject, Python, ToPyObject};
67 use std::borrow::Cow;
68 use std::fmt::Debug;
69 use std::path::{Path, PathBuf};
70
71 #[test]
72 #[cfg(not(windows))]
73 fn test_non_utf8_conversion() {
74 Python::with_gil(|py| {
75 use std::ffi::OsStr;
76 #[cfg(not(target_os = "wasi"))]
77 use std::os::unix::ffi::OsStrExt;
78 #[cfg(target_os = "wasi")]
79 use std::os::wasi::ffi::OsStrExt;
80
81 // this is not valid UTF-8
82 let payload = &[250, 251, 252, 253, 254, 255, 0, 255];
83 let path = Path::new(OsStr::from_bytes(payload));
84
85 // do a roundtrip into Pythonland and back and compare
86 let py_str: PyObject = path.into_py(py);
87 let path_2: PathBuf = py_str.extract(py).unwrap();
88 assert_eq!(path, path_2);
89 });
90 }
91
92 #[test]
93 fn test_topyobject_roundtrip() {
94 Python::with_gil(|py| {
95 fn test_roundtrip<T: ToPyObject + AsRef<Path> + Debug>(py: Python<'_>, obj: T) {
96 let pyobject = obj.to_object(py);
97 let pystring: &PyString = pyobject.extract(py).unwrap();
98 assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy());
99 let roundtripped_obj: PathBuf = pystring.extract().unwrap();
100 assert_eq!(obj.as_ref(), roundtripped_obj.as_path());
101 }
102 let path = Path::new("Hello\0\n🐍");
103 test_roundtrip::<&Path>(py, path);
104 test_roundtrip::<Cow<'_, Path>>(py, Cow::Borrowed(path));
105 test_roundtrip::<Cow<'_, Path>>(py, Cow::Owned(path.to_path_buf()));
106 test_roundtrip::<PathBuf>(py, path.to_path_buf());
107 });
108 }
109
110 #[test]
111 fn test_intopy_roundtrip() {
112 Python::with_gil(|py| {
113 fn test_roundtrip<T: IntoPy<PyObject> + AsRef<Path> + Debug + Clone>(
114 py: Python<'_>,
115 obj: T,
116 ) {
117 let pyobject = obj.clone().into_py(py);
118 let pystring: &PyString = pyobject.extract(py).unwrap();
119 assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy());
120 let roundtripped_obj: PathBuf = pystring.extract().unwrap();
121 assert_eq!(obj.as_ref(), roundtripped_obj.as_path());
122 }
123 let path = Path::new("Hello\0\n🐍");
124 test_roundtrip::<&Path>(py, path);
125 test_roundtrip::<PathBuf>(py, path.to_path_buf());
126 test_roundtrip::<&PathBuf>(py, &path.to_path_buf());
127 })
128 }
129}
130