From 13c4ece749e6a86f3399c0e4a133e1e8add00e89 Mon Sep 17 00:00:00 2001 From: messense Date: Sat, 5 Jun 2021 14:18:22 +0800 Subject: [PATCH] Add support for extracting PathBuf from pathlib.Path --- CHANGELOG.md | 1 + examples/pyo3-pytests/src/lib.rs | 4 ++++ examples/pyo3-pytests/src/path.rs | 21 +++++++++++++++++++++ examples/pyo3-pytests/tests/test_path.py | 18 ++++++++++++++++++ src/conversions/path.rs | 19 +++++++++++++++++-- 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 examples/pyo3-pytests/src/path.rs create mode 100644 examples/pyo3-pytests/tests/test_path.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 73f76289010..a7c43e8884d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add `#[pyo3(name = "...")]` syntax for setting Python names. [#1567](https://github.com/PyO3/pyo3/pull/1567) - Add FFI definition `PyDateTime_TimeZone_UTC`. [#1572](https://github.com/PyO3/pyo3/pull/1572) - Add support for `#[pyclass(extends=Exception)]`. [#1591](https://github.com/PyO3/pyo3/pull/1591) +- Add support for extracting `PathBuf` from `pathlib.Path`. [#1654](https://github.com/PyO3/pyo3/pull/1654) ### Changed - Allow only one `#[pymethods]` block per `#[pyclass]` by default, to simplify the proc macro implementations. Add `multiple-pymethods` feature to opt-in to the more complex full behavior. [#1457](https://github.com/PyO3/pyo3/pull/1457) diff --git a/examples/pyo3-pytests/src/lib.rs b/examples/pyo3-pytests/src/lib.rs index 0dea9c03679..ed0b818038d 100644 --- a/examples/pyo3-pytests/src/lib.rs +++ b/examples/pyo3-pytests/src/lib.rs @@ -8,6 +8,7 @@ pub mod dict_iter; pub mod misc; pub mod objstore; pub mod othermod; +pub mod path; pub mod pyclass_iter; pub mod subclassing; @@ -17,6 +18,7 @@ use dict_iter::*; use misc::*; use objstore::*; use othermod::*; +use path::*; use pyclass_iter::*; use subclassing::*; @@ -28,6 +30,7 @@ fn pyo3_pytests(py: Python, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(misc))?; m.add_wrapped(wrap_pymodule!(objstore))?; m.add_wrapped(wrap_pymodule!(othermod))?; + m.add_wrapped(wrap_pymodule!(path))?; m.add_wrapped(wrap_pymodule!(pyclass_iter))?; m.add_wrapped(wrap_pymodule!(subclassing))?; @@ -42,6 +45,7 @@ fn pyo3_pytests(py: Python, m: &PyModule) -> PyResult<()> { sys_modules.set_item("pyo3_pytests.misc", m.getattr("misc")?)?; sys_modules.set_item("pyo3_pytests.objstore", m.getattr("objstore")?)?; sys_modules.set_item("pyo3_pytests.othermod", m.getattr("othermod")?)?; + sys_modules.set_item("pyo3_pytests.path", m.getattr("path")?)?; sys_modules.set_item("pyo3_pytests.pyclass_iter", m.getattr("pyclass_iter")?)?; sys_modules.set_item("pyo3_pytests.subclassing", m.getattr("subclassing")?)?; diff --git a/examples/pyo3-pytests/src/path.rs b/examples/pyo3-pytests/src/path.rs new file mode 100644 index 00000000000..d3214aa0c91 --- /dev/null +++ b/examples/pyo3-pytests/src/path.rs @@ -0,0 +1,21 @@ +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; +use std::path::{Path, PathBuf}; + +#[pyfunction] +fn make_path() -> PathBuf { + Path::new("/root").to_owned() +} + +#[pyfunction] +fn take_pathbuf(path: PathBuf) -> PathBuf { + path +} + +#[pymodule] +fn path(_py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(make_path, m)?)?; + m.add_function(wrap_pyfunction!(take_pathbuf, m)?)?; + + Ok(()) +} diff --git a/examples/pyo3-pytests/tests/test_path.py b/examples/pyo3-pytests/tests/test_path.py new file mode 100644 index 00000000000..07c4cb3504f --- /dev/null +++ b/examples/pyo3-pytests/tests/test_path.py @@ -0,0 +1,18 @@ +import pathlib + +import pyo3_pytests.path as rpath + + +def test_make_path(): + p = rpath.make_path() + assert p == "/root" + + +def test_take_pathbuf(): + p = "/root" + assert rpath.take_pathbuf(p) == p + + +def test_take_pathlib(): + p = pathlib.Path("/root") + assert rpath.take_pathbuf(p) == str(p) diff --git a/src/conversions/path.rs b/src/conversions/path.rs index a08345b9b26..5b1be28b0ff 100644 --- a/src/conversions/path.rs +++ b/src/conversions/path.rs @@ -1,4 +1,5 @@ -use crate::{FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject}; +use crate::types::PyType; +use crate::{FromPyObject, IntoPy, PyAny, PyNativeType, PyObject, PyResult, Python, ToPyObject}; use std::borrow::Cow; use std::ffi::OsString; use std::path::{Path, PathBuf}; @@ -13,7 +14,21 @@ impl ToPyObject for Path { impl FromPyObject<'_> for PathBuf { fn extract(ob: &PyAny) -> PyResult { - Ok(PathBuf::from(OsString::extract(ob)?)) + let os_str = match OsString::extract(ob) { + Ok(s) => s, + Err(err) => { + let py = ob.py(); + let pathlib = py.import("pathlib")?; + let pathlib_path: &PyType = pathlib.getattr("Path")?.downcast()?; + if pathlib_path.is_instance(ob)? { + let path_str = ob.call_method0("__str__")?; + OsString::extract(path_str)? + } else { + return Err(err.into()); + } + } + }; + Ok(PathBuf::from(os_str)) } }