1 | use crate::conversion::IntoPyObject; |
2 | use crate::ffi_ptr_ext::FfiPtrExt; |
3 | use crate::instance::Bound; |
4 | use crate::sync::GILOnceCell; |
5 | use crate::types::any::PyAnyMethods; |
6 | use crate::{ffi, FromPyObject, IntoPyObjectExt, PyAny, PyErr, PyObject, PyResult, Python}; |
7 | #[allow (deprecated)] |
8 | use crate::{IntoPy, ToPyObject}; |
9 | use std::borrow::Cow; |
10 | use std::ffi::OsString; |
11 | use std::path::{Path, PathBuf}; |
12 | |
13 | #[allow (deprecated)] |
14 | impl 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 | |
23 | impl 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: Bound<'_, PyAny> = unsafe { ffi::PyOS_FSPath(path:ob.as_ptr()).assume_owned_or_err(ob.py())? }; |
27 | Ok(path.extract::<OsString>()?.into()) |
28 | } |
29 | } |
30 | |
31 | #[allow (deprecated)] |
32 | impl IntoPy<PyObject> for &Path { |
33 | #[inline ] |
34 | fn into_py(self, py: Python<'_>) -> PyObject { |
35 | self.to_object(py) |
36 | } |
37 | } |
38 | |
39 | impl<'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(),), kwargs:None) |
50 | } |
51 | } |
52 | |
53 | impl<'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)] |
65 | impl 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)] |
73 | impl IntoPy<PyObject> for Cow<'_, Path> { |
74 | #[inline ] |
75 | fn into_py(self, py: Python<'_>) -> PyObject { |
76 | (*self).to_object(py) |
77 | } |
78 | } |
79 | |
80 | impl<'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 | |
91 | impl<'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)] |
103 | impl ToPyObject for PathBuf { |
104 | #[inline ] |
105 | fn to_object(&self, py: Python<'_>) -> PyObject { |
106 | (**self).to_object(py) |
107 | } |
108 | } |
109 | |
110 | #[allow (deprecated)] |
111 | impl IntoPy<PyObject> for PathBuf { |
112 | #[inline ] |
113 | fn into_py(self, py: Python<'_>) -> PyObject { |
114 | (*self).to_object(py) |
115 | } |
116 | } |
117 | |
118 | impl<'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)] |
130 | impl IntoPy<PyObject> for &PathBuf { |
131 | #[inline ] |
132 | fn into_py(self, py: Python<'_>) -> PyObject { |
133 | (**self).to_object(py) |
134 | } |
135 | } |
136 | |
137 | impl<'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)] |
149 | mod 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 | } |
231 | |