Skip to content

Commit ffd7276

Browse files
Convert PathBuf & Path into python pathlib.Path instead of PyString (#4925)
* Convert `PathBuf` into python `pathlib.Path` * Make sure `ToPyObject` & `IntoPy` convert to PyString * Add from pystring test
1 parent b07871d commit ffd7276

File tree

4 files changed

+71
-36
lines changed

4 files changed

+71
-36
lines changed

newsfragments/4925.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Convert `PathBuf` & `Path` into python `pathlib.Path` instead of `PyString`

pytests/tests/test_path.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@
77

88
def test_make_path():
99
p = rpath.make_path()
10-
assert p == "/root"
10+
assert p == pathlib.Path("/root")
1111

1212

1313
def test_take_pathbuf():
1414
p = "/root"
15-
assert rpath.take_pathbuf(p) == p
15+
assert rpath.take_pathbuf(p) == pathlib.Path(p)
1616

1717

1818
def test_take_pathlib():
1919
p = pathlib.Path("/root")
20-
assert rpath.take_pathbuf(p) == str(p)
20+
assert rpath.take_pathbuf(p) == p
2121

2222

2323
def test_take_pathlike():
24-
assert rpath.take_pathbuf(PathLike("/root")) == "/root"
24+
assert rpath.take_pathbuf(PathLike("/root")) == pathlib.Path("/root")
2525

2626

2727
def test_take_invalid_pathlike():

src/conversions/std/path.rs

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
use crate::conversion::IntoPyObject;
22
use crate::ffi_ptr_ext::FfiPtrExt;
33
use crate::instance::Bound;
4+
use crate::sync::GILOnceCell;
45
use crate::types::any::PyAnyMethods;
5-
use crate::types::PyString;
6-
use crate::{ffi, FromPyObject, PyAny, PyObject, PyResult, Python};
6+
use crate::{ffi, FromPyObject, IntoPyObjectExt, PyAny, PyErr, PyObject, PyResult, Python};
77
#[allow(deprecated)]
88
use crate::{IntoPy, ToPyObject};
99
use std::borrow::Cow;
10-
use std::convert::Infallible;
1110
use std::ffi::OsString;
1211
use std::path::{Path, PathBuf};
1312

1413
#[allow(deprecated)]
1514
impl ToPyObject for Path {
1615
#[inline]
1716
fn to_object(&self, py: Python<'_>) -> PyObject {
18-
self.into_pyobject(py).unwrap().into_any().unbind()
17+
self.as_os_str().into_py_any(py).unwrap()
1918
}
2019
}
2120

@@ -33,25 +32,28 @@ impl FromPyObject<'_> for PathBuf {
3332
impl IntoPy<PyObject> for &Path {
3433
#[inline]
3534
fn into_py(self, py: Python<'_>) -> PyObject {
36-
self.into_pyobject(py).unwrap().into_any().unbind()
35+
self.to_object(py)
3736
}
3837
}
3938

4039
impl<'py> IntoPyObject<'py> for &Path {
41-
type Target = PyString;
40+
type Target = PyAny;
4241
type Output = Bound<'py, Self::Target>;
43-
type Error = Infallible;
42+
type Error = PyErr;
4443

4544
#[inline]
4645
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
47-
self.as_os_str().into_pyobject(py)
46+
static PY_PATH: GILOnceCell<PyObject> = GILOnceCell::new();
47+
PY_PATH
48+
.import(py, "pathlib", "Path")?
49+
.call((self.as_os_str(),), None)
4850
}
4951
}
5052

5153
impl<'py> IntoPyObject<'py> for &&Path {
52-
type Target = PyString;
54+
type Target = PyAny;
5355
type Output = Bound<'py, Self::Target>;
54-
type Error = Infallible;
56+
type Error = PyErr;
5557

5658
#[inline]
5759
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
@@ -63,90 +65,90 @@ impl<'py> IntoPyObject<'py> for &&Path {
6365
impl ToPyObject for Cow<'_, Path> {
6466
#[inline]
6567
fn to_object(&self, py: Python<'_>) -> PyObject {
66-
self.into_pyobject(py).unwrap().into_any().unbind()
68+
(**self).to_object(py)
6769
}
6870
}
6971

7072
#[allow(deprecated)]
7173
impl IntoPy<PyObject> for Cow<'_, Path> {
7274
#[inline]
7375
fn into_py(self, py: Python<'_>) -> PyObject {
74-
self.into_pyobject(py).unwrap().into_any().unbind()
76+
(*self).to_object(py)
7577
}
7678
}
7779

7880
impl<'py> IntoPyObject<'py> for Cow<'_, Path> {
79-
type Target = PyString;
81+
type Target = PyAny;
8082
type Output = Bound<'py, Self::Target>;
81-
type Error = Infallible;
83+
type Error = PyErr;
8284

8385
#[inline]
8486
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
85-
self.as_os_str().into_pyobject(py)
87+
(*self).into_pyobject(py)
8688
}
8789
}
8890

8991
impl<'py> IntoPyObject<'py> for &Cow<'_, Path> {
90-
type Target = PyString;
92+
type Target = PyAny;
9193
type Output = Bound<'py, Self::Target>;
92-
type Error = Infallible;
94+
type Error = PyErr;
9395

9496
#[inline]
9597
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
96-
self.as_os_str().into_pyobject(py)
98+
(&**self).into_pyobject(py)
9799
}
98100
}
99101

100102
#[allow(deprecated)]
101103
impl ToPyObject for PathBuf {
102104
#[inline]
103105
fn to_object(&self, py: Python<'_>) -> PyObject {
104-
self.into_pyobject(py).unwrap().into_any().unbind()
106+
(**self).to_object(py)
105107
}
106108
}
107109

108110
#[allow(deprecated)]
109111
impl IntoPy<PyObject> for PathBuf {
110112
#[inline]
111113
fn into_py(self, py: Python<'_>) -> PyObject {
112-
self.into_pyobject(py).unwrap().into_any().unbind()
114+
(*self).to_object(py)
113115
}
114116
}
115117

116118
impl<'py> IntoPyObject<'py> for PathBuf {
117-
type Target = PyString;
119+
type Target = PyAny;
118120
type Output = Bound<'py, Self::Target>;
119-
type Error = Infallible;
121+
type Error = PyErr;
120122

121123
#[inline]
122124
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
123-
self.as_os_str().into_pyobject(py)
125+
(&self).into_pyobject(py)
124126
}
125127
}
126128

127129
#[allow(deprecated)]
128130
impl IntoPy<PyObject> for &PathBuf {
129131
#[inline]
130132
fn into_py(self, py: Python<'_>) -> PyObject {
131-
self.into_pyobject(py).unwrap().into_any().unbind()
133+
(**self).to_object(py)
132134
}
133135
}
134136

135137
impl<'py> IntoPyObject<'py> for &PathBuf {
136-
type Target = PyString;
138+
type Target = PyAny;
137139
type Output = Bound<'py, Self::Target>;
138-
type Error = Infallible;
140+
type Error = PyErr;
139141

140142
#[inline]
141143
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
142-
self.as_os_str().into_pyobject(py)
144+
(&**self).into_pyobject(py)
143145
}
144146
}
145147

146148
#[cfg(test)]
147149
mod tests {
148150
use crate::types::{PyAnyMethods, PyString, PyStringMethods};
149-
use crate::{BoundObject, IntoPyObject, Python};
151+
use crate::{IntoPyObject, IntoPyObjectExt, PyObject, Python};
150152
use std::borrow::Cow;
151153
use std::fmt::Debug;
152154
use std::path::{Path, PathBuf};
@@ -180,10 +182,42 @@ mod tests {
180182
T: IntoPyObject<'py> + AsRef<Path> + Debug + Clone,
181183
T::Error: Debug,
182184
{
183-
let pyobject = obj.clone().into_pyobject(py).unwrap().into_any();
184-
let pystring = pyobject.as_borrowed().downcast::<PyString>().unwrap();
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();
185219
assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy());
186-
let roundtripped_obj: PathBuf = pystring.extract().unwrap();
220+
let roundtripped_obj: PathBuf = pyobject.extract().unwrap();
187221
assert_eq!(obj.as_ref(), roundtripped_obj.as_path());
188222
}
189223
let path = Path::new("Hello\0\n🐍");

tests/test_datetime_import.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ fn test_bad_datetime_module_panic() {
1818
let sys = py.import("sys").unwrap();
1919
sys.getattr("path")
2020
.unwrap()
21-
.call_method1("insert", (0, tmpdir.path()))
21+
.call_method1("insert", (0, tmpdir.path().as_os_str()))
2222
.unwrap();
2323

2424
// This should panic because the "datetime" module is empty

0 commit comments

Comments
 (0)