#![cfg(feature = "macros")] use pyo3::prelude::*; mod common; #[pyclass] struct Foo { #[pyo3(get)] x: i32, } #[pyclass] struct Bar { #[pyo3(get)] x: i32, } #[pymethods] impl Foo { #[classattr] const MY_CONST: &'static str = "foobar"; #[classattr] #[pyo3(name = "RENAMED_CONST")] const MY_CONST_2: &'static str = "foobar_2"; #[classattr] fn a() -> i32 { 5 } #[classattr] #[pyo3(name = "B")] fn b() -> String { "bar".to_string() } #[classattr] fn bar() -> Bar { Bar { x: 2 } } #[classattr] fn a_foo() -> Foo { Foo { x: 1 } } #[classattr] fn a_foo_with_py(py: Python<'_>) -> Py { Py::new(py, Foo { x: 1 }).unwrap() } } #[test] fn class_attributes() { Python::with_gil(|py| { let foo_obj = py.get_type::(); py_assert!(py, foo_obj, "foo_obj.MY_CONST == 'foobar'"); py_assert!(py, foo_obj, "foo_obj.RENAMED_CONST == 'foobar_2'"); py_assert!(py, foo_obj, "foo_obj.a == 5"); py_assert!(py, foo_obj, "foo_obj.B == 'bar'"); py_assert!(py, foo_obj, "foo_obj.a_foo.x == 1"); py_assert!(py, foo_obj, "foo_obj.a_foo_with_py.x == 1"); }); } // Ignored because heap types are not immutable: // https://github.com/python/cpython/blob/master/Objects/typeobject.c#L3399-L3409 #[test] #[ignore] fn class_attributes_are_immutable() { Python::with_gil(|py| { let foo_obj = py.get_type::(); py_expect_exception!(py, foo_obj, "foo_obj.a = 6", PyTypeError); }); } #[pymethods] impl Bar { #[classattr] fn a_foo() -> Foo { Foo { x: 3 } } } #[test] fn recursive_class_attributes() { Python::with_gil(|py| { let foo_obj = py.get_type::(); let bar_obj = py.get_type::(); py_assert!(py, foo_obj, "foo_obj.a_foo.x == 1"); py_assert!(py, foo_obj, "foo_obj.bar.x == 2"); py_assert!(py, bar_obj, "bar_obj.a_foo.x == 3"); }); } #[test] fn test_fallible_class_attribute() { use pyo3::{exceptions::PyValueError, types::PyString}; struct CaptureStdErr<'py> { oldstderr: &'py PyAny, string_io: &'py PyAny, } impl<'py> CaptureStdErr<'py> { fn new(py: Python<'py>) -> PyResult { let sys = py.import("sys")?; let oldstderr = sys.getattr("stderr")?; let string_io = py.import("io")?.getattr("StringIO")?.call0()?; sys.setattr("stderr", string_io)?; Ok(Self { oldstderr, string_io, }) } fn reset(self) -> PyResult { let py = self.string_io.py(); let payload = self .string_io .getattr("getvalue")? .call0()? .downcast::()? .to_str()? .to_owned(); let sys = py.import("sys")?; sys.setattr("stderr", self.oldstderr)?; Ok(payload) } } #[pyclass] struct BrokenClass; #[pymethods] impl BrokenClass { #[classattr] fn fails_to_init() -> PyResult { Err(PyValueError::new_err("failed to create class attribute")) } } Python::with_gil(|py| { let stderr = CaptureStdErr::new(py).unwrap(); assert!(std::panic::catch_unwind(|| py.get_type::()).is_err()); assert_eq!( stderr.reset().unwrap().trim(), "\ ValueError: failed to create class attribute The above exception was the direct cause of the following exception: RuntimeError: An error occurred while initializing `BrokenClass.fails_to_init` The above exception was the direct cause of the following exception: RuntimeError: An error occurred while initializing class BrokenClass" ) }); }