1 | use crate::{ |
2 | ffi, FromPyObject, FromPyPointer, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject, |
3 | }; |
4 | use std::borrow::Cow; |
5 | use std::ffi::OsString; |
6 | use std::path::{Path, PathBuf}; |
7 | |
8 | impl 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 | |
16 | impl 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 | |
24 | impl<'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 | |
31 | impl<'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 | |
38 | impl<'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 | |
45 | impl ToPyObject for PathBuf { |
46 | #[inline ] |
47 | fn to_object(&self, py: Python<'_>) -> PyObject { |
48 | self.as_os_str().to_object(py) |
49 | } |
50 | } |
51 | |
52 | impl IntoPy<PyObject> for PathBuf { |
53 | fn into_py(self, py: Python<'_>) -> PyObject { |
54 | self.into_os_string().to_object(py) |
55 | } |
56 | } |
57 | |
58 | impl<'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)] |
65 | mod 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 | |