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 diff --git a/src/types/dict.rs b/src/types/dict.rs index bd75d89722b..e8fc6f65c02 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -156,6 +156,27 @@ 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`. + #[cfg(not(PyPy))] + 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`. @@ -472,6 +493,8 @@ where mod tests { use super::*; #[cfg(not(PyPy))] + use crate::exceptions; + #[cfg(not(PyPy))] use crate::{types::PyList, PyTypeInfo}; use crate::{types::PyTuple, IntoPy, PyObject, PyTryFrom, Python, ToPyObject}; use std::collections::{BTreeMap, HashMap}; @@ -562,6 +585,30 @@ mod tests { }); } + #[test] + #[cfg(not(PyPy))] + 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()); + assert!(dict + .get_item_with_error(dict) + .unwrap_err() + .is_instance_of::(py)); + }); + } + #[test] fn test_set_item() { Python::with_gil(|py| {