extern crate pyo3; use dict_derive::FromPyObject; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; #[derive(FromPyObject, Debug)] struct User { name: Option, email: String, age: u16, } #[derive(FromPyObject, Debug)] struct UserWithLifetime<'a> { name: Option<&'a str>, email: &'a str, age: u16, } fn make_user_dict<'a, A, B, C>( py: &'a Python, email: A, age: B, name: Option>, ) -> PyResult<&'a PyDict> where A: ToPyObject, B: ToPyObject, C: ToPyObject, { let dict = PyDict::new(*py); if let Some(opt) = name { dict.set_item("name", opt)?; } dict.set_item("email", email)?; dict.set_item("age", age)?; Ok(dict) } #[test] fn test_conversion() -> PyResult<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { { let dict = make_user_dict(&py, "tester@tests.com", 27, Some(Some("Tester")))?; let result: PyResult = dict.extract(); assert!(result.is_ok()); let user = result.unwrap(); assert_eq!(&user.name.unwrap(), "Tester"); assert_eq!(&user.email, "tester@tests.com"); assert_eq!(user.age, 27); } { let name: Option> = None; let dict = make_user_dict(&py, "tester@tests.com", 27, name)?; let result: PyResult = dict.extract(); assert!(result.is_ok()); let user = result.unwrap(); assert_eq!(user.name, None); assert_eq!(&user.email, "tester@tests.com"); assert_eq!(user.age, 27); } { let name: Option> = Some(None); let dict = make_user_dict(&py, "tester@tests.com", 27, name)?; let result: PyResult = dict.extract(); assert!(result.is_ok()); let user = result.unwrap(); assert_eq!(user.name, None); assert_eq!(&user.email, "tester@tests.com"); assert_eq!(user.age, 27); } { let dict = make_user_dict(&py, "tester@tests.com", 27, Some(Some("Tester")))?; let result: PyResult = dict.extract(); assert!(result.is_ok()); let user = result.unwrap(); assert_eq!(user.name, Some("Tester")); assert_eq!(user.email, "tester@tests.com"); assert_eq!(user.age, 27); } Ok(()) }) } #[test] fn test_type_error() -> PyResult<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { let dict = make_user_dict(&py, "tester@tests.com", "27", Some(Some("Test")))?; let result: PyResult = dict.extract(); assert!(result.is_err()); let err = result.unwrap_err(); assert!(err.is_instance_of::(py)); let result = err.value(py).to_string(); assert_eq!(&result, "Unable to convert key: age. Error: TypeError: 'str' object cannot be interpreted as an integer"); Ok(()) }) } #[test] fn test_missing_key() -> PyResult<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { let dict = make_user_dict(&py, "tester@tests.com", 27, Some(Some("Test")))?; dict.del_item("age")?; let result: PyResult = dict.extract(); assert!(result.is_err()); let err = result.unwrap_err(); assert!(err.is_instance_of::(py)); let result = err.value(py).to_string(); assert_eq!(&result, "Missing required key: age"); Ok(()) }) } #[test] fn test_wrong_type() -> PyResult<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { let list = PyList::new(py, vec![1, 2, 3]); let result: PyResult = list.extract(); assert!(result.is_err()); let err = result.unwrap_err(); assert!(err.is_instance_of::(py)); let result = err.value(py).to_string(); assert_eq!(&result, "Invalid type to convert, expected dict"); Ok(()) }) } use std::option; #[derive(FromPyObject)] struct TotallyOptionalUser { name: Option, email: option::Option, age: std::option::Option, address: core::option::Option, } #[test] fn test_optionals() -> PyResult<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { let dict = PyDict::new(py); let result: PyResult = dict.extract(); assert!(result.is_ok()); let user = result.unwrap(); assert_eq!(user.name, None); assert_eq!(user.email, None); assert_eq!(user.age, None); assert_eq!(user.address, None); Ok(()) }) }