From 18cfd6dfd1403be6be26a503852a6de5e7c2dda6 Mon Sep 17 00:00:00 2001 From: dswij Date: Thu, 4 Aug 2022 15:48:21 +0800 Subject: [PATCH 1/5] Expose `PyDict_GetItemWithError` on `PyDict` object --- src/types/dict.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/types/dict.rs b/src/types/dict.rs index bd75d89722b..104894b8f56 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -156,6 +156,26 @@ impl PyDict { } } + /// Gets an item from the dictionary, + /// + /// returns `Ok(None)` if item is not present, or `Err(PyErr)` if an error occurs. + /// + /// To get a `KeyError` for non-existing keys, use `PyAny::get_item_with_error`. + pub fn get_item_with_error(&self, key: K) -> PyResult> + where + K: ToPyObject, + { + unsafe { + let ptr = + ffi::PyDict_GetItemWithError(self.as_ptr(), key.to_object(self.py()).as_ptr()); + if !ffi::PyErr_Occurred().is_null() { + return Err(PyErr::fetch(self.py())); + } + + Ok(NonNull::new(ptr).map(|p| self.py().from_owned_ptr(ffi::_Py_NewRef(p.as_ptr())))) + } + } + /// Sets an item value. /// /// This is equivalent to the Python statement `self[key] = value`. @@ -471,6 +491,7 @@ where #[cfg(test)] mod tests { use super::*; + use crate::exceptions; #[cfg(not(PyPy))] use crate::{types::PyList, PyTypeInfo}; use crate::{types::PyTuple, IntoPy, PyObject, PyTryFrom, Python, ToPyObject}; @@ -562,6 +583,32 @@ mod tests { }); } + #[test] + fn test_get_item_with_error() { + Python::with_gil(|py| { + let mut v = HashMap::new(); + v.insert(7, 32); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_ref(py)).unwrap(); + assert_eq!( + 32, + dict.get_item_with_error(7i32) + .unwrap() + .unwrap() + .extract::() + .unwrap() + ); + assert!(dict.get_item_with_error(8i32).unwrap().is_none()); + + let dict_clone = dict.clone(); + if let Err(err) = dict.get_item_with_error(dict_clone) { + assert!(err.is_instance_of::(py)); + } else { + panic!() + }; + }); + } + #[test] fn test_set_item() { Python::with_gil(|py| { From 80ce332ee20ef0f99fe3223c6b5b481e22e218c7 Mon Sep 17 00:00:00 2001 From: dswij Date: Fri, 5 Aug 2022 14:14:47 +0800 Subject: [PATCH 2/5] Expose only on non-pypy --- src/types/dict.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/types/dict.rs b/src/types/dict.rs index 104894b8f56..a96f37bb850 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -161,6 +161,7 @@ impl PyDict { /// returns `Ok(None)` if item is not present, or `Err(PyErr)` if an error occurs. /// /// To get a `KeyError` for non-existing keys, use `PyAny::get_item_with_error`. + #[cfg(not(PyPy))] pub fn get_item_with_error(&self, key: K) -> PyResult> where K: ToPyObject, @@ -584,6 +585,7 @@ mod tests { } #[test] + #[cfg(not(PyPy))] fn test_get_item_with_error() { Python::with_gil(|py| { let mut v = HashMap::new(); @@ -600,8 +602,7 @@ mod tests { ); assert!(dict.get_item_with_error(8i32).unwrap().is_none()); - let dict_clone = dict.clone(); - if let Err(err) = dict.get_item_with_error(dict_clone) { + if let Err(err) = dict.get_item_with_error(dict) { assert!(err.is_instance_of::(py)); } else { panic!() From 3f1df62557a926b8d7b54e6f101319d806c2310c Mon Sep 17 00:00:00 2001 From: dswij Date: Fri, 5 Aug 2022 14:17:01 +0800 Subject: [PATCH 3/5] use `unwrap_err` on `GetItemWithError` test --- src/types/dict.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/types/dict.rs b/src/types/dict.rs index a96f37bb850..187dcb44bc0 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -601,12 +601,10 @@ mod tests { .unwrap() ); assert!(dict.get_item_with_error(8i32).unwrap().is_none()); - - if let Err(err) = dict.get_item_with_error(dict) { - assert!(err.is_instance_of::(py)); - } else { - panic!() - }; + assert!(dict + .get_item_with_error(dict) + .unwrap_err() + .is_instance_of::(py)); }); } From ff53193066df5a0effc0d03c39d81954b0d57fe9 Mon Sep 17 00:00:00 2001 From: dswij Date: Fri, 5 Aug 2022 14:22:51 +0800 Subject: [PATCH 4/5] Add changes info to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42fb3c37640..6de60e174ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `PySuper` object [#2486](https://github.com/PyO3/pyo3/pull/2486) - Add support for generating PyPy Windows import library. [#2506](https://github.com/PyO3/pyo3/pull/2506) - Add FFI definitions for `Py_EnterRecursiveCall` and `Py_LeaveRecursiveCall`. [#2511](https://github.com/PyO3/pyo3/pull/2511) +- Add `get_item_with_error` on `PyDict` that exposes `PyDict_GetItemWIthError` for non-PyPy. [#2536](https://github.com/PyO3/pyo3/pull/2536) ### Changed From df1c280bff6c1a146e9fc7cc92c4fab5593a3aff Mon Sep 17 00:00:00 2001 From: dswij Date: Sat, 6 Aug 2022 22:38:23 +0800 Subject: [PATCH 5/5] Ignore import for pypy ignored test --- src/types/dict.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/dict.rs b/src/types/dict.rs index 187dcb44bc0..e8fc6f65c02 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -492,6 +492,7 @@ where #[cfg(test)] mod tests { use super::*; + #[cfg(not(PyPy))] use crate::exceptions; #[cfg(not(PyPy))] use crate::{types::PyList, PyTypeInfo};