From c7f834ad87bf9eead0f8e670019f5d019cae4c9b Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sat, 9 Sep 2023 13:05:51 +0100 Subject: [PATCH] add migration guide entry for `PyDict::get_item` --- guide/src/migration.md | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/guide/src/migration.md b/guide/src/migration.md index f22b1481..ec6c6c84 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -9,6 +9,56 @@ For a detailed list of all changes, see the [CHANGELOG](changelog.md). PyO3 0.20 has increased minimum Rust version to 1.56. This enables use of newer language features and simplifies maintenance of the project. +### `PyDict::get_item` now returns a `Result` + +`PyDict::get_item` in PyO3 0.19 and older was implemented using a Python API which would suppress all exceptions and return `None` in those cases. This included errors in `__hash__` and `__eq__` implementations of the key being looked up. + +Newer recommendations by the Python core developers advise against using these APIs which suppress exceptions, instead allowing exceptions to bubble upwards. `PyDict::get_item_with_error` already implemented this recommended behavior, so that API has been renamed to `PyDict::get_item`. + +Before: + +```rust,ignore +use pyo3::prelude::*; +use pyo3::exceptions::PyTypeError; +use pyo3::types::{PyDict, IntoPyDict}; + +# fn main() { +# let _ = +Python::with_gil(|py| { + let dict: &PyDict = [("a", 1)].into_py_dict(py); + // `a` is in the dictionary, with value 1 + assert!(dict.get_item("a").map_or(Ok(false), |x| x.eq(1))?); + // `b` is not in the dictionary + assert!(dict.get_item("b").is_none()); + // `dict` is not hashable, so this fails with a `TypeError` + assert!(dict.get_item_with_error(dict).unwrap_err().is_instance_of::(py)); +}); +# } +``` + +After: + +```rust +use pyo3::prelude::*; +use pyo3::exceptions::PyTypeError; +use pyo3::types::{PyDict, IntoPyDict}; + +# fn main() { +# let _ = +Python::with_gil(|py| -> PyResult<()> { + let dict: &PyDict = [("a", 1)].into_py_dict(py); + // `a` is in the dictionary, with value 1 + assert!(dict.get_item("a")?.map_or(Ok(false), |x| x.eq(1))?); + // `b` is not in the dictionary + assert!(dict.get_item("b")?.is_none()); + // `dict` is not hashable, so this fails with a `TypeError` + assert!(dict.get_item(dict).unwrap_err().is_instance_of::(py)); + + Ok(()) +}); +# } +``` + ### Required arguments are no longer accepted after optional arguments [Trailing `Option` arguments](./function/signature.md#trailing-optional-arguments) have an automatic default of `None`. To avoid unwanted changes when modifying function signatures, in PyO3 0.18 it was deprecated to have a required argument after an `Option` argument without using `#[pyo3(signature = (...))]` to specify the intended defaults. In PyO3 0.20, this becomes a hard error.