pyo3/tests/test_class_basics.rs

225 lines
5.4 KiB
Rust

use pyo3::prelude::*;
use pyo3::py_run;
mod common;
#[pyclass]
struct EmptyClass {}
#[test]
fn empty_class() {
let gil = Python::acquire_gil();
let py = gil.python();
let typeobj = py.get_type::<EmptyClass>();
// By default, don't allow creating instances from python.
assert!(typeobj.call((), None).is_err());
py_assert!(py, typeobj, "typeobj.__name__ == 'EmptyClass'");
}
/// Line1
///Line2
/// Line3
// this is not doc string
#[pyclass]
struct ClassWithDocs {
/// Property field
#[pyo3(get, set)]
value: i32,
/// Read-only property field
#[pyo3(get)]
readonly: i32,
/// Write-only property field
#[pyo3(set)]
writeonly: i32,
}
#[test]
fn class_with_docstr() {
{
let gil = Python::acquire_gil();
let py = gil.python();
let typeobj = py.get_type::<ClassWithDocs>();
py_run!(
py,
typeobj,
"assert typeobj.__doc__ == 'Line1\\nLine2\\n Line3'"
);
py_run!(
py,
typeobj,
"assert typeobj.value.__doc__ == 'Property field'"
);
py_run!(
py,
typeobj,
"assert typeobj.readonly.__doc__ == 'Read-only property field'"
);
py_run!(
py,
typeobj,
"assert typeobj.writeonly.__doc__ == 'Write-only property field'"
);
}
}
#[pyclass(name=CustomName)]
struct EmptyClass2 {}
#[pymethods]
impl EmptyClass2 {
#[name = "custom_fn"]
fn bar(&self) {}
#[staticmethod]
#[name = "custom_static"]
fn bar_static() {}
}
#[test]
fn custom_names() {
let gil = Python::acquire_gil();
let py = gil.python();
let typeobj = py.get_type::<EmptyClass2>();
py_assert!(py, typeobj, "typeobj.__name__ == 'CustomName'");
py_assert!(py, typeobj, "typeobj.custom_fn.__name__ == 'custom_fn'");
py_assert!(
py,
typeobj,
"typeobj.custom_static.__name__ == 'custom_static'"
);
py_assert!(py, typeobj, "not hasattr(typeobj, 'bar')");
py_assert!(py, typeobj, "not hasattr(typeobj, 'bar_static')");
}
#[pyclass]
struct RawIdents {
#[pyo3(get, set)]
r#type: i64,
}
#[pymethods]
impl RawIdents {
fn r#fn(&self) {}
}
#[test]
fn test_raw_idents() {
let gil = Python::acquire_gil();
let py = gil.python();
let typeobj = py.get_type::<RawIdents>();
py_assert!(py, typeobj, "not hasattr(typeobj, 'r#fn')");
py_assert!(py, typeobj, "hasattr(typeobj, 'fn')");
py_assert!(py, typeobj, "hasattr(typeobj, 'type')");
}
#[pyclass]
struct EmptyClassInModule {}
#[test]
fn empty_class_in_module() {
let gil = Python::acquire_gil();
let py = gil.python();
let module = PyModule::new(py, "test_module.nested").unwrap();
module.add_class::<EmptyClassInModule>().unwrap();
let ty = module.getattr("EmptyClassInModule").unwrap();
assert_eq!(
ty.getattr("__name__").unwrap().extract::<String>().unwrap(),
"EmptyClassInModule"
);
let module: String = ty.getattr("__module__").unwrap().extract().unwrap();
// Rationale: The class can be added to many modules, but will only be initialized once.
// We currently have no way of determining a canonical module, so builtins is better
// than using whatever calls init first.
assert_eq!(module, "builtins");
}
#[pyclass]
struct ClassWithObjectField {
// It used to be that PyObject was not supported with (get, set)
// - this test is just ensuring it compiles.
#[pyo3(get, set)]
value: PyObject,
}
#[pymethods]
impl ClassWithObjectField {
#[new]
fn new(value: PyObject) -> ClassWithObjectField {
Self { value }
}
}
#[test]
fn class_with_object_field() {
let gil = Python::acquire_gil();
let py = gil.python();
let ty = py.get_type::<ClassWithObjectField>();
py_assert!(py, ty, "ty(5).value == 5");
py_assert!(py, ty, "ty(None).value == None");
}
#[pyclass(unsendable)]
struct UnsendableBase {
rc: std::rc::Rc<usize>,
}
#[pymethods]
impl UnsendableBase {
fn value(&self) -> usize {
*self.rc.as_ref()
}
}
#[pyclass(extends=UnsendableBase)]
struct UnsendableChild {}
/// If a class is marked as `unsendable`, it panics when accessed by another thread.
#[test]
fn panic_unsendable() {
if option_env!("RUSTFLAGS")
.map(|s| s.contains("-Cpanic=abort"))
.unwrap_or(false)
{
return;
}
let gil = Python::acquire_gil();
let py = gil.python();
let base = || UnsendableBase {
rc: std::rc::Rc::new(0),
};
let unsendable_base = PyCell::new(py, base()).unwrap();
let unsendable_child = PyCell::new(py, (UnsendableChild {}, base())).unwrap();
let source = pyo3::indoc::indoc!(
r#"
def value():
return unsendable.value()
import concurrent.futures
executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
future = executor.submit(value)
try:
result = future.result()
assert False, 'future must panic'
except BaseException as e:
assert str(e) == 'test_class_basics::UnsendableBase is unsendable, but sent to another thread!'
"#
);
let globals = PyModule::import(py, "__main__").unwrap().dict();
let test = |unsendable| {
globals.set_item("unsendable", unsendable).unwrap();
py.run(source, Some(globals), None)
.map_err(|e| e.print(py))
.unwrap();
};
test(unsendable_base.as_ref());
test(unsendable_child.as_ref());
}