Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/4925.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Convert `PathBuf` & `Path` into python `pathlib.Path` instead of `PyString`
8 changes: 4 additions & 4 deletions pytests/tests/test_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@

def test_make_path():
p = rpath.make_path()
assert p == "/root"
assert p == pathlib.Path("/root")


def test_take_pathbuf():
p = "/root"
assert rpath.take_pathbuf(p) == p
assert rpath.take_pathbuf(p) == pathlib.Path(p)


def test_take_pathlib():
p = pathlib.Path("/root")
assert rpath.take_pathbuf(p) == str(p)
assert rpath.take_pathbuf(p) == p


def test_take_pathlike():
assert rpath.take_pathbuf(PathLike("/root")) == "/root"
assert rpath.take_pathbuf(PathLike("/root")) == pathlib.Path("/root")


def test_take_invalid_pathlike():
Expand Down
96 changes: 65 additions & 31 deletions src/conversions/std/path.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
use crate::conversion::IntoPyObject;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::instance::Bound;
use crate::sync::GILOnceCell;
use crate::types::any::PyAnyMethods;
use crate::types::PyString;
use crate::{ffi, FromPyObject, PyAny, PyObject, PyResult, Python};
use crate::{ffi, FromPyObject, IntoPyObjectExt, PyAny, PyErr, PyObject, PyResult, Python};
#[allow(deprecated)]
use crate::{IntoPy, ToPyObject};
use std::borrow::Cow;
use std::convert::Infallible;
use std::ffi::OsString;
use std::path::{Path, PathBuf};

#[allow(deprecated)]
impl ToPyObject for Path {
#[inline]
fn to_object(&self, py: Python<'_>) -> PyObject {
self.into_pyobject(py).unwrap().into_any().unbind()
self.as_os_str().into_py_any(py).unwrap()
}
}

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

impl<'py> IntoPyObject<'py> for &Path {
type Target = PyString;
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
type Error = PyErr;

#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.as_os_str().into_pyobject(py)
static PY_PATH: GILOnceCell<PyObject> = GILOnceCell::new();
PY_PATH
.import(py, "pathlib", "Path")?
.call((self.as_os_str(),), None)
}
}

impl<'py> IntoPyObject<'py> for &&Path {
type Target = PyString;
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
type Error = PyErr;

#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Expand All @@ -63,90 +65,90 @@ impl<'py> IntoPyObject<'py> for &&Path {
impl ToPyObject for Cow<'_, Path> {
#[inline]
fn to_object(&self, py: Python<'_>) -> PyObject {
self.into_pyobject(py).unwrap().into_any().unbind()
(**self).to_object(py)
}
}

#[allow(deprecated)]
impl IntoPy<PyObject> for Cow<'_, Path> {
#[inline]
fn into_py(self, py: Python<'_>) -> PyObject {
self.into_pyobject(py).unwrap().into_any().unbind()
(*self).to_object(py)
}
}

impl<'py> IntoPyObject<'py> for Cow<'_, Path> {
type Target = PyString;
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
type Error = PyErr;

#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.as_os_str().into_pyobject(py)
(*self).into_pyobject(py)
}
}

impl<'py> IntoPyObject<'py> for &Cow<'_, Path> {
type Target = PyString;
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
type Error = PyErr;

#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.as_os_str().into_pyobject(py)
(&**self).into_pyobject(py)
}
}

#[allow(deprecated)]
impl ToPyObject for PathBuf {
#[inline]
fn to_object(&self, py: Python<'_>) -> PyObject {
self.into_pyobject(py).unwrap().into_any().unbind()
(**self).to_object(py)
}
}

#[allow(deprecated)]
impl IntoPy<PyObject> for PathBuf {
#[inline]
fn into_py(self, py: Python<'_>) -> PyObject {
self.into_pyobject(py).unwrap().into_any().unbind()
(*self).to_object(py)
}
}

impl<'py> IntoPyObject<'py> for PathBuf {
type Target = PyString;
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
type Error = PyErr;

#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.as_os_str().into_pyobject(py)
(&self).into_pyobject(py)
}
}

#[allow(deprecated)]
impl IntoPy<PyObject> for &PathBuf {
#[inline]
fn into_py(self, py: Python<'_>) -> PyObject {
self.into_pyobject(py).unwrap().into_any().unbind()
(**self).to_object(py)
}
}

impl<'py> IntoPyObject<'py> for &PathBuf {
type Target = PyString;
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
type Error = PyErr;

#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.as_os_str().into_pyobject(py)
(&**self).into_pyobject(py)
}
}

#[cfg(test)]
mod tests {
use crate::types::{PyAnyMethods, PyString, PyStringMethods};
use crate::{BoundObject, IntoPyObject, Python};
use crate::{IntoPyObject, IntoPyObjectExt, PyObject, Python};
use std::borrow::Cow;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -180,10 +182,42 @@ mod tests {
T: IntoPyObject<'py> + AsRef<Path> + Debug + Clone,
T::Error: Debug,
{
let pyobject = obj.clone().into_pyobject(py).unwrap().into_any();
let pystring = pyobject.as_borrowed().downcast::<PyString>().unwrap();
let pyobject = obj.clone().into_bound_py_any(py).unwrap();
let roundtripped_obj: PathBuf = pyobject.extract().unwrap();
assert_eq!(obj.as_ref(), roundtripped_obj.as_path());
}
let path = Path::new("Hello\0\n🐍");
test_roundtrip::<&Path>(py, path);
test_roundtrip::<Cow<'_, Path>>(py, Cow::Borrowed(path));
test_roundtrip::<Cow<'_, Path>>(py, Cow::Owned(path.to_path_buf()));
test_roundtrip::<PathBuf>(py, path.to_path_buf());
});
}

#[test]
fn test_from_pystring() {
Python::with_gil(|py| {
let path = "Hello\0\n🐍";
let pystring = PyString::new(py, path);
let roundtrip: PathBuf = pystring.extract().unwrap();
assert_eq!(roundtrip, Path::new(path));
});
}

#[test]
#[allow(deprecated)]
fn test_intopy_string() {
use crate::IntoPy;

Python::with_gil(|py| {
fn test_roundtrip<T>(py: Python<'_>, obj: T)
where
T: IntoPy<PyObject> + AsRef<Path> + Debug + Clone,
{
let pyobject = obj.clone().into_py(py).into_bound(py);
let pystring = pyobject.downcast_exact::<PyString>().unwrap();
assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy());
let roundtripped_obj: PathBuf = pystring.extract().unwrap();
let roundtripped_obj: PathBuf = pyobject.extract().unwrap();
assert_eq!(obj.as_ref(), roundtripped_obj.as_path());
}
let path = Path::new("Hello\0\n🐍");
Expand Down
2 changes: 1 addition & 1 deletion tests/test_datetime_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fn test_bad_datetime_module_panic() {
let sys = py.import("sys").unwrap();
sys.getattr("path")
.unwrap()
.call_method1("insert", (0, tmpdir.path()))
.call_method1("insert", (0, tmpdir.path().as_os_str()))
.unwrap();

// This should panic because the "datetime" module is empty
Expand Down