1use pyo3::{prelude::*, types::*};
2
3pub fn all_builtin_types(any: &Bound<'_, PyAny>) -> bool {
4 if any.is_instance_of::<PyString>()
5 || any.is_instance_of::<PyBool>()
6 || any.is_instance_of::<PyInt>()
7 || any.is_instance_of::<PyFloat>()
8 || any.is_none()
9 {
10 return true;
11 }
12 if any.is_instance_of::<PyDict>() {
13 return any
14 .downcast::<PyDict>()
15 .map(|dict| {
16 dict.into_iter()
17 .all(|(k, v)| all_builtin_types(&k) && all_builtin_types(&v))
18 })
19 .unwrap_or(false);
20 }
21 if any.is_instance_of::<PyList>() {
22 return any
23 .downcast::<PyList>()
24 .map(|list| list.into_iter().all(|v| all_builtin_types(&v)))
25 .unwrap_or(false);
26 }
27 if any.is_instance_of::<PyTuple>() {
28 return any
29 .downcast::<PyTuple>()
30 .map(|list| list.into_iter().all(|v| all_builtin_types(&v)))
31 .unwrap_or(false);
32 }
33 false
34}
35
36pub fn fmt_py_obj(any: &Bound<'_, PyAny>) -> String {
37 if all_builtin_types(any) {
38 if let Ok(py_str: Bound<'_, PyString>) = any.repr() {
39 return py_str.to_string();
40 }
41 }
42 "...".to_owned()
43}
44
45#[cfg(test)]
46mod test {
47 use pyo3::IntoPyObjectExt;
48
49 use super::*;
50 #[pyclass]
51 #[derive(Debug)]
52 struct A {}
53 #[test]
54 fn test_fmt_dict() {
55 pyo3::prepare_freethreaded_python();
56 Python::with_gil(|py| {
57 let dict = PyDict::new(py);
58 _ = dict.set_item("k1", "v1");
59 _ = dict.set_item("k2", 2);
60 assert_eq!("{'k1': 'v1', 'k2': 2}", fmt_py_obj(&dict));
61 // class A variable can not be formatted
62 _ = dict.set_item("k3", A {});
63 assert_eq!("...", fmt_py_obj(&dict));
64 })
65 }
66 #[test]
67 fn test_fmt_list() {
68 pyo3::prepare_freethreaded_python();
69 Python::with_gil(|py| {
70 let list = PyList::new(py, [1, 2]).unwrap();
71 assert_eq!("[1, 2]", fmt_py_obj(&list));
72 // class A variable can not be formatted
73 let list = PyList::new(py, [A {}, A {}]).unwrap();
74 assert_eq!("...", fmt_py_obj(&list));
75 })
76 }
77 #[test]
78 fn test_fmt_tuple() {
79 pyo3::prepare_freethreaded_python();
80 Python::with_gil(|py| {
81 let tuple = PyTuple::new(py, [1, 2]).unwrap();
82 assert_eq!("(1, 2)", fmt_py_obj(&tuple));
83 let tuple = PyTuple::new(py, [1]).unwrap();
84 assert_eq!("(1,)", fmt_py_obj(&tuple));
85 // class A variable can not be formatted
86 let tuple = PyTuple::new(py, [A {}]).unwrap();
87 assert_eq!("...", fmt_py_obj(&tuple));
88 })
89 }
90 #[test]
91 fn test_fmt_other() {
92 pyo3::prepare_freethreaded_python();
93 Python::with_gil(|py| {
94 // str
95 assert_eq!("'123'", fmt_py_obj(&"123".into_bound_py_any(py).unwrap()));
96 assert_eq!(
97 "\"don't\"",
98 fmt_py_obj(&"don't".into_bound_py_any(py).unwrap())
99 );
100 assert_eq!(
101 "'str\\\\'",
102 fmt_py_obj(&"str\\".into_bound_py_any(py).unwrap())
103 );
104 // bool
105 assert_eq!("True", fmt_py_obj(&true.into_bound_py_any(py).unwrap()));
106 assert_eq!("False", fmt_py_obj(&false.into_bound_py_any(py).unwrap()));
107 // int
108 assert_eq!("123", fmt_py_obj(&123.into_bound_py_any(py).unwrap()));
109 // float
110 assert_eq!("1.23", fmt_py_obj(&1.23.into_bound_py_any(py).unwrap()));
111 // None
112 let none: Option<usize> = None;
113 assert_eq!("None", fmt_py_obj(&none.into_bound_py_any(py).unwrap()));
114 // class A variable can not be formatted
115 assert_eq!("...", fmt_py_obj(&A {}.into_bound_py_any(py).unwrap()));
116 })
117 }
118}
119