Skip to content

Commit 480fe7e

Browse files
authored
Expose PyDict_GetItemWithError on PyDict object (#2536)
* Expose `PyDict_GetItemWithError` on `PyDict` object * Expose only on non-pypy * use `unwrap_err` on `GetItemWithError` test * Add changes info to changelog * Ignore import for pypy ignored test
1 parent 1b7a850 commit 480fe7e

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
- Add `PySuper` object [#2486](https://github.com/PyO3/pyo3/pull/2486)
2929
- Add support for generating PyPy Windows import library. [#2506](https://github.com/PyO3/pyo3/pull/2506)
3030
- Add FFI definitions for `Py_EnterRecursiveCall` and `Py_LeaveRecursiveCall`. [#2511](https://github.com/PyO3/pyo3/pull/2511)
31+
- Add `get_item_with_error` on `PyDict` that exposes `PyDict_GetItemWIthError` for non-PyPy. [#2536](https://github.com/PyO3/pyo3/pull/2536)
3132

3233
### Changed
3334

src/types/dict.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,27 @@ impl PyDict {
156156
}
157157
}
158158

159+
/// Gets an item from the dictionary,
160+
///
161+
/// returns `Ok(None)` if item is not present, or `Err(PyErr)` if an error occurs.
162+
///
163+
/// To get a `KeyError` for non-existing keys, use `PyAny::get_item_with_error`.
164+
#[cfg(not(PyPy))]
165+
pub fn get_item_with_error<K>(&self, key: K) -> PyResult<Option<&PyAny>>
166+
where
167+
K: ToPyObject,
168+
{
169+
unsafe {
170+
let ptr =
171+
ffi::PyDict_GetItemWithError(self.as_ptr(), key.to_object(self.py()).as_ptr());
172+
if !ffi::PyErr_Occurred().is_null() {
173+
return Err(PyErr::fetch(self.py()));
174+
}
175+
176+
Ok(NonNull::new(ptr).map(|p| self.py().from_owned_ptr(ffi::_Py_NewRef(p.as_ptr()))))
177+
}
178+
}
179+
159180
/// Sets an item value.
160181
///
161182
/// This is equivalent to the Python statement `self[key] = value`.
@@ -472,6 +493,8 @@ where
472493
mod tests {
473494
use super::*;
474495
#[cfg(not(PyPy))]
496+
use crate::exceptions;
497+
#[cfg(not(PyPy))]
475498
use crate::{types::PyList, PyTypeInfo};
476499
use crate::{types::PyTuple, IntoPy, PyObject, PyTryFrom, Python, ToPyObject};
477500
use std::collections::{BTreeMap, HashMap};
@@ -562,6 +585,30 @@ mod tests {
562585
});
563586
}
564587

588+
#[test]
589+
#[cfg(not(PyPy))]
590+
fn test_get_item_with_error() {
591+
Python::with_gil(|py| {
592+
let mut v = HashMap::new();
593+
v.insert(7, 32);
594+
let ob = v.to_object(py);
595+
let dict = <PyDict as PyTryFrom>::try_from(ob.as_ref(py)).unwrap();
596+
assert_eq!(
597+
32,
598+
dict.get_item_with_error(7i32)
599+
.unwrap()
600+
.unwrap()
601+
.extract::<i32>()
602+
.unwrap()
603+
);
604+
assert!(dict.get_item_with_error(8i32).unwrap().is_none());
605+
assert!(dict
606+
.get_item_with_error(dict)
607+
.unwrap_err()
608+
.is_instance_of::<exceptions::PyTypeError>(py));
609+
});
610+
}
611+
565612
#[test]
566613
fn test_set_item() {
567614
Python::with_gil(|py| {

0 commit comments

Comments
 (0)