Skip to content

Commit

Permalink
Merge pull request #3374 from Tpt/pathlike
Browse files Browse the repository at this point in the history
Makes PathBuf FromPyObject implementation work on all os.PathLike
  • Loading branch information
adamreichold authored Aug 9, 2023
2 parents bab7ff0 + dd04d2c commit 1df7270
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 17 deletions.
1 change: 1 addition & 0 deletions guide/src/conversions/tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The table below contains the Python type and the corresponding function argument
| `decimal.Decimal` | `rust_decimal::Decimal`[^5] | - |
| `ipaddress.IPv4Address` | `std::net::IpAddr`, `std::net::IpV4Addr` | - |
| `ipaddress.IPv6Address` | `std::net::IpAddr`, `std::net::IpV6Addr` | - |
| `os.PathLike ` | `PathBuf`, `Path` | `&PyString`, `&PyUnicode` |
| `pathlib.Path` | `PathBuf`, `Path` | `&PyString`, `&PyUnicode` |
| `typing.Optional[T]` | `Option<T>` | - |
| `typing.Sequence[T]` | `Vec<T>` | `&PySequence` |
Expand Down
1 change: 1 addition & 0 deletions newsfragments/3374.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`PathBuf` `FromPyObject` implementation now works on all `os.PathLike` values.
1 change: 1 addition & 0 deletions newsfragments/3374.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Linking of `PyOS_FSPath` on PyPy.
1 change: 1 addition & 0 deletions pyo3-ffi/src/osmodule.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::object::PyObject;

extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyOS_FSPath")]
pub fn PyOS_FSPath(path: *mut PyObject) -> *mut PyObject;
}
24 changes: 24 additions & 0 deletions pytests/tests/test_path.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pathlib

import pytest

import pyo3_pytests.path as rpath


Expand All @@ -16,3 +18,25 @@ def test_take_pathbuf():
def test_take_pathlib():
p = pathlib.Path("/root")
assert rpath.take_pathbuf(p) == str(p)


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


def test_take_invalid_pathlike():
with pytest.raises(TypeError):
assert rpath.take_pathbuf(PathLike(1))


def test_take_invalid():
with pytest.raises(TypeError):
assert rpath.take_pathbuf(3)


class PathLike:
def __init__(self, path):
self._path = path

def __fspath__(self):
return self._path
24 changes: 7 additions & 17 deletions src/conversions/std/path.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::intern;
use crate::{FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject};
use crate::{
ffi, AsPyPointer, FromPyObject, FromPyPointer, IntoPy, PyAny, PyObject, PyResult, Python,
ToPyObject,
};
use std::borrow::Cow;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
Expand All @@ -14,21 +16,9 @@ impl ToPyObject for Path {

impl FromPyObject<'_> for PathBuf {
fn extract(ob: &PyAny) -> PyResult<Self> {
let os_str = match OsString::extract(ob) {
Ok(s) => s,
Err(err) => {
let py = ob.py();
let pathlib = py.import(intern!(py, "pathlib"))?;
let pathlib_path = pathlib.getattr(intern!(py, "Path"))?;
if ob.is_instance(pathlib_path)? {
let path_str = ob.call_method0(intern!(py, "__str__"))?;
OsString::extract(path_str)?
} else {
return Err(err);
}
}
};
Ok(PathBuf::from(os_str))
// We use os.fspath to get the underlying path as bytes or str
let path = unsafe { PyAny::from_owned_ptr_or_err(ob.py(), ffi::PyOS_FSPath(ob.as_ptr())) }?;
Ok(OsString::extract(path)?.into())
}
}

Expand Down

0 comments on commit 1df7270

Please sign in to comment.