1 | use pyo3::{prelude::*, types::*}; |
2 | |
3 | pub 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 | |
36 | pub 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)] |
46 | mod 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 | |