Vendor deps.

This commit is contained in:
jimmy committed 2026-09-16 20:10:35 -04:00
1 parent 294527085e
commit 91e18fde3a
1714 files changed
+556667 -7

No files matched your search

+45
View File
@@ -0,0 +1,45 @@
#![cfg(feature = "anyhow")]
use pyo3::wrap_pyfunction;
#[test]
fn test_anyhow_py_function_ok_result() {
use pyo3::{py_run, pyfunction, Python};
#[pyfunction]
#[expect(clippy::unnecessary_wraps)]
fn produce_ok_result() -> anyhow::Result<String> {
Ok(String::from("OK buddy"))
}
Python::attach(|py| {
let func = wrap_pyfunction!(produce_ok_result)(py).unwrap();
py_run!(
py,
func,
r#"
func()
"#
);
});
}
#[test]
fn test_anyhow_py_function_err_result() {
use pyo3::prelude::PyDictMethods;
use pyo3::{pyfunction, types::PyDict, Python};
#[pyfunction]
fn produce_err_result() -> anyhow::Result<String> {
anyhow::bail!("error time")
}
Python::attach(|py| {
let func = wrap_pyfunction!(produce_err_result)(py).unwrap();
let locals = PyDict::new(py);
locals.set_item("func", func).unwrap();
py.run(c"func()", None, Some(&locals)).unwrap_err();
});
}
+57
View File
@@ -0,0 +1,57 @@
#![cfg(feature = "macros")]
#![cfg(not(PyPy))]
use pyo3::prelude::*;
#[pyfunction]
fn foo() -> usize {
123
}
#[pymodule]
fn module_fn_with_functions(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(foo, m)?)?;
Ok(())
}
#[pymodule]
mod module_mod_with_functions {
#[pymodule_export]
use super::foo;
}
#[cfg(not(any(PyPy, GraalPy, all(Py_LIMITED_API, Py_GIL_DISABLED))))]
#[test]
fn test_module_append_to_inittab() {
use pyo3::append_to_inittab;
append_to_inittab!(module_fn_with_functions);
append_to_inittab!(module_mod_with_functions);
Python::attach(|py| {
py.run(
cr#"
import module_fn_with_functions
assert module_fn_with_functions.foo() == 123
"#,
None,
None,
)
.map_err(|e| e.display(py))
.unwrap();
});
Python::attach(|py| {
py.run(
cr#"
import module_mod_with_functions
assert module_mod_with_functions.foo() == 123
"#,
None,
None,
)
.map_err(|e| e.display(py))
.unwrap();
});
}
+803
View File
@@ -0,0 +1,803 @@
#![cfg(feature = "macros")]
use pyo3::class::basic::CompareOp;
use pyo3::py_run;
use pyo3::{prelude::*, BoundObject};
mod test_utils;
#[pyclass]
struct UnaryArithmetic {
inner: f64,
}
#[pymethods]
impl UnaryArithmetic {
#[new]
fn new(value: f64) -> Self {
UnaryArithmetic { inner: value }
}
fn __repr__(&self) -> String {
format!("UA({})", self.inner)
}
fn __neg__(&self) -> Self {
Self::new(-self.inner)
}
fn __pos__(&self) -> Self {
Self::new(self.inner)
}
fn __abs__(&self) -> Self {
Self::new(self.inner.abs())
}
fn __invert__(&self) -> Self {
Self::new(self.inner.recip())
}
#[pyo3(signature=(_ndigits=None))]
fn __round__(&self, _ndigits: Option<u32>) -> Self {
Self::new(self.inner.round())
}
}
#[test]
fn unary_arithmetic() {
Python::attach(|py| {
let c = Py::new(py, UnaryArithmetic::new(2.7)).unwrap();
py_run!(py, c, "assert repr(-c) == 'UA(-2.7)'");
py_run!(py, c, "assert repr(+c) == 'UA(2.7)'");
py_run!(py, c, "assert repr(abs(c)) == 'UA(2.7)'");
py_run!(py, c, "assert repr(~c) == 'UA(0.37037037037037035)'");
py_run!(py, c, "assert repr(round(c)) == 'UA(3)'");
py_run!(py, c, "assert repr(round(c, 1)) == 'UA(3)'");
let c: Bound<'_, PyAny> = c.extract(py).unwrap();
assert_py_eq!(c.neg().unwrap().repr().unwrap().as_any(), "UA(-2.7)");
assert_py_eq!(c.pos().unwrap().repr().unwrap().as_any(), "UA(2.7)");
assert_py_eq!(c.abs().unwrap().repr().unwrap().as_any(), "UA(2.7)");
assert_py_eq!(
c.bitnot().unwrap().repr().unwrap().as_any(),
"UA(0.37037037037037035)"
);
// Ensure that passing a wrong self type from Python does not cause UB
py_expect_exception!(py, c, "type(c).__neg__(object())", PyTypeError);
py_expect_exception!(py, c, "type(c).__pos__(object())", PyTypeError);
py_expect_exception!(py, c, "type(c).__abs__(object())", PyTypeError);
py_expect_exception!(py, c, "type(c).__invert__(object())", PyTypeError);
py_expect_exception!(py, c, "type(c).__round__(object())", PyTypeError);
});
}
#[pyclass]
struct Indexable(i32);
#[pymethods]
impl Indexable {
fn __index__(&self) -> i32 {
self.0
}
fn __int__(&self) -> i32 {
self.0
}
fn __float__(&self) -> f64 {
f64::from(self.0)
}
fn __invert__(&self) -> Self {
Self(!self.0)
}
}
#[test]
fn indexable() {
Python::attach(|py| {
let i = Py::new(py, Indexable(5)).unwrap();
py_run!(py, i, "assert int(i) == 5");
py_run!(py, i, "assert [0, 1, 2, 3, 4, 5][i] == 5");
py_run!(py, i, "assert float(i) == 5.0");
py_run!(py, i, "assert int(~i) == -6");
// Ensure that passing a wrong self type from Python does not cause UB
py_expect_exception!(py, i, "type(i).__index__(object())", PyTypeError);
py_expect_exception!(py, i, "type(i).__int__(object())", PyTypeError);
py_expect_exception!(py, i, "type(i).__float__(object())", PyTypeError);
py_expect_exception!(py, i, "type(i).__invert__(object())", PyTypeError);
})
}
#[pyclass]
struct InPlaceOperations {
value: u32,
}
#[pymethods]
impl InPlaceOperations {
fn __repr__(&self) -> String {
format!("IPO({:?})", self.value)
}
fn __iadd__(&mut self, other: u32) {
self.value += other;
}
fn __isub__(&mut self, other: u32) {
self.value -= other;
}
fn __imul__(&mut self, other: u32) {
self.value *= other;
}
fn __ilshift__(&mut self, other: u32) {
self.value <<= other;
}
fn __irshift__(&mut self, other: u32) {
self.value >>= other;
}
fn __iand__(&mut self, other: u32) {
self.value &= other;
}
fn __ixor__(&mut self, other: u32) {
self.value ^= other;
}
fn __ior__(&mut self, other: u32) {
self.value |= other;
}
fn __ipow__(&mut self, other: u32, _modulo: Option<u32>) {
self.value = self.value.pow(other);
}
}
#[test]
fn inplace_operations() {
Python::attach(|py| {
let init = |value, code| {
let c = Py::new(py, InPlaceOperations { value }).unwrap();
py_run!(py, c, code);
};
init(0, "d = c; c += 1; assert repr(c) == repr(d) == 'IPO(1)'");
init(10, "d = c; c -= 1; assert repr(c) == repr(d) == 'IPO(9)'");
init(3, "d = c; c *= 3; assert repr(c) == repr(d) == 'IPO(9)'");
init(3, "d = c; c <<= 2; assert repr(c) == repr(d) == 'IPO(12)'");
init(12, "d = c; c >>= 2; assert repr(c) == repr(d) == 'IPO(3)'");
init(12, "d = c; c &= 10; assert repr(c) == repr(d) == 'IPO(8)'");
init(12, "d = c; c |= 3; assert repr(c) == repr(d) == 'IPO(15)'");
init(12, "d = c; c ^= 5; assert repr(c) == repr(d) == 'IPO(9)'");
init(3, "d = c; c **= 4; assert repr(c) == repr(d) == 'IPO(81)'");
init(
3,
"d = c; c.__ipow__(4); assert repr(c) == repr(d) == 'IPO(81)'",
);
let c = Py::new(py, InPlaceOperations { value: 0 }).unwrap();
// Ensure that passing a wrong self type from Python does not cause UB
py_expect_exception!(py, c, "type(c).__iadd__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__isub__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__imul__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__ilshift__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__irshift__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__iand__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__ixor__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__ior__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__ipow__(object(), 1)", PyTypeError);
});
}
#[pyclass]
struct BinaryArithmetic {}
#[pymethods]
impl BinaryArithmetic {
fn __repr__(&self) -> &'static str {
"BA"
}
fn __add__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA + {rhs:?}")
}
fn __sub__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA - {rhs:?}")
}
fn __mul__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA * {rhs:?}")
}
fn __matmul__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA @ {rhs:?}")
}
fn __truediv__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA / {rhs:?}")
}
fn __floordiv__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA // {rhs:?}")
}
fn __mod__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA % {rhs:?}")
}
fn __divmod__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("divmod(BA, {rhs:?})")
}
fn __lshift__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA << {rhs:?}")
}
fn __rshift__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA >> {rhs:?}")
}
fn __and__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA & {rhs:?}")
}
fn __xor__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA ^ {rhs:?}")
}
fn __or__(&self, rhs: &Bound<'_, PyAny>) -> String {
format!("BA | {rhs:?}")
}
fn __pow__(&self, rhs: &Bound<'_, PyAny>, mod_: Option<u32>) -> String {
format!("BA ** {rhs:?} (mod: {mod_:?})")
}
}
#[test]
fn binary_arithmetic() {
Python::attach(|py| {
let c = Py::new(py, BinaryArithmetic {}).unwrap();
py_run!(py, c, "assert c + c == 'BA + BA'");
py_run!(py, c, "assert c.__add__(c) == 'BA + BA'");
py_run!(py, c, "assert c + 1 == 'BA + 1'");
py_run!(py, c, "assert c - 1 == 'BA - 1'");
py_run!(py, c, "assert c * 1 == 'BA * 1'");
py_run!(py, c, "assert c @ 1 == 'BA @ 1'");
py_run!(py, c, "assert c / 1 == 'BA / 1'");
py_run!(py, c, "assert c // 1 == 'BA // 1'");
py_run!(py, c, "assert c % 1 == 'BA % 1'");
py_run!(py, c, "assert divmod(c, 1) == 'divmod(BA, 1)'");
py_run!(py, c, "assert c << 1 == 'BA << 1'");
py_run!(py, c, "assert c >> 1 == 'BA >> 1'");
py_run!(py, c, "assert c & 1 == 'BA & 1'");
py_run!(py, c, "assert c ^ 1 == 'BA ^ 1'");
py_run!(py, c, "assert c | 1 == 'BA | 1'");
py_run!(py, c, "assert c ** 1 == 'BA ** 1 (mod: None)'");
// Class with __add__ only should not allow the reverse op;
// this is consistent with Python classes.
py_expect_exception!(py, c, "1 + c", PyTypeError);
py_expect_exception!(py, c, "1 - c", PyTypeError);
py_expect_exception!(py, c, "1 * c", PyTypeError);
py_expect_exception!(py, c, "1 @ c", PyTypeError);
py_expect_exception!(py, c, "1 / c", PyTypeError);
py_expect_exception!(py, c, "1 // c", PyTypeError);
py_expect_exception!(py, c, "1 % c", PyTypeError);
py_expect_exception!(py, c, "divmod(1, c)", PyTypeError);
py_expect_exception!(py, c, "1 << c", PyTypeError);
py_expect_exception!(py, c, "1 >> c", PyTypeError);
py_expect_exception!(py, c, "1 & c", PyTypeError);
py_expect_exception!(py, c, "1 ^ c", PyTypeError);
py_expect_exception!(py, c, "1 | c", PyTypeError);
py_expect_exception!(py, c, "1 ** c", PyTypeError);
py_run!(py, c, "assert pow(c, 1, 100) == 'BA ** 1 (mod: Some(100))'");
let c: Bound<'_, PyAny> = c.extract(py).unwrap();
assert_py_eq!(c.add(&c).unwrap(), "BA + BA");
assert_py_eq!(c.sub(&c).unwrap(), "BA - BA");
assert_py_eq!(c.mul(&c).unwrap(), "BA * BA");
assert_py_eq!(c.matmul(&c).unwrap(), "BA @ BA");
assert_py_eq!(c.div(&c).unwrap(), "BA / BA");
assert_py_eq!(c.floor_div(&c).unwrap(), "BA // BA");
assert_py_eq!(c.rem(&c).unwrap(), "BA % BA");
assert_py_eq!(c.divmod(&c).unwrap(), "divmod(BA, BA)");
assert_py_eq!(c.lshift(&c).unwrap(), "BA << BA");
assert_py_eq!(c.rshift(&c).unwrap(), "BA >> BA");
assert_py_eq!(c.bitand(&c).unwrap(), "BA & BA");
assert_py_eq!(c.bitor(&c).unwrap(), "BA | BA");
assert_py_eq!(c.bitxor(&c).unwrap(), "BA ^ BA");
assert_py_eq!(c.pow(&c, py.None()).unwrap(), "BA ** BA (mod: None)");
});
}
#[pyclass]
struct RhsArithmetic(String);
#[pymethods]
impl RhsArithmetic {
fn __radd__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} + {}", self.0)
}
fn __rsub__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} - {}", self.0)
}
fn __rmul__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} * {}", self.0)
}
fn __rlshift__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} << {}", self.0)
}
fn __rrshift__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} >> {}", self.0)
}
fn __rand__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} & {}", self.0)
}
fn __rxor__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} ^ {}", self.0)
}
fn __ror__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} | {}", self.0)
}
fn __rpow__(&self, other: &Bound<'_, PyAny>, _mod: Option<&Bound<'_, PyAny>>) -> String {
format!("{other:?} ** {}", self.0)
}
}
#[test]
fn rhs_arithmetic() {
Python::attach(|py| {
let c = Py::new(py, RhsArithmetic("RA".to_string())).unwrap();
py_run!(py, c, "assert c.__radd__(1) == '1 + RA'");
py_run!(py, c, "assert 1 + c == '1 + RA'");
py_run!(py, c, "assert c.__rsub__(1) == '1 - RA'");
py_run!(py, c, "assert 1 - c == '1 - RA'");
py_run!(py, c, "assert c.__rmul__(1) == '1 * RA'");
py_run!(py, c, "assert 1 * c == '1 * RA'");
py_run!(py, c, "assert c.__rlshift__(1) == '1 << RA'");
py_run!(py, c, "assert 1 << c == '1 << RA'");
py_run!(py, c, "assert c.__rrshift__(1) == '1 >> RA'");
py_run!(py, c, "assert 1 >> c == '1 >> RA'");
py_run!(py, c, "assert c.__rand__(1) == '1 & RA'");
py_run!(py, c, "assert 1 & c == '1 & RA'");
py_run!(py, c, "assert c.__rxor__(1) == '1 ^ RA'");
py_run!(py, c, "assert 1 ^ c == '1 ^ RA'");
py_run!(py, c, "assert c.__ror__(1) == '1 | RA'");
py_run!(py, c, "assert 1 | c == '1 | RA'");
py_run!(py, c, "assert c.__rpow__(1) == '1 ** RA'");
py_run!(py, c, "assert 1 ** c == '1 ** RA'");
});
}
#[pyclass]
struct LhsAndRhs {}
impl std::fmt::Debug for LhsAndRhs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "LR")
}
}
#[pymethods]
impl LhsAndRhs {
// fn __repr__(&self) -> &'static str {
// "BA"
// }
fn __add__(lhs: PyRef<'_, Self>, rhs: &Bound<'_, PyAny>) -> String {
format!("{lhs:?} + {rhs:?}")
}
fn __sub__(lhs: PyRef<'_, Self>, rhs: &Bound<'_, PyAny>) -> String {
format!("{lhs:?} - {rhs:?}")
}
fn __mul__(lhs: PyRef<'_, Self>, rhs: &Bound<'_, PyAny>) -> String {
format!("{lhs:?} * {rhs:?}")
}
fn __lshift__(lhs: PyRef<'_, Self>, rhs: &Bound<'_, PyAny>) -> String {
format!("{lhs:?} << {rhs:?}")
}
fn __rshift__(lhs: PyRef<'_, Self>, rhs: &Bound<'_, PyAny>) -> String {
format!("{lhs:?} >> {rhs:?}")
}
fn __and__(lhs: PyRef<'_, Self>, rhs: &Bound<'_, PyAny>) -> String {
format!("{lhs:?} & {rhs:?}")
}
fn __xor__(lhs: PyRef<'_, Self>, rhs: &Bound<'_, PyAny>) -> String {
format!("{lhs:?} ^ {rhs:?}")
}
fn __or__(lhs: PyRef<'_, Self>, rhs: &Bound<'_, PyAny>) -> String {
format!("{lhs:?} | {rhs:?}")
}
fn __pow__(lhs: PyRef<'_, Self>, rhs: &Bound<'_, PyAny>, _mod: Option<usize>) -> String {
format!("{lhs:?} ** {rhs:?}")
}
fn __matmul__(lhs: PyRef<'_, Self>, rhs: &Bound<'_, PyAny>) -> String {
format!("{lhs:?} @ {rhs:?}")
}
fn __radd__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} + RA")
}
fn __rsub__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} - RA")
}
fn __rmul__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} * RA")
}
fn __rlshift__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} << RA")
}
fn __rrshift__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} >> RA")
}
fn __rand__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} & RA")
}
fn __rxor__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} ^ RA")
}
fn __ror__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} | RA")
}
fn __rpow__(&self, other: &Bound<'_, PyAny>, _mod: Option<&Bound<'_, PyAny>>) -> String {
format!("{other:?} ** RA")
}
fn __rmatmul__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} @ RA")
}
fn __rtruediv__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} / RA")
}
fn __rfloordiv__(&self, other: &Bound<'_, PyAny>) -> String {
format!("{other:?} // RA")
}
}
#[test]
fn lhs_fellback_to_rhs() {
Python::attach(|py| {
let c = Py::new(py, LhsAndRhs {}).unwrap();
// If the left hand value is `LhsAndRhs`, LHS is used.
py_run!(py, c, "assert c + 1 == 'LR + 1'");
py_run!(py, c, "assert c - 1 == 'LR - 1'");
py_run!(py, c, "assert c * 1 == 'LR * 1'");
py_run!(py, c, "assert c << 1 == 'LR << 1'");
py_run!(py, c, "assert c >> 1 == 'LR >> 1'");
py_run!(py, c, "assert c & 1 == 'LR & 1'");
py_run!(py, c, "assert c ^ 1 == 'LR ^ 1'");
py_run!(py, c, "assert c | 1 == 'LR | 1'");
py_run!(py, c, "assert c ** 1 == 'LR ** 1'");
py_run!(py, c, "assert c @ 1 == 'LR @ 1'");
// Fellback to RHS because of type mismatching
py_run!(py, c, "assert 1 + c == '1 + RA'");
py_run!(py, c, "assert 1 - c == '1 - RA'");
py_run!(py, c, "assert 1 * c == '1 * RA'");
py_run!(py, c, "assert 1 << c == '1 << RA'");
py_run!(py, c, "assert 1 >> c == '1 >> RA'");
py_run!(py, c, "assert 1 & c == '1 & RA'");
py_run!(py, c, "assert 1 ^ c == '1 ^ RA'");
py_run!(py, c, "assert 1 | c == '1 | RA'");
py_run!(py, c, "assert 1 ** c == '1 ** RA'");
py_run!(py, c, "assert 1 @ c == '1 @ RA'");
});
}
#[pyclass]
struct RichComparisons {}
#[pymethods]
impl RichComparisons {
fn __repr__(&self) -> &'static str {
"RC"
}
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> String {
match op {
CompareOp::Lt => format!("{} < {:?}", self.__repr__(), other),
CompareOp::Le => format!("{} <= {:?}", self.__repr__(), other),
CompareOp::Eq => format!("{} == {:?}", self.__repr__(), other),
CompareOp::Ne => format!("{} != {:?}", self.__repr__(), other),
CompareOp::Gt => format!("{} > {:?}", self.__repr__(), other),
CompareOp::Ge => format!("{} >= {:?}", self.__repr__(), other),
}
}
}
#[pyclass]
struct RichComparisons2 {}
#[pymethods]
impl RichComparisons2 {
fn __repr__(&self) -> &'static str {
"RC2"
}
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
match op {
CompareOp::Eq => true
.into_pyobject(other.py())
.map_err(Into::into)
.map(BoundObject::into_any)
.map(BoundObject::unbind),
CompareOp::Ne => false
.into_pyobject(other.py())
.map_err(Into::into)
.map(BoundObject::into_any)
.map(BoundObject::unbind),
_ => Ok(other.py().NotImplemented()),
}
}
}
#[test]
fn rich_comparisons() {
Python::attach(|py| {
let c = Py::new(py, RichComparisons {}).unwrap();
py_run!(py, c, "assert (c < c) == 'RC < RC'");
py_run!(py, c, "assert (c < 1) == 'RC < 1'");
py_run!(py, c, "assert (1 < c) == 'RC > 1'");
py_run!(py, c, "assert (c <= c) == 'RC <= RC'");
py_run!(py, c, "assert (c <= 1) == 'RC <= 1'");
py_run!(py, c, "assert (1 <= c) == 'RC >= 1'");
py_run!(py, c, "assert (c == c) == 'RC == RC'");
py_run!(py, c, "assert (c == 1) == 'RC == 1'");
py_run!(py, c, "assert (1 == c) == 'RC == 1'");
py_run!(py, c, "assert (c != c) == 'RC != RC'");
py_run!(py, c, "assert (c != 1) == 'RC != 1'");
py_run!(py, c, "assert (1 != c) == 'RC != 1'");
py_run!(py, c, "assert (c > c) == 'RC > RC'");
py_run!(py, c, "assert (c > 1) == 'RC > 1'");
py_run!(py, c, "assert (1 > c) == 'RC < 1'");
py_run!(py, c, "assert (c >= c) == 'RC >= RC'");
py_run!(py, c, "assert (c >= 1) == 'RC >= 1'");
py_run!(py, c, "assert (1 >= c) == 'RC <= 1'");
// Ensure that passing a wrong self type from Python does not cause UB
py_expect_exception!(py, c, "type(c).__lt__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__le__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__eq__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__ne__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__gt__(object(), 1)", PyTypeError);
py_expect_exception!(py, c, "type(c).__ge__(object(), 1)", PyTypeError);
});
}
#[test]
fn rich_comparisons_python_3_type_error() {
Python::attach(|py| {
let c2 = Py::new(py, RichComparisons2 {}).unwrap();
py_expect_exception!(py, c2, "c2 < c2", PyTypeError);
py_expect_exception!(py, c2, "c2 < 1", PyTypeError);
py_expect_exception!(py, c2, "1 < c2", PyTypeError);
py_expect_exception!(py, c2, "c2 <= c2", PyTypeError);
py_expect_exception!(py, c2, "c2 <= 1", PyTypeError);
py_expect_exception!(py, c2, "1 <= c2", PyTypeError);
py_run!(py, c2, "assert (c2 == c2) == True");
py_run!(py, c2, "assert (c2 == 1) == True");
py_run!(py, c2, "assert (1 == c2) == True");
py_run!(py, c2, "assert (c2 != c2) == False");
py_run!(py, c2, "assert (c2 != 1) == False");
py_run!(py, c2, "assert (1 != c2) == False");
py_expect_exception!(py, c2, "c2 > c2", PyTypeError);
py_expect_exception!(py, c2, "c2 > 1", PyTypeError);
py_expect_exception!(py, c2, "1 > c2", PyTypeError);
py_expect_exception!(py, c2, "c2 >= c2", PyTypeError);
py_expect_exception!(py, c2, "c2 >= 1", PyTypeError);
py_expect_exception!(py, c2, "1 >= c2", PyTypeError);
});
}
// Checks that binary operations for which the arguments don't match the
// required type, return NotImplemented.
mod return_not_implemented {
use super::*;
#[pyclass]
struct RichComparisonToSelf {}
#[pymethods]
impl RichComparisonToSelf {
fn __repr__(&self) -> &'static str {
"RC_Self"
}
fn __richcmp__(&self, other: PyRef<'_, Self>, _op: CompareOp) -> Py<PyAny> {
other.py().None()
}
fn __add__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __sub__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __mul__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __matmul__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __truediv__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __floordiv__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __mod__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __pow__(slf: PyRef<'_, Self>, _other: u8, _modulo: Option<u8>) -> PyRef<'_, Self> {
slf
}
fn __lshift__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __rshift__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __divmod__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __and__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __or__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
fn __xor__<'p>(slf: PyRef<'p, Self>, _other: PyRef<'p, Self>) -> PyRef<'p, Self> {
slf
}
// Inplace assignments
fn __iadd__(&mut self, _other: PyRef<'_, Self>) {}
fn __isub__(&mut self, _other: PyRef<'_, Self>) {}
fn __imul__(&mut self, _other: PyRef<'_, Self>) {}
fn __imatmul__(&mut self, _other: PyRef<'_, Self>) {}
fn __itruediv__(&mut self, _other: PyRef<'_, Self>) {}
fn __ifloordiv__(&mut self, _other: PyRef<'_, Self>) {}
fn __imod__(&mut self, _other: PyRef<'_, Self>) {}
fn __ilshift__(&mut self, _other: PyRef<'_, Self>) {}
fn __irshift__(&mut self, _other: PyRef<'_, Self>) {}
fn __iand__(&mut self, _other: PyRef<'_, Self>) {}
fn __ior__(&mut self, _other: PyRef<'_, Self>) {}
fn __ixor__(&mut self, _other: PyRef<'_, Self>) {}
fn __ipow__(&mut self, _other: PyRef<'_, Self>, _modulo: Option<u8>) {}
}
fn _test_binary_dunder(dunder: &str) {
Python::attach(|py| {
let c2 = Py::new(py, RichComparisonToSelf {}).unwrap();
py_run!(
py,
c2,
&format!("class Other: pass\nassert c2.__{dunder}__(Other()) is NotImplemented")
);
});
}
fn _test_binary_operator(operator: &str, dunder: &str) {
_test_binary_dunder(dunder);
Python::attach(|py| {
let c2 = Py::new(py, RichComparisonToSelf {}).unwrap();
py_expect_exception!(
py,
c2,
format!("class Other: pass\nc2 {} Other()", operator),
PyTypeError
);
});
}
fn _test_inplace_binary_operator(operator: &str, dunder: &str) {
_test_binary_operator(operator, dunder);
}
#[test]
fn equality() {
_test_binary_dunder("eq");
_test_binary_dunder("ne");
}
#[test]
fn ordering() {
_test_binary_operator("<", "lt");
_test_binary_operator("<=", "le");
_test_binary_operator(">", "gt");
_test_binary_operator(">=", "ge");
}
#[test]
fn bitwise() {
_test_binary_operator("&", "and");
_test_binary_operator("|", "or");
_test_binary_operator("^", "xor");
_test_binary_operator("<<", "lshift");
_test_binary_operator(">>", "rshift");
}
#[test]
fn arith() {
_test_binary_operator("+", "add");
_test_binary_operator("-", "sub");
_test_binary_operator("*", "mul");
_test_binary_operator("@", "matmul");
_test_binary_operator("/", "truediv");
_test_binary_operator("//", "floordiv");
_test_binary_operator("%", "mod");
_test_binary_operator("**", "pow");
}
#[test]
fn reverse_arith() {
_test_binary_dunder("radd");
_test_binary_dunder("rsub");
_test_binary_dunder("rmul");
_test_binary_dunder("rmatmul");
_test_binary_dunder("rtruediv");
_test_binary_dunder("rfloordiv");
_test_binary_dunder("rmod");
_test_binary_dunder("rdivmod");
_test_binary_dunder("rpow");
}
#[test]
fn inplace_bitwise() {
_test_inplace_binary_operator("&=", "iand");
_test_inplace_binary_operator("|=", "ior");
_test_inplace_binary_operator("^=", "ixor");
_test_inplace_binary_operator("<<=", "ilshift");
_test_inplace_binary_operator(">>=", "irshift");
}
#[test]
fn inplace_arith() {
_test_inplace_binary_operator("+=", "iadd");
_test_inplace_binary_operator("-=", "isub");
_test_inplace_binary_operator("*=", "imul");
_test_inplace_binary_operator("@=", "imatmul");
_test_inplace_binary_operator("/=", "itruediv");
_test_inplace_binary_operator("//=", "ifloordiv");
_test_inplace_binary_operator("%=", "imod");
_test_inplace_binary_operator("**=", "ipow");
}
}
+143
View File
@@ -0,0 +1,143 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(feature = "macros")]
#![cfg(any(not(Py_LIMITED_API), Py_3_11))]
use pyo3::{buffer::PyBuffer, exceptions::PyBufferError, ffi, prelude::*};
use std::{
ffi::{c_int, c_void},
ptr,
};
#[macro_use]
mod test_utils;
enum TestGetBufferError {
NullShape,
NullStrides,
IncorrectItemSize,
IncorrectFormat,
IncorrectAlignment,
}
#[pyclass]
struct TestBufferErrors {
buf: Vec<u32>,
error: Option<TestGetBufferError>,
}
#[pymethods]
impl TestBufferErrors {
unsafe fn __getbuffer__(
slf: PyRefMut<'_, Self>,
view: *mut ffi::Py_buffer,
flags: c_int,
) -> PyResult<()> {
if view.is_null() {
return Err(PyBufferError::new_err("View is null"));
}
if (flags & ffi::PyBUF_WRITABLE) == ffi::PyBUF_WRITABLE {
return Err(PyBufferError::new_err("Object is not writable"));
}
let bytes = &slf.buf;
unsafe {
(*view).buf = bytes.as_ptr() as *mut c_void;
(*view).len = bytes.len() as isize;
(*view).readonly = 1;
(*view).itemsize = std::mem::size_of::<u32>() as isize;
let msg = c"I";
(*view).format = msg.as_ptr() as *mut _;
(*view).ndim = 1;
(*view).shape = &mut (*view).len;
(*view).strides = &mut (*view).itemsize;
(*view).suboffsets = ptr::null_mut();
(*view).internal = ptr::null_mut();
if let Some(err) = &slf.error {
use TestGetBufferError::*;
match err {
NullShape => {
(*view).shape = std::ptr::null_mut();
}
NullStrides => {
(*view).strides = std::ptr::null_mut();
}
IncorrectItemSize => {
(*view).itemsize += 1;
}
IncorrectFormat => {
(*view).format = c"B".as_ptr() as _;
}
IncorrectAlignment => (*view).buf = (*view).buf.add(1),
}
}
(*view).obj = slf.into_ptr();
}
Ok(())
}
}
#[test]
fn test_get_buffer_errors() {
Python::attach(|py| {
let instance = Py::new(
py,
TestBufferErrors {
buf: vec![0, 1, 2, 3],
error: None,
},
)
.unwrap();
assert!(PyBuffer::<u32>::get(instance.bind(py)).is_ok());
instance.borrow_mut(py).error = Some(TestGetBufferError::NullShape);
assert_eq!(
PyBuffer::<u32>::get(instance.bind(py))
.unwrap_err()
.to_string(),
"BufferError: shape is null"
);
instance.borrow_mut(py).error = Some(TestGetBufferError::NullStrides);
assert_eq!(
PyBuffer::<u32>::get(instance.bind(py))
.unwrap_err()
.to_string(),
"BufferError: strides is null"
);
instance.borrow_mut(py).error = Some(TestGetBufferError::IncorrectItemSize);
assert_eq!(
PyBuffer::<u32>::get(instance.bind(py))
.unwrap_err()
.to_string(),
"BufferError: buffer contents are not compatible with u32"
);
instance.borrow_mut(py).error = Some(TestGetBufferError::IncorrectFormat);
assert_eq!(
PyBuffer::<u32>::get(instance.bind(py))
.unwrap_err()
.to_string(),
"BufferError: buffer contents are not compatible with u32"
);
instance.borrow_mut(py).error = Some(TestGetBufferError::IncorrectAlignment);
assert_eq!(
PyBuffer::<u32>::get(instance.bind(py))
.unwrap_err()
.to_string(),
"BufferError: buffer contents are insufficiently aligned for u32"
);
});
}
+191
View File
@@ -0,0 +1,191 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(feature = "macros")]
#![cfg(any(not(Py_LIMITED_API), Py_3_11))]
use pyo3::buffer::PyBuffer;
use pyo3::exceptions::PyBufferError;
use pyo3::ffi;
use pyo3::prelude::*;
use pyo3::types::IntoPyDict;
use std::ffi::CString;
use std::ffi::{c_int, c_void};
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
mod test_utils;
#[pyclass]
struct TestBufferClass {
vec: Vec<u8>,
drop_called: Arc<AtomicBool>,
}
#[pymethods]
impl TestBufferClass {
unsafe fn __getbuffer__(
slf: Bound<'_, Self>,
view: *mut ffi::Py_buffer,
flags: c_int,
) -> PyResult<()> {
unsafe { fill_view_from_readonly_data(view, flags, &slf.borrow().vec, slf.into_any()) }
}
unsafe fn __releasebuffer__(&self, view: *mut ffi::Py_buffer) {
// Release memory held by the format string
drop(unsafe { CString::from_raw((*view).format) });
}
}
impl Drop for TestBufferClass {
fn drop(&mut self) {
print!("dropped");
self.drop_called.store(true, Ordering::Relaxed);
}
}
#[test]
fn test_buffer() {
let drop_called = Arc::new(AtomicBool::new(false));
Python::attach(|py| {
let instance = Py::new(
py,
TestBufferClass {
vec: vec![b' ', b'2', b'3'],
drop_called: drop_called.clone(),
},
)
.unwrap();
let env = [("ob", instance)].into_py_dict(py).unwrap();
py_assert!(py, *env, "bytes(ob) == b' 23'");
});
assert!(drop_called.load(Ordering::Relaxed));
}
#[test]
fn test_buffer_referenced() {
let drop_called = Arc::new(AtomicBool::new(false));
let buf = {
let input = vec![b' ', b'2', b'3'];
Python::attach(|py| {
let instance = TestBufferClass {
vec: input.clone(),
drop_called: drop_called.clone(),
}
.into_pyobject(py)
.unwrap();
let buf = PyBuffer::<u8>::get(&instance).unwrap();
assert_eq!(buf.to_vec(py).unwrap(), input);
drop(instance);
buf
})
};
assert!(!drop_called.load(Ordering::Relaxed));
Python::attach(|_| {
drop(buf);
});
assert!(drop_called.load(Ordering::Relaxed));
}
#[test]
fn test_releasebuffer_unraisable_error() {
use pyo3::exceptions::PyValueError;
use test_utils::UnraisableCapture;
#[pyclass]
struct ReleaseBufferError {}
#[pymethods]
impl ReleaseBufferError {
unsafe fn __getbuffer__(
slf: Bound<'_, Self>,
view: *mut ffi::Py_buffer,
flags: c_int,
) -> PyResult<()> {
static BUF_BYTES: &[u8] = b"hello world";
unsafe { fill_view_from_readonly_data(view, flags, BUF_BYTES, slf.into_any()) }
}
unsafe fn __releasebuffer__(&self, _view: *mut ffi::Py_buffer) -> PyResult<()> {
Err(PyValueError::new_err("oh dear"))
}
}
Python::attach(|py| {
let instance = Py::new(py, ReleaseBufferError {}).unwrap();
let (err, object) = UnraisableCapture::enter(py, |capture| {
let env = [("ob", instance.clone_ref(py))].into_py_dict(py).unwrap();
assert!(capture.take_capture().is_none());
py_assert!(py, *env, "bytes(ob) == b'hello world'");
capture.take_capture().unwrap()
});
assert_eq!(err.to_string(), "ValueError: oh dear");
assert!(object.is(&instance));
});
}
/// # Safety
///
/// `view` must be a valid pointer to ffi::Py_buffer, or null
/// `data` must outlive the Python lifetime of `owner` (i.e. data must be owned by owner, or data
/// must be static data)
unsafe fn fill_view_from_readonly_data(
view: *mut ffi::Py_buffer,
flags: c_int,
data: &[u8],
owner: Bound<'_, PyAny>,
) -> PyResult<()> {
if view.is_null() {
return Err(PyBufferError::new_err("View is null"));
}
if (flags & ffi::PyBUF_WRITABLE) == ffi::PyBUF_WRITABLE {
return Err(PyBufferError::new_err("Object is not writable"));
}
unsafe {
(*view).obj = owner.into_ptr();
(*view).buf = data.as_ptr() as *mut c_void;
(*view).len = data.len() as isize;
(*view).readonly = 1;
(*view).itemsize = 1;
(*view).format = if (flags & ffi::PyBUF_FORMAT) == ffi::PyBUF_FORMAT {
let msg = CString::new("B").unwrap();
msg.into_raw()
} else {
ptr::null_mut()
};
(*view).ndim = 1;
(*view).shape = if (flags & ffi::PyBUF_ND) == ffi::PyBUF_ND {
&mut (*view).len
} else {
ptr::null_mut()
};
(*view).strides = if (flags & ffi::PyBUF_STRIDES) == ffi::PyBUF_STRIDES {
&mut (*view).itemsize
} else {
ptr::null_mut()
};
(*view).suboffsets = ptr::null_mut();
(*view).internal = ptr::null_mut();
}
Ok(())
}
+52
View File
@@ -0,0 +1,52 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::types::PyBytes;
mod test_utils;
#[pyfunction]
fn bytes_pybytes_conversion(bytes: &[u8]) -> &[u8] {
bytes
}
#[test]
fn test_pybytes_bytes_conversion() {
Python::attach(|py| {
let f = wrap_pyfunction!(bytes_pybytes_conversion)(py).unwrap();
py_assert!(py, f, "f(b'Hello World') == b'Hello World'");
});
}
#[pyfunction]
fn bytes_vec_conversion(py: Python<'_>, bytes: Vec<u8>) -> Bound<'_, PyBytes> {
PyBytes::new(py, bytes.as_slice())
}
#[test]
fn test_pybytes_vec_conversion() {
Python::attach(|py| {
let f = wrap_pyfunction!(bytes_vec_conversion)(py).unwrap();
py_assert!(py, f, "f(b'Hello World') == b'Hello World'");
});
}
#[test]
fn test_bytearray_vec_conversion() {
Python::attach(|py| {
let f = wrap_pyfunction!(bytes_vec_conversion)(py).unwrap();
py_assert!(py, f, "f(bytearray(b'Hello World')) == b'Hello World'");
});
}
#[test]
fn test_py_as_bytes() {
let pyobj: pyo3::Py<pyo3::types::PyBytes> =
Python::attach(|py| pyo3::types::PyBytes::new(py, b"abc").unbind());
let data = Python::attach(|py| pyobj.as_bytes(py));
assert_eq!(data, b"abc");
Python::attach(move |_py| drop(pyobj));
}
+358
View File
@@ -0,0 +1,358 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::py_run;
mod test_utils;
#[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<Foo> {
Py::new(py, Foo { x: 1 }).unwrap()
}
}
#[test]
fn class_attributes() {
Python::attach(|py| {
let foo_obj = py.get_type::<Foo>();
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");
});
}
#[test]
fn class_attributes_mutable() {
#[pyclass]
struct Foo {}
#[pymethods]
impl Foo {
#[classattr]
const MY_CONST: &'static str = "foobar";
#[classattr]
fn a() -> i32 {
5
}
}
Python::attach(|py| {
let obj = py.get_type::<Foo>();
py_run!(py, obj, "obj.MY_CONST = 'BAZ'");
py_run!(py, obj, "obj.a = 42");
py_assert!(py, obj, "obj.MY_CONST == 'BAZ'");
py_assert!(py, obj, "obj.a == 42");
});
}
#[test]
#[cfg(any(Py_3_14, all(Py_3_10, not(Py_LIMITED_API))))]
fn immutable_type_object() {
#[pyclass(immutable_type)]
struct ImmutableType {}
#[pymethods]
impl ImmutableType {
#[classattr]
const MY_CONST: &'static str = "foobar";
#[classattr]
fn a() -> i32 {
5
}
}
#[pyclass(immutable_type)]
enum SimpleImmutable {
Variant = 42,
}
#[pyclass(immutable_type)]
enum ComplexImmutable {
Variant(u32),
}
Python::attach(|py| {
let obj = py.get_type::<ImmutableType>();
py_expect_exception!(py, obj, "obj.MY_CONST = 'FOOBAR'", PyTypeError);
py_expect_exception!(py, obj, "obj.a = 6", PyTypeError);
let obj = py.get_type::<SimpleImmutable>();
py_expect_exception!(py, obj, "obj.Variant = 0", PyTypeError);
let obj = py.get_type::<ComplexImmutable>();
py_expect_exception!(py, obj, "obj.Variant = 0", PyTypeError);
});
}
#[pymethods]
impl Bar {
#[classattr]
fn a_foo() -> Foo {
Foo { x: 3 }
}
}
#[test]
fn recursive_class_attributes() {
Python::attach(|py| {
let foo_obj = py.get_type::<Foo>();
let bar_obj = py.get_type::<Bar>();
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]
#[cfg(panic = "unwind")]
fn test_fallible_class_attribute() {
use pyo3::exceptions::PyValueError;
use test_utils::UnraisableCapture;
#[pyclass]
struct BrokenClass;
#[pymethods]
impl BrokenClass {
#[classattr]
fn fails_to_init() -> PyResult<i32> {
Err(PyValueError::new_err("failed to create class attribute"))
}
}
Python::attach(|py| {
let (err, object) = UnraisableCapture::enter(py, |capture| {
// Accessing the type will attempt to initialize the class attributes
assert!(std::panic::catch_unwind(|| py.get_type::<BrokenClass>()).is_err());
capture.take_capture().unwrap()
});
assert!(object.is_none());
assert_eq!(
err.to_string(),
"RuntimeError: An error occurred while initializing class BrokenClass"
);
let cause = err.cause(py).unwrap();
assert_eq!(
cause.to_string(),
"RuntimeError: An error occurred while initializing `BrokenClass.fails_to_init`"
);
let cause = cause.cause(py).unwrap();
assert_eq!(
cause.to_string(),
"ValueError: failed to create class attribute"
);
assert!(cause.cause(py).is_none());
});
}
#[pyclass(get_all, set_all, rename_all = "camelCase")]
struct StructWithRenamedFields {
first_field: bool,
second_field: u8,
#[pyo3(name = "third_field")]
fourth_field: bool,
}
#[pymethods]
impl StructWithRenamedFields {
#[new]
fn new() -> Self {
Self {
first_field: true,
second_field: 5,
fourth_field: false,
}
}
}
#[test]
fn test_renaming_all_struct_fields() {
use pyo3::types::PyBool;
Python::attach(|py| {
let struct_class = py.get_type::<StructWithRenamedFields>();
let struct_obj = struct_class.call0().unwrap();
assert!(struct_obj
.setattr("firstField", PyBool::new(py, false))
.is_ok());
py_assert!(py, struct_obj, "struct_obj.firstField == False");
py_assert!(py, struct_obj, "struct_obj.secondField == 5");
assert!(struct_obj
.setattr("third_field", PyBool::new(py, true))
.is_ok());
py_assert!(py, struct_obj, "struct_obj.third_field == True");
});
}
#[pyclass(get_all, set_all, new = "from_fields")]
struct AutoNewCls {
a: i32,
b: String,
c: Option<f64>,
}
#[test]
fn new_impl() {
Python::attach(|py| {
// python should be able to do AutoNewCls(1, "two", 3.0)
let cls = py.get_type::<AutoNewCls>();
pyo3::py_run!(
py,
cls,
"inst = cls(1, 'two', 3.0); assert inst.a == 1; assert inst.b == 'two'; assert inst.c == 3.0"
);
});
}
#[pyclass(new = "from_fields", get_all)]
struct Point2d(#[pyo3(name = "first")] f64, #[pyo3(name = "second")] f64);
#[test]
fn new_impl_tuple_struct() {
Python::attach(|py| {
// python should be able to do AutoNewCls(1, "two", 3.0)
let cls = py.get_type::<Point2d>();
pyo3::py_run!(
py,
cls,
"inst = cls(0.2, 0.3); assert inst.first == 0.2; assert inst.second == 0.3"
);
});
}
macro_rules! test_case {
($struct_name: ident, $rule: literal, $field_name: ident, $renamed_field_name: literal, $test_name: ident) => {
#[pyclass(get_all, set_all, rename_all = $rule)]
#[allow(non_snake_case)]
struct $struct_name {
$field_name: u8,
}
#[pymethods]
impl $struct_name {
#[new]
fn new() -> Self {
Self { $field_name: 0 }
}
}
#[test]
fn $test_name() {
//use pyo3::types::PyInt;
Python::attach(|py| {
let struct_class = py.get_type::<$struct_name>();
let struct_obj = struct_class.call0().unwrap();
assert!(struct_obj.setattr($renamed_field_name, 2).is_ok());
let attr = struct_obj.getattr($renamed_field_name).unwrap();
assert_eq!(2, attr.extract::<u8>().unwrap());
});
}
};
}
test_case!(
LowercaseTest,
"lowercase",
fieldOne,
"fieldone",
test_rename_all_lowercase
);
test_case!(
CamelCaseTest,
"camelCase",
field_one,
"fieldOne",
test_rename_all_camel_case
);
test_case!(
KebabCaseTest,
"kebab-case",
field_one,
"field-one",
test_rename_all_kebab_case
);
test_case!(
PascalCaseTest,
"PascalCase",
field_one,
"FieldOne",
test_rename_all_pascal_case
);
test_case!(
ScreamingSnakeCaseTest,
"SCREAMING_SNAKE_CASE",
field_one,
"FIELD_ONE",
test_rename_all_screaming_snake_case
);
test_case!(
ScreamingKebabCaseTest,
"SCREAMING-KEBAB-CASE",
field_one,
"FIELD-ONE",
test_rename_all_screaming_kebab_case
);
test_case!(
SnakeCaseTest,
"snake_case",
fieldOne,
"field_one",
test_rename_all_snake_case
);
test_case!(
UppercaseTest,
"UPPERCASE",
fieldOne,
"FIELDONE",
test_rename_all_uppercase
);
+820
View File
@@ -0,0 +1,820 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::PyType;
#[cfg(not(target_arch = "wasm32"))]
use pyo3::PyClass;
mod test_utils;
#[pyclass]
struct EmptyClass {}
#[test]
fn empty_class() {
Python::attach(|py| {
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'");
});
}
#[pyclass]
struct UnitClass;
#[test]
fn unit_class() {
Python::attach(|py| {
let typeobj = py.get_type::<UnitClass>();
// By default, don't allow creating instances from python.
assert!(typeobj.call((), None).is_err());
py_assert!(py, typeobj, "typeobj.__name__ == 'UnitClass'");
});
}
/// 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() {
Python::attach(|py| {
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 {
#[pyo3(name = "custom_fn")]
fn bar(&self) {}
#[staticmethod]
#[pyo3(name = "custom_static")]
fn bar_static() {}
#[getter]
#[pyo3(name = "custom_getter")]
fn foo(&self) -> i32 {
5
}
}
#[test]
fn custom_names() {
Python::attach(|py| {
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,
"typeobj.custom_getter.__name__ == 'custom_getter'"
);
py_assert!(py, typeobj, "not hasattr(typeobj, 'bar')");
py_assert!(py, typeobj, "not hasattr(typeobj, 'bar_static')");
py_assert!(py, typeobj, "not hasattr(typeobj, 'foo')");
});
}
#[pyclass(name = "loop")]
struct ClassRustKeywords {
#[pyo3(name = "unsafe", get, set)]
unsafe_variable: usize,
}
#[pymethods]
impl ClassRustKeywords {
#[pyo3(name = "struct")]
fn struct_method(&self) {}
#[staticmethod]
#[pyo3(name = "type")]
fn type_method() {}
}
#[test]
fn keyword_names() {
Python::attach(|py| {
let typeobj = py.get_type::<ClassRustKeywords>();
py_assert!(py, typeobj, "typeobj.__name__ == 'loop'");
py_assert!(py, typeobj, "typeobj.struct.__name__ == 'struct'");
py_assert!(py, typeobj, "typeobj.type.__name__ == 'type'");
py_assert!(py, typeobj, "typeobj.unsafe.__name__ == 'unsafe'");
py_assert!(py, typeobj, "not hasattr(typeobj, 'unsafe_variable')");
py_assert!(py, typeobj, "not hasattr(typeobj, 'struct_method')");
py_assert!(py, typeobj, "not hasattr(typeobj, 'type_method')");
});
}
#[pyclass]
struct RawIdents {
#[pyo3(get, set)]
r#type: i64,
}
#[pymethods]
impl RawIdents {
fn r#fn(&self) {}
}
#[test]
fn test_raw_idents() {
Python::attach(|py| {
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 {}
// Ignored because heap types do not show up as being in builtins, instead they
// raise AttributeError:
// https://github.com/python/cpython/blob/v3.11.1/Objects/typeobject.c#L541-L570
#[test]
#[ignore]
fn empty_class_in_module() {
Python::attach(|py| {
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: Py<PyAny>,
}
#[pymethods]
impl ClassWithObjectField {
#[new]
fn new(value: Py<PyAny>) -> ClassWithObjectField {
Self { value }
}
}
#[test]
fn class_with_object_field() {
Python::attach(|py| {
let ty = py.get_type::<ClassWithObjectField>();
py_assert!(py, ty, "ty(5).value == 5");
py_assert!(py, ty, "ty(None).value == None");
});
}
#[pyclass(frozen, eq, hash)]
#[derive(PartialEq, Hash)]
struct ClassWithHash {
value: usize,
}
#[test]
fn class_with_hash() {
Python::attach(|py| {
use pyo3::types::IntoPyDict;
let class = ClassWithHash { value: 42 };
let hash = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
class.hash(&mut hasher);
hasher.finish() as isize
};
let env = [
("obj", Py::new(py, class).unwrap().into_any()),
("hsh", hash.into_pyobject(py).unwrap().into_any().unbind()),
]
.into_py_dict(py)
.unwrap();
py_assert!(py, *env, "hash(obj) == hsh");
});
}
#[pyclass(unsendable, subclass)]
struct UnsendableBase {
value: std::rc::Rc<usize>,
}
#[pymethods]
impl UnsendableBase {
#[new]
fn new(value: usize) -> UnsendableBase {
Self {
value: std::rc::Rc::new(value),
}
}
#[getter]
fn value(&self) -> usize {
*self.value
}
}
#[pyclass(extends=UnsendableBase)]
struct UnsendableChild {}
#[pymethods]
impl UnsendableChild {
#[new]
fn new(value: usize) -> PyClassInitializer<Self> {
PyClassInitializer::from(UnsendableBase::new(value)).add_subclass(UnsendableChild {})
}
}
#[cfg(not(target_arch = "wasm32"))]
fn test_unsendable<T: PyClass + 'static>() -> PyResult<()> {
let (keep_obj_here, obj) = Python::attach(|py| -> PyResult<_> {
let obj: Py<T> = PyType::new::<T>(py).call1((5,))?.extract()?;
// Accessing the value inside this thread should not panic
let caught_panic =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| -> PyResult<_> {
assert_eq!(obj.getattr(py, "value")?.extract::<usize>(py)?, 5);
Ok(())
}))
.is_err();
assert!(!caught_panic);
Ok((obj.clone_ref(py), obj))
})?;
let caught_panic = std::thread::spawn(move || {
// This access must panic
Python::attach(move |py| {
obj.borrow(py);
});
})
.join();
Python::attach(|_py| drop(keep_obj_here));
if let Err(err) = caught_panic {
if let Some(msg) = err.downcast_ref::<String>() {
panic!("{}", msg);
}
}
Ok(())
}
/// If a class is marked as `unsendable`, it panics when accessed by another thread.
#[test]
#[cfg(not(target_arch = "wasm32"))]
#[should_panic(
expected = "test_class_basics::UnsendableBase is unsendable, but sent to another thread"
)]
fn panic_unsendable_base() {
test_unsendable::<UnsendableBase>().unwrap();
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
#[should_panic(
expected = "test_class_basics::UnsendableBase is unsendable, but sent to another thread"
)]
fn panic_unsendable_child() {
test_unsendable::<UnsendableChild>().unwrap();
}
fn get_length(obj: &Bound<'_, PyAny>) -> PyResult<usize> {
let length = obj.len()?;
Ok(length)
}
fn is_even(obj: &Bound<'_, PyAny>) -> PyResult<bool> {
obj.extract::<i32>().map(|i| i % 2 == 0)
}
#[pyclass]
struct ClassWithFromPyWithMethods {}
#[pymethods]
impl ClassWithFromPyWithMethods {
fn instance_method(&self, #[pyo3(from_py_with = get_length)] argument: usize) -> usize {
argument
}
#[classmethod]
fn classmethod(
_cls: &Bound<'_, PyType>,
#[pyo3(from_py_with = Bound::<'_, PyAny>::len)] argument: usize,
) -> usize {
argument
}
#[staticmethod]
fn staticmethod(#[pyo3(from_py_with = get_length)] argument: usize) -> usize {
argument
}
fn __contains__(&self, #[pyo3(from_py_with = is_even)] obj: bool) -> bool {
obj
}
}
#[test]
fn test_pymethods_from_py_with() {
Python::attach(|py| {
let instance = Py::new(py, ClassWithFromPyWithMethods {}).unwrap();
py_run!(
py,
instance,
r#"
arg = {1: 1, 2: 3}
assert instance.instance_method(arg) == 2
assert instance.classmethod(arg) == 2
assert instance.staticmethod(arg) == 2
assert 42 in instance
assert 73 not in instance
"#
);
})
}
#[pyclass]
struct TupleClass(#[pyo3(get, set, name = "value")] i32);
#[test]
fn test_tuple_struct_class() {
Python::attach(|py| {
let typeobj = py.get_type::<TupleClass>();
assert!(typeobj.call((), None).is_err());
py_assert!(py, typeobj, "typeobj.__name__ == 'TupleClass'");
let instance = Py::new(py, TupleClass(5)).unwrap();
py_run!(
py,
instance,
r#"
assert instance.value == 5;
instance.value = 1234;
assert instance.value == 1234;
"#
);
assert_eq!(instance.borrow(py).0, 1234);
});
}
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[pyclass(dict, subclass)]
struct DunderDictSupport {
// Make sure that dict_offset runs with non-zero sized Self
_pad: [u8; 32],
}
#[test]
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
fn dunder_dict_support() {
Python::attach(|py| {
let inst = Py::new(
py,
DunderDictSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
},
)
.unwrap();
py_run!(
py,
inst,
r#"
inst.a = 1
assert inst.a == 1
"#
);
});
}
#[test]
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
fn access_dunder_dict() {
Python::attach(|py| {
let inst = Py::new(
py,
DunderDictSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
},
)
.unwrap();
py_run!(
py,
inst,
r#"
inst.a = 1
assert inst.__dict__ == {'a': 1}
"#
);
});
}
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[test]
fn dunder_dict_is_released() {
Python::attach(|py| {
let inst = Py::new(
py,
DunderDictSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
},
)
.unwrap();
inst.setattr(py, "a", 1).unwrap();
let dict = inst.bind(py).getattr("__dict__").unwrap();
let get_refcnt = || {
// SAFETY: `dict` holds a valid reference while its reference count is read.
unsafe { pyo3::ffi::Py_REFCNT(dict.as_ptr()) }
};
let refcnt = get_refcnt();
drop(inst);
assert_eq!(get_refcnt(), refcnt - 1);
py_assert!(py, dict, "dict == {'a': 1}");
});
}
// The `__dict__` slot must hold exactly what CPython's `tp_new` left there: CPython 3.11
// and 3.12 eagerly create the instance dict in `object.__new__` for types with a nonzero
// `tp_dictoffset`, and the pyclass contents initialization must preserve it rather than
// clobber it. All other versions create the dict lazily on first access.
#[cfg(all(not(Py_LIMITED_API), not(PyPy), not(GraalPy)))]
#[test]
fn instance_dict_slot_is_not_clobbered() {
Python::attach(|py| {
let inst = Py::new(
py,
DunderDictSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
},
)
.unwrap();
// Read the `__dict__` slot directly (see `_PyObject_GetDictPtr`), *without*
// going through `__dict__`.
// SAFETY: `inst` is a valid object whose type has a positive `tp_dictoffset`.
let read_slot = || unsafe {
let offset = (*pyo3::ffi::Py_TYPE(inst.as_ptr())).tp_dictoffset;
assert!(offset > 0);
*inst
.as_ptr()
.cast::<u8>()
.offset(offset)
.cast::<*mut pyo3::ffi::PyObject>()
};
let slot_dict = read_slot();
if cfg!(all(Py_3_11, not(Py_3_13))) {
// `object.__new__` created the dict; it must survive pyclass initialization.
assert!(
!slot_dict.is_null(),
"the eagerly created __dict__ was clobbered during pyclass initialization"
);
// The slot holds the only reference to it.
// SAFETY: previous assert guarantees it's a valid PyObject
assert_eq!(unsafe { pyo3::ffi::Py_REFCNT(slot_dict) }, 1);
} else {
// No eager creation on these versions; the slot starts out empty.
assert!(slot_dict.is_null());
}
// Whichever way the dict comes into existence, `__dict__` must be the dict
// stored in the slot.
let dict_attr = inst.bind(py).getattr("__dict__").unwrap();
assert_eq!(read_slot(), dict_attr.as_ptr());
});
}
// If the base class has dict support, child class also has dict
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[pyclass(extends=DunderDictSupport)]
struct InheritDict {
_value: usize,
}
#[test]
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
fn inherited_dict() {
Python::attach(|py| {
let initializer = PyClassInitializer::from(DunderDictSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
})
.add_subclass(InheritDict { _value: 0 });
let inst = Py::new(py, initializer).unwrap();
py_run!(
py,
inst,
r#"
inst.a = 1
assert inst.a == 1
"#
);
});
}
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[pyclass(weakref, dict)]
struct WeakRefDunderDictSupport {
// Make sure that weaklist_offset runs with non-zero sized Self
_pad: [u8; 32],
}
#[test]
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
fn weakref_dunder_dict_support() {
Python::attach(|py| {
let inst = Py::new(
py,
WeakRefDunderDictSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
},
)
.unwrap();
py_run!(
py,
inst,
"import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1"
);
});
}
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[pyclass(weakref, subclass)]
struct WeakRefSupport {
_pad: [u8; 32],
}
#[test]
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
fn weakref_support() {
Python::attach(|py| {
let inst = Py::new(
py,
WeakRefSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
},
)
.unwrap();
py_run!(
py,
inst,
"import weakref; assert weakref.ref(inst)() is inst"
);
});
}
// If the base class has weakref support, child class also has weakref.
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[pyclass(extends=WeakRefSupport)]
struct InheritWeakRef {
_value: usize,
}
#[test]
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
fn inherited_weakref() {
Python::attach(|py| {
let initializer = PyClassInitializer::from(WeakRefSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
})
.add_subclass(InheritWeakRef { _value: 0 });
let inst = Py::new(py, initializer).unwrap();
py_run!(
py,
inst,
"import weakref; assert weakref.ref(inst)() is inst"
);
});
}
#[test]
fn access_frozen_class_without_gil() {
use std::sync::atomic::{AtomicUsize, Ordering};
#[pyclass(frozen)]
struct FrozenCounter {
value: AtomicUsize,
}
let py_counter: Py<FrozenCounter> = Python::attach(|py| {
let counter = FrozenCounter {
value: AtomicUsize::new(0),
};
let cell = Bound::new(py, counter).unwrap();
cell.get().value.fetch_add(1, Ordering::Relaxed);
cell.into()
});
assert_eq!(py_counter.get().value.load(Ordering::Relaxed), 1);
Python::attach(move |_py| drop(py_counter));
}
#[test]
#[cfg_attr(target_arch = "wasm32", ignore)]
fn drop_unsendable_elsewhere() {
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::thread::spawn;
use test_utils::UnraisableCapture;
#[pyclass(unsendable)]
struct Unsendable {
dropped: Arc<AtomicBool>,
}
impl Drop for Unsendable {
fn drop(&mut self) {
self.dropped.store(true, Ordering::SeqCst);
}
}
Python::attach(|py| {
let (err, object) = UnraisableCapture::enter(py, |capture| {
let dropped = Arc::new(AtomicBool::new(false));
let unsendable = Py::new(
py,
Unsendable {
dropped: dropped.clone(),
},
)
.unwrap();
py.detach(|| {
spawn(move || {
Python::attach(move |py| {
drop(unsendable);
// On the free-threaded build, dropping an object on its non-origin thread
// will not immediately drop it because the refcounts need to be merged.
//
// Force GC to ensure the drop happens now on the wrong thread.
py.run(c"import gc; gc.collect()", None, None).unwrap();
});
})
.join()
.unwrap();
});
assert!(!dropped.load(Ordering::SeqCst));
capture.take_capture().unwrap()
});
assert_eq!(err.to_string(), "RuntimeError: test_class_basics::drop_unsendable_elsewhere::Unsendable is unsendable, but is being dropped on another thread");
assert!(object.is_none());
});
}
#[test]
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
fn test_unsendable_dict() {
#[pyclass(dict, unsendable)]
struct UnsendableDictClass {}
#[pymethods]
impl UnsendableDictClass {
#[new]
fn new() -> Self {
UnsendableDictClass {}
}
}
Python::attach(|py| {
let inst = Py::new(py, UnsendableDictClass {}).unwrap();
py_run!(py, inst, "assert inst.__dict__ == {}");
});
}
#[test]
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
fn test_unsendable_dict_with_weakref() {
#[pyclass(dict, unsendable, weakref)]
struct UnsendableDictClassWithWeakRef {}
#[pymethods]
impl UnsendableDictClassWithWeakRef {
#[new]
fn new() -> Self {
Self {}
}
}
Python::attach(|py| {
let inst = Py::new(py, UnsendableDictClassWithWeakRef {}).unwrap();
py_run!(py, inst, "assert inst.__dict__ == {}");
py_run!(
py,
inst,
"import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1"
);
});
}
#[cfg(Py_3_9)]
#[pyclass(generic)]
struct ClassWithRuntimeParametrization {
#[pyo3(get, set)]
value: Py<PyAny>,
}
#[cfg(Py_3_9)]
#[pymethods]
impl ClassWithRuntimeParametrization {
#[new]
fn new(value: Py<PyAny>) -> ClassWithRuntimeParametrization {
Self { value }
}
}
#[test]
#[cfg(Py_3_9)]
fn test_runtime_parametrization() {
Python::attach(|py| {
let ty = py.get_type::<ClassWithRuntimeParametrization>();
py_assert!(py, ty, "ty[int] == ty.__class_getitem__((int,))");
py_run!(
py,
ty,
"import types;
assert ty.__class_getitem__((int,)) == types.GenericAlias(ty, (int,))"
);
});
}
+286
View File
@@ -0,0 +1,286 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
mod test_utils;
#[pyclass(eq, skip_from_py_object)]
#[derive(Debug, Clone, PartialEq)]
pub enum MyEnum {
Variant,
OtherVariant,
}
#[pyclass(eq, ord, skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
pub enum MyEnumOrd {
Variant,
OtherVariant,
}
#[test]
fn test_enum_eq_enum() {
Python::attach(|py| {
let var1 = Py::new(py, MyEnum::Variant).unwrap();
let var2 = Py::new(py, MyEnum::Variant).unwrap();
let other_var = Py::new(py, MyEnum::OtherVariant).unwrap();
py_assert!(py, var1 var2, "var1 == var2");
py_assert!(py, var1 other_var, "var1 != other_var");
py_assert!(py, var1 var2, "(var1 != var2) == False");
})
}
#[test]
fn test_enum_eq_incomparable() {
Python::attach(|py| {
let var1 = Py::new(py, MyEnum::Variant).unwrap();
py_assert!(py, var1, "(var1 == 'foo') == False");
py_assert!(py, var1, "(var1 != 'foo') == True");
})
}
#[test]
fn test_enum_ord_comparable_opt_in_only() {
Python::attach(|py| {
let var1 = Py::new(py, MyEnum::Variant).unwrap();
let var2 = Py::new(py, MyEnum::OtherVariant).unwrap();
// ordering on simple enums if opt in only, thus raising an error below
py_expect_exception!(py, var1 var2, "(var1 > var2) == False", PyTypeError);
})
}
#[test]
fn test_simple_enum_ord_comparable() {
Python::attach(|py| {
let var1 = Py::new(py, MyEnumOrd::Variant).unwrap();
let var2 = Py::new(py, MyEnumOrd::OtherVariant).unwrap();
let var3 = Py::new(py, MyEnumOrd::OtherVariant).unwrap();
py_assert!(py, var1 var2, "(var1 > var2) == False");
py_assert!(py, var1 var2, "(var1 < var2) == True");
py_assert!(py, var1 var2, "(var1 >= var2) == False");
py_assert!(py, var2 var3, "(var3 >= var2) == True");
py_assert!(py, var1 var2, "(var1 <= var2) == True");
})
}
#[pyclass(eq, ord, skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
pub enum MyComplexEnumOrd {
Variant(i32),
OtherVariant(String),
}
#[pyclass(eq, ord, skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
pub enum MyComplexEnumOrd2 {
Variant { msg: String, idx: u32 },
OtherVariant { name: String, idx: u32 },
}
#[test]
fn test_complex_enum_ord_comparable() {
Python::attach(|py| {
let var1 = Py::new(py, MyComplexEnumOrd::Variant(-2)).unwrap();
let var2 = Py::new(py, MyComplexEnumOrd::Variant(5)).unwrap();
let var3 = Py::new(py, MyComplexEnumOrd::OtherVariant("a".to_string())).unwrap();
let var4 = Py::new(py, MyComplexEnumOrd::OtherVariant("b".to_string())).unwrap();
py_assert!(py, var1 var2, "(var1 > var2) == False");
py_assert!(py, var1 var2, "(var1 < var2) == True");
py_assert!(py, var1 var2, "(var1 >= var2) == False");
py_assert!(py, var1 var2, "(var1 <= var2) == True");
py_assert!(py, var1 var3, "(var1 >= var3) == False");
py_assert!(py, var1 var3, "(var1 <= var3) == True");
py_assert!(py, var3 var4, "(var3 >= var4) == False");
py_assert!(py, var3 var4, "(var3 <= var4) == True");
let var5 = Py::new(
py,
MyComplexEnumOrd2::Variant {
msg: "hello".to_string(),
idx: 1,
},
)
.unwrap();
let var6 = Py::new(
py,
MyComplexEnumOrd2::Variant {
msg: "hello".to_string(),
idx: 1,
},
)
.unwrap();
let var7 = Py::new(
py,
MyComplexEnumOrd2::Variant {
msg: "goodbye".to_string(),
idx: 7,
},
)
.unwrap();
let var8 = Py::new(
py,
MyComplexEnumOrd2::Variant {
msg: "about".to_string(),
idx: 0,
},
)
.unwrap();
let var9 = Py::new(
py,
MyComplexEnumOrd2::OtherVariant {
name: "albert".to_string(),
idx: 1,
},
)
.unwrap();
py_assert!(py, var5 var6, "(var5 == var6) == True");
py_assert!(py, var5 var6, "(var5 <= var6) == True");
py_assert!(py, var6 var7, "(var6 <= var7) == False");
py_assert!(py, var6 var7, "(var6 >= var7) == True");
py_assert!(py, var5 var8, "(var5 > var8) == True");
py_assert!(py, var8 var9, "(var9 > var8) == True");
})
}
#[pyclass(eq, ord, skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
pub struct Point {
x: i32,
y: i32,
z: i32,
}
#[test]
fn test_struct_numeric_ord_comparable() {
Python::attach(|py| {
let var1 = Py::new(py, Point { x: 10, y: 2, z: 3 }).unwrap();
let var2 = Py::new(py, Point { x: 2, y: 2, z: 3 }).unwrap();
let var3 = Py::new(py, Point { x: 1, y: 22, z: 4 }).unwrap();
let var4 = Py::new(py, Point { x: 1, y: 3, z: 4 }).unwrap();
let var5 = Py::new(py, Point { x: 1, y: 3, z: 4 }).unwrap();
py_assert!(py, var1 var2, "(var1 > var2) == True");
py_assert!(py, var1 var2, "(var1 <= var2) == False");
py_assert!(py, var2 var3, "(var3 < var2) == True");
py_assert!(py, var3 var4, "(var3 > var4) == True");
py_assert!(py, var4 var5, "(var4 == var5) == True");
py_assert!(py, var3 var5, "(var3 != var5) == True");
})
}
#[pyclass(eq, ord, skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
pub struct Person {
surname: String,
given_name: String,
}
#[test]
fn test_struct_string_ord_comparable() {
Python::attach(|py| {
let var1 = Py::new(
py,
Person {
surname: "zzz".to_string(),
given_name: "bob".to_string(),
},
)
.unwrap();
let var2 = Py::new(
py,
Person {
surname: "aaa".to_string(),
given_name: "sally".to_string(),
},
)
.unwrap();
let var3 = Py::new(
py,
Person {
surname: "eee".to_string(),
given_name: "qqq".to_string(),
},
)
.unwrap();
let var4 = Py::new(
py,
Person {
surname: "ddd".to_string(),
given_name: "aaa".to_string(),
},
)
.unwrap();
py_assert!(py, var1 var2, "(var1 > var2) == True");
py_assert!(py, var1 var2, "(var1 <= var2) == False");
py_assert!(py, var1 var3, "(var1 >= var3) == True");
py_assert!(py, var2 var3, "(var2 >= var3) == False");
py_assert!(py, var3 var4, "(var3 >= var4) == True");
py_assert!(py, var3 var4, "(var3 != var4) == True");
})
}
#[pyclass(eq, ord, skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Record {
name: String,
title: String,
idx: u32,
}
impl PartialOrd for Record {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.idx.partial_cmp(&other.idx).unwrap())
}
}
#[test]
fn test_struct_custom_ord_comparable() {
Python::attach(|py| {
let var1 = Py::new(
py,
Record {
name: "zzz".to_string(),
title: "bbb".to_string(),
idx: 9,
},
)
.unwrap();
let var2 = Py::new(
py,
Record {
name: "ddd".to_string(),
title: "aaa".to_string(),
idx: 1,
},
)
.unwrap();
let var3 = Py::new(
py,
Record {
name: "vvv".to_string(),
title: "ggg".to_string(),
idx: 19,
},
)
.unwrap();
let var4 = Py::new(
py,
Record {
name: "vvv".to_string(),
title: "ggg".to_string(),
idx: 19,
},
)
.unwrap();
py_assert!(py, var1 var2, "(var1 > var2) == True");
py_assert!(py, var1 var2, "(var1 <= var2) == False");
py_assert!(py, var1 var3, "(var1 >= var3) == False");
py_assert!(py, var2 var3, "(var2 >= var3) == False");
py_assert!(py, var3 var4, "(var3 == var4) == True");
py_assert!(py, var2 var4, "(var2 != var4) == True");
})
}
+159
View File
@@ -0,0 +1,159 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
#[macro_use]
mod test_utils;
#[pyclass(from_py_object)]
#[derive(Clone, Debug, PartialEq)]
struct Cloneable {
x: i32,
}
#[test]
fn test_cloneable_pyclass() {
let c = Cloneable { x: 10 };
Python::attach(|py| {
let py_c = Py::new(py, c.clone()).unwrap();
let c2: Cloneable = py_c.extract(py).unwrap();
assert_eq!(c, c2);
{
let rc: PyRef<'_, Cloneable> = py_c.extract(py).unwrap();
assert_eq!(&c, &*rc);
// Drops PyRef before taking PyRefMut
}
let mrc: PyRefMut<'_, Cloneable> = py_c.extract(py).unwrap();
assert_eq!(&c, &*mrc);
});
}
#[pyclass(subclass)]
#[derive(Default)]
struct BaseClass {
value: i32,
}
#[pymethods]
impl BaseClass {
fn foo(&self) -> &'static str {
"BaseClass"
}
}
#[pyclass(extends=BaseClass)]
struct SubClass {}
#[pymethods]
impl SubClass {
fn foo(&self) -> &'static str {
"SubClass"
}
}
#[pyclass]
struct PolymorphicContainer {
#[pyo3(get, set)]
inner: Py<BaseClass>,
}
#[test]
fn test_polymorphic_container_stores_base_class() {
Python::attach(|py| {
let p = Py::new(
py,
PolymorphicContainer {
inner: Py::new(py, BaseClass::default()).unwrap(),
},
)
.unwrap();
py_assert!(py, p, "p.inner.foo() == 'BaseClass'");
});
}
#[test]
fn test_polymorphic_container_stores_sub_class() {
Python::attach(|py| {
let p = Py::new(
py,
PolymorphicContainer {
inner: Py::new(py, BaseClass::default()).unwrap(),
},
)
.unwrap();
p.bind(py)
.setattr(
"inner",
Py::new(
py,
PyClassInitializer::from(BaseClass::default()).add_subclass(SubClass {}),
)
.unwrap(),
)
.unwrap();
py_assert!(py, p, "p.inner.foo() == 'SubClass'");
});
}
#[test]
fn test_polymorphic_container_does_not_accept_other_types() {
Python::attach(|py| {
let p = Py::new(
py,
PolymorphicContainer {
inner: Py::new(py, BaseClass::default()).unwrap(),
},
)
.unwrap();
let setattr = |value: Bound<'_, PyAny>| p.bind(py).setattr("inner", value);
assert!(setattr(1i32.into_pyobject(py).unwrap().into_any()).is_err());
assert!(setattr(py.None().into_bound(py)).is_err());
assert!(setattr((1i32, 2i32).into_pyobject(py).unwrap().into_any()).is_err());
});
}
#[test]
fn test_pyref_as_base() {
Python::attach(|py| {
let initializer =
PyClassInitializer::from(BaseClass { value: 120 }).add_subclass(SubClass {});
let cell = Bound::new(py, initializer).unwrap();
// First try PyRefMut
let sub: PyRefMut<'_, SubClass> = cell.borrow_mut();
let mut base: PyRefMut<'_, BaseClass> = sub.into_super();
assert_eq!(120, base.value);
base.value = 999;
assert_eq!(999, base.value);
drop(base);
// Repeat for PyRef
let sub: PyRef<'_, SubClass> = cell.borrow();
let base: PyRef<'_, BaseClass> = sub.into_super();
assert_eq!(999, base.value);
});
}
#[test]
fn test_pycell_deref() {
Python::attach(|py| {
let initializer =
PyClassInitializer::from(BaseClass { value: 120 }).add_subclass(SubClass {});
let obj = Bound::new(py, initializer).unwrap();
// Should be able to deref as PyAny
assert_eq!(
obj.call_method0("foo")
.and_then(|e| e.extract::<String>())
.unwrap(),
"SubClass"
);
});
}
+178
View File
@@ -0,0 +1,178 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::py_run;
use std::fmt::{Display, Formatter};
mod test_utils;
#[pyclass(eq, str)]
#[derive(Debug, PartialEq)]
pub enum MyEnum2 {
Variant,
OtherVariant,
}
impl Display for MyEnum2 {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
#[pyclass(eq, str)]
#[derive(Debug, PartialEq)]
pub enum MyEnum3 {
#[pyo3(name = "AwesomeVariant")]
Variant,
OtherVariant,
}
impl Display for MyEnum3 {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let variant = match self {
MyEnum3::Variant => "AwesomeVariant",
MyEnum3::OtherVariant => "OtherVariant",
};
write!(f, "MyEnum.{variant}")
}
}
#[test]
fn test_enum_class_fmt() {
Python::attach(|py| {
let var2 = Py::new(py, MyEnum2::Variant).unwrap();
let var3 = Py::new(py, MyEnum3::Variant).unwrap();
let var4 = Py::new(py, MyEnum3::OtherVariant).unwrap();
py_assert!(py, var2, "str(var2) == 'Variant'");
py_assert!(py, var3, "str(var3) == 'MyEnum.AwesomeVariant'");
py_assert!(py, var4, "str(var4) == 'MyEnum.OtherVariant'");
})
}
#[pyclass(str = "X: {x}, Y: {y}, Z: {z}", skip_from_py_object)]
#[derive(PartialEq, Eq, Clone, PartialOrd)]
pub struct Point {
x: i32,
y: i32,
z: i32,
}
#[test]
fn test_custom_struct_custom_str() {
Python::attach(|py| {
let var1 = Py::new(py, Point { x: 1, y: 2, z: 3 }).unwrap();
py_assert!(py, var1, "str(var1) == 'X: 1, Y: 2, Z: 3'");
})
}
#[pyclass(str, skip_from_py_object)]
#[derive(PartialEq, Eq, Clone, PartialOrd)]
pub struct Point2 {
x: i32,
y: i32,
z: i32,
}
impl Display for Point2 {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {}, {})", self.x, self.y, self.z)
}
}
#[test]
fn test_struct_str() {
Python::attach(|py| {
let var1 = Py::new(py, Point2 { x: 1, y: 2, z: 3 }).unwrap();
py_assert!(py, var1, "str(var1) == '(1, 2, 3)'");
})
}
#[pyclass(str)]
#[derive(PartialEq, Debug)]
enum ComplexEnumWithStr {
A(u32),
B { msg: String },
}
impl Display for ComplexEnumWithStr {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
#[test]
fn test_custom_complex_enum_str() {
Python::attach(|py| {
let var1 = Py::new(py, ComplexEnumWithStr::A(45)).unwrap();
let var2 = Py::new(
py,
ComplexEnumWithStr::B {
msg: "Hello".to_string(),
},
)
.unwrap();
py_assert!(py, var1, "str(var1) == 'A(45)'");
py_assert!(py, var2, "str(var2) == 'B { msg: \"Hello\" }'");
})
}
#[pyclass(str = "{0}, {1}, {2}")]
#[derive(PartialEq)]
struct Coord(u32, u32, u32);
#[pyclass(str = "{{{0}, {1}, {2}}}")]
#[derive(PartialEq)]
struct Coord2(u32, u32, u32);
#[test]
fn test_str_representation_by_position() {
Python::attach(|py| {
let var1 = Py::new(py, Coord(1, 2, 3)).unwrap();
let var2 = Py::new(py, Coord2(1, 2, 3)).unwrap();
py_assert!(py, var1, "str(var1) == '1, 2, 3'");
py_assert!(py, var2, "str(var2) == '{1, 2, 3}'");
})
}
#[pyclass(str = "name: {name}: {name}, idn: {idn:03} with message: {msg}")]
#[derive(PartialEq, Debug)]
struct Point4 {
name: String,
msg: String,
idn: u32,
}
#[test]
fn test_mixed_and_repeated_str_formats() {
Python::attach(|py| {
let var1 = Py::new(
py,
Point4 {
name: "aaa".to_string(),
msg: "hello".to_string(),
idn: 1,
},
)
.unwrap();
py_run!(
py,
var1,
r#"
assert str(var1) == 'name: aaa: aaa, idn: 001 with message: hello'
"#
);
})
}
#[pyclass(str = "type: {r#type}")]
struct Foo {
r#type: u32,
}
#[test]
fn test_raw_identifier_struct_custom_str() {
Python::attach(|py| {
let var1 = Py::new(py, Foo { r#type: 3 }).unwrap();
py_assert!(py, var1, "str(var1) == 'type: 3'");
})
}
+110
View File
@@ -0,0 +1,110 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
#[pyclass(subclass)]
struct Base {
num: u32,
}
#[pymethods]
impl Base {
#[new]
fn new() -> Self {
Self { num: 0 }
}
fn __init__(&mut self) {
self.num += 42
}
}
#[test]
fn test_base_init() {
Python::attach(|py| {
let typeobj = py.get_type::<Base>();
let obj = typeobj.call((), None).unwrap().cast_into::<Base>().unwrap();
// check __init__ was called
assert_eq!(obj.borrow().num, 42);
});
}
#[pyclass(extends=Base)]
struct SubWithoutInit;
#[pymethods]
impl SubWithoutInit {
#[new]
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(Base::new()).add_subclass(Self)
}
}
#[test]
fn test_subclass_without_init_calls_base_init() {
Python::attach(|py| {
let typeobj = py.get_type::<SubWithoutInit>();
let obj = typeobj
.call((), None)
.unwrap()
.cast_into::<SubWithoutInit>()
.unwrap();
// check Base.__init__ was called
assert_eq!(obj.as_super().borrow().num, 42);
});
}
#[pyclass(extends=Base)]
struct SubWithInit;
#[pymethods]
impl SubWithInit {
#[new]
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(Base::new()).add_subclass(Self)
}
fn __init__(mut slf: pyo3::PyClassGuardMut<'_, Self>) {
slf.as_super().__init__(); // need to call super __init__ manually
slf.as_super().num += 1;
}
}
#[test]
fn test_subclass_with_init() {
Python::attach(|py| {
let typeobj = py.get_type::<SubWithInit>();
let obj = typeobj
.call((), None)
.unwrap()
.cast_into::<SubWithInit>()
.unwrap();
// check SubWithInit.__init__ was called, and Base.__init__ was only called once (through
// SubWithInit.__init__)
assert_eq!(obj.as_super().borrow().num, 43);
});
}
#[test]
fn test_arbitrary_object() {
#[pyclass]
struct A {}
#[pyclass]
struct B {}
#[pymethods]
impl A {
#[new]
fn __new__() -> B {
B {}
}
}
Python::attach(|py| {
let typeobj = py.get_type::<A>();
let obj = typeobj.call((), None).unwrap();
assert!(obj.is_instance_of::<B>());
});
}
+325
View File
@@ -0,0 +1,325 @@
#![cfg(feature = "macros")]
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::sync::PyOnceLock;
use pyo3::types::IntoPyDict;
#[pyclass]
struct EmptyClassWithNew {}
#[pymethods]
impl EmptyClassWithNew {
#[new]
fn new() -> EmptyClassWithNew {
EmptyClassWithNew {}
}
}
#[test]
fn empty_class_with_new() {
Python::attach(|py| {
let typeobj = py.get_type::<EmptyClassWithNew>();
assert!(typeobj
.call((), None)
.unwrap()
.cast::<EmptyClassWithNew>()
.is_ok());
// Calling with arbitrary args or kwargs is not ok
assert!(typeobj.call(("some", "args"), None).is_err());
assert!(typeobj
.call((), Some(&[("some", "kwarg")].into_py_dict(py).unwrap()))
.is_err());
});
}
#[pyclass]
struct UnitClassWithNew;
#[pymethods]
impl UnitClassWithNew {
#[new]
fn new() -> Self {
Self
}
}
#[test]
fn unit_class_with_new() {
Python::attach(|py| {
let typeobj = py.get_type::<UnitClassWithNew>();
assert!(typeobj
.call((), None)
.unwrap()
.cast::<UnitClassWithNew>()
.is_ok());
});
}
#[pyclass]
struct TupleClassWithNew(i32);
#[pymethods]
impl TupleClassWithNew {
#[new]
fn new(arg: i32) -> Self {
Self(arg)
}
}
#[test]
fn tuple_class_with_new() {
Python::attach(|py| {
let typeobj = py.get_type::<TupleClassWithNew>();
let wrp = typeobj.call((42,), None).unwrap();
let obj = wrp.cast::<TupleClassWithNew>().unwrap();
let obj_ref = obj.borrow();
assert_eq!(obj_ref.0, 42);
});
}
#[pyclass]
#[derive(Debug)]
struct NewWithOneArg {
data: i32,
}
#[pymethods]
impl NewWithOneArg {
#[new]
fn new(arg: i32) -> NewWithOneArg {
NewWithOneArg { data: arg }
}
}
#[test]
fn new_with_one_arg() {
Python::attach(|py| {
let typeobj = py.get_type::<NewWithOneArg>();
let wrp = typeobj.call((42,), None).unwrap();
let obj = wrp.cast::<NewWithOneArg>().unwrap();
let obj_ref = obj.borrow();
assert_eq!(obj_ref.data, 42);
});
}
#[pyclass]
struct NewWithTwoArgs {
data1: i32,
data2: i32,
}
#[pymethods]
impl NewWithTwoArgs {
#[new]
fn new(arg1: i32, arg2: i32) -> Self {
NewWithTwoArgs {
data1: arg1,
data2: arg2,
}
}
}
#[test]
fn new_with_two_args() {
Python::attach(|py| {
let typeobj = py.get_type::<NewWithTwoArgs>();
let wrp = typeobj
.call((10, 20), None)
.map_err(|e| e.display(py))
.unwrap();
let obj = wrp.cast::<NewWithTwoArgs>().unwrap();
let obj_ref = obj.borrow();
assert_eq!(obj_ref.data1, 10);
assert_eq!(obj_ref.data2, 20);
});
}
#[pyclass(subclass)]
struct SuperClass {
#[pyo3(get)]
from_rust: bool,
}
#[pymethods]
impl SuperClass {
#[new]
fn new() -> Self {
SuperClass { from_rust: true }
}
}
/// Checks that `subclass.__new__` works correctly.
/// See https://github.com/PyO3/pyo3/issues/947 for the corresponding bug.
#[test]
fn subclass_new() {
Python::attach(|py| {
let super_cls = py.get_type::<SuperClass>();
let source = pyo3_ffi::c_str!(
r#"
class Class(SuperClass):
def __new__(cls):
return super().__new__(cls) # This should return an instance of Class
@property
def from_rust(self):
return False
c = Class()
assert c.from_rust is False
"#
);
let globals = PyModule::import(py, "__main__").unwrap().dict();
globals.set_item("SuperClass", super_cls).unwrap();
py.run(source, Some(&globals), None)
.map_err(|e| e.display(py))
.unwrap();
});
}
#[pyclass]
#[derive(Debug)]
struct NewWithCustomError {}
struct CustomError;
impl From<CustomError> for PyErr {
fn from(_error: CustomError) -> PyErr {
PyValueError::new_err("custom error")
}
}
#[pymethods]
impl NewWithCustomError {
#[new]
fn new() -> Result<NewWithCustomError, CustomError> {
Err(CustomError)
}
}
#[test]
fn new_with_custom_error() {
Python::attach(|py| {
let typeobj = py.get_type::<NewWithCustomError>();
let err = typeobj.call0().unwrap_err();
assert_eq!(err.to_string(), "ValueError: custom error");
});
}
#[pyclass]
struct NewExisting {
#[pyo3(get)]
num: usize,
}
#[pymethods]
impl NewExisting {
#[new]
fn new(py: pyo3::Python<'_>, val: usize) -> pyo3::Py<NewExisting> {
static PRE_BUILT: PyOnceLock<[pyo3::Py<NewExisting>; 2]> = PyOnceLock::new();
let existing = PRE_BUILT.get_or_init(py, || {
[
pyo3::Py::new(py, NewExisting { num: 0 }).unwrap(),
pyo3::Py::new(py, NewExisting { num: 1 }).unwrap(),
]
});
if val < existing.len() {
return existing[val].clone_ref(py);
}
pyo3::Py::new(py, NewExisting { num: val }).unwrap()
}
}
#[test]
fn test_new_existing() {
Python::attach(|py| {
let typeobj = py.get_type::<NewExisting>();
let obj1 = typeobj.call1((0,)).unwrap();
let obj2 = typeobj.call1((0,)).unwrap();
let obj3 = typeobj.call1((1,)).unwrap();
let obj4 = typeobj.call1((1,)).unwrap();
let obj5 = typeobj.call1((2,)).unwrap();
let obj6 = typeobj.call1((2,)).unwrap();
assert_eq!(obj1.getattr("num").unwrap().extract::<u32>().unwrap(), 0);
assert_eq!(obj2.getattr("num").unwrap().extract::<u32>().unwrap(), 0);
assert_eq!(obj3.getattr("num").unwrap().extract::<u32>().unwrap(), 1);
assert_eq!(obj4.getattr("num").unwrap().extract::<u32>().unwrap(), 1);
assert_eq!(obj5.getattr("num").unwrap().extract::<u32>().unwrap(), 2);
assert_eq!(obj6.getattr("num").unwrap().extract::<u32>().unwrap(), 2);
assert!(obj1.is(&obj2));
assert!(obj3.is(&obj4));
assert!(!obj1.is(&obj3));
assert!(!obj1.is(&obj5));
assert!(!obj5.is(&obj6));
});
}
#[pyclass]
struct NewReturnsPy;
#[pymethods]
impl NewReturnsPy {
#[new]
fn new(py: Python<'_>) -> PyResult<Py<NewReturnsPy>> {
Py::new(py, NewReturnsPy)
}
}
#[test]
fn test_new_returns_py() {
Python::attach(|py| {
let type_ = py.get_type::<NewReturnsPy>();
let obj = type_.call0().unwrap();
assert!(obj.is_exact_instance_of::<NewReturnsPy>());
})
}
#[pyclass]
struct NewReturnsBound;
#[pymethods]
impl NewReturnsBound {
#[new]
fn new(py: Python<'_>) -> PyResult<Bound<'_, NewReturnsBound>> {
Bound::new(py, NewReturnsBound)
}
}
#[test]
fn test_new_returns_bound() {
Python::attach(|py| {
let type_ = py.get_type::<NewReturnsBound>();
let obj = type_.call0().unwrap();
assert!(obj.is_exact_instance_of::<NewReturnsBound>());
})
}
#[pyo3::pyclass]
struct NewClassMethod {
#[pyo3(get)]
cls: pyo3::Py<PyAny>,
}
#[pyo3::pymethods]
impl NewClassMethod {
#[new]
#[classmethod]
fn new(cls: &pyo3::Bound<'_, pyo3::types::PyType>) -> Self {
Self {
cls: cls.clone().into_any().unbind(),
}
}
}
#[test]
fn test_new_class_method() {
pyo3::Python::attach(|py| {
let cls = py.get_type::<NewClassMethod>();
pyo3::py_run!(py, cls, "assert cls().cls is cls");
});
}
+480
View File
@@ -0,0 +1,480 @@
#![cfg(feature = "experimental-async")]
#![cfg(not(target_arch = "wasm32"))]
use std::{ffi::CString, task::Poll, thread, time::Duration};
use futures::{channel::oneshot, future::poll_fn, FutureExt};
#[cfg(not(target_has_atomic = "64"))]
use portable_atomic::{AtomicBool, Ordering};
use pyo3::{
coroutine::CancelHandle,
prelude::*,
py_run,
types::{IntoPyDict, PyDict, PyTuple, PyType},
};
#[cfg(target_has_atomic = "64")]
use std::sync::atomic::{AtomicBool, Ordering};
mod test_utils;
fn handle_windows(test: &str) -> String {
let set_event_loop_policy = r#"
import asyncio, sys
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
"#;
pyo3::impl_::unindent::unindent(set_event_loop_policy) + &pyo3::impl_::unindent::unindent(test)
}
#[test]
fn noop_coroutine() {
#[pyfunction]
async fn noop() -> usize {
42
}
Python::attach(|py| {
let noop = wrap_pyfunction!(noop, py).unwrap();
let test = "import asyncio; assert asyncio.run(noop()) == 42";
py_run!(py, noop, &handle_windows(test));
})
}
#[test]
fn test_async_function_returns_unit_none() {
#[pyfunction]
async fn returns_unit() {}
Python::attach(|py| {
let returns_unit = wrap_pyfunction!(returns_unit, py).unwrap();
let test = "import asyncio; assert asyncio.run(returns_unit()) is None";
pyo3::py_run!(py, returns_unit, &handle_windows(test));
});
}
#[test]
fn test_coroutine_qualname() {
#[pyfunction]
async fn my_fn() {}
#[pyclass]
struct MyClass;
#[pymethods]
impl MyClass {
#[new]
fn new() -> Self {
Self
}
// TODO use &self when possible
async fn my_method(_self: Py<Self>) {}
#[classmethod]
async fn my_classmethod(_cls: Py<PyType>) {}
#[staticmethod]
async fn my_staticmethod() {}
}
Python::attach(|py| {
let test = r#"
for coro, name, qualname in [
(my_fn(), "my_fn", "my_fn"),
(MyClass().my_method(), "my_method", "MyClass.my_method"),
#(MyClass().my_classmethod(), "my_classmethod", "MyClass.my_classmethod"),
(MyClass.my_staticmethod(), "my_staticmethod", "MyClass.my_staticmethod"),
]:
assert coro.__name__ == name and coro.__qualname__ == qualname
"#;
let locals = [
("my_fn", wrap_pyfunction!(my_fn, py).unwrap().as_any()),
("MyClass", py.get_type::<MyClass>().as_any()),
]
.into_py_dict(py)
.unwrap();
py_run!(py, *locals, &handle_windows(test));
})
}
#[test]
fn sleep_0_like_coroutine() {
#[pyfunction]
async fn sleep_0() -> usize {
let mut waken = false;
poll_fn(|cx| {
if !waken {
cx.waker().wake_by_ref();
waken = true;
return Poll::Pending;
}
Poll::Ready(42)
})
.await
}
Python::attach(|py| {
let sleep_0 = wrap_pyfunction!(sleep_0, py).unwrap();
let test = "import asyncio; assert asyncio.run(sleep_0()) == 42";
py_run!(py, sleep_0, &handle_windows(test));
})
}
#[pyfunction]
async fn sleep(seconds: f64) -> usize {
let (tx, rx) = oneshot::channel();
thread::spawn(move || {
thread::sleep(Duration::from_secs_f64(seconds));
tx.send(42).unwrap();
});
rx.await.unwrap()
}
#[test]
fn sleep_coroutine() {
Python::attach(|py| {
let sleep = wrap_pyfunction!(sleep, py).unwrap();
let test = r#"import asyncio; assert asyncio.run(sleep(0.1)) == 42"#;
py_run!(py, sleep, &handle_windows(test));
})
}
#[pyfunction]
async fn return_tuple() -> (usize, usize) {
(42, 43)
}
#[test]
fn tuple_coroutine() {
Python::attach(|py| {
let func = wrap_pyfunction!(return_tuple, py).unwrap();
let test = r#"import asyncio; assert asyncio.run(func()) == (42, 43)"#;
py_run!(py, func, &handle_windows(test));
})
}
#[test]
fn cancelled_coroutine() {
Python::attach(|py| {
let sleep = wrap_pyfunction!(sleep, py).unwrap();
let test = r#"
import asyncio
async def main():
task = asyncio.create_task(sleep(999))
await asyncio.sleep(0)
task.cancel()
await task
asyncio.run(main())
"#;
let globals = PyDict::new(py);
globals.set_item("sleep", sleep).unwrap();
let err = py
.run(
&CString::new(pyo3::impl_::unindent::unindent(&handle_windows(test))).unwrap(),
Some(&globals),
None,
)
.unwrap_err();
assert_eq!(
err.value(py).get_type().qualname().unwrap(),
"CancelledError"
);
})
}
#[test]
fn coroutine_cancel_handle() {
#[pyfunction]
async fn cancellable_sleep(
seconds: f64,
#[pyo3(cancel_handle)] mut cancel: CancelHandle,
) -> usize {
futures::select! {
_ = sleep(seconds).fuse() => 42,
_ = cancel.cancelled().fuse() => 0,
}
}
Python::attach(|py| {
let cancellable_sleep = wrap_pyfunction!(cancellable_sleep, py).unwrap();
let test = r#"
import asyncio;
async def main():
task = asyncio.create_task(cancellable_sleep(999))
await asyncio.sleep(0)
task.cancel()
return await task
assert asyncio.run(main()) == 0
"#;
let globals = PyDict::new(py);
globals
.set_item("cancellable_sleep", cancellable_sleep)
.unwrap();
py.run(
&CString::new(pyo3::impl_::unindent::unindent(&handle_windows(test))).unwrap(),
Some(&globals),
None,
)
.unwrap();
})
}
#[test]
fn coroutine_is_cancelled() {
#[pyfunction]
async fn sleep_loop(#[pyo3(cancel_handle)] cancel: CancelHandle) {
while !cancel.is_cancelled() {
sleep(0.001).await;
}
}
Python::attach(|py| {
let sleep_loop = wrap_pyfunction!(sleep_loop, py).unwrap();
let test = r#"
import asyncio;
async def main():
task = asyncio.create_task(sleep_loop())
await asyncio.sleep(0)
task.cancel()
await task
asyncio.run(main())
"#;
let globals = PyDict::new(py);
globals.set_item("sleep_loop", sleep_loop).unwrap();
py.run(
&CString::new(pyo3::impl_::unindent::unindent(&handle_windows(test))).unwrap(),
Some(&globals),
None,
)
.unwrap();
})
}
#[test]
fn coroutine_panic() {
#[pyfunction]
async fn panic() {
panic!("test panic");
}
Python::attach(|py| {
let panic = wrap_pyfunction!(panic, py).unwrap();
let test = r#"
import asyncio
coro = panic()
try:
asyncio.run(coro)
except BaseException as err:
assert type(err).__name__ == "PanicException"
assert str(err) == "test panic"
else:
assert False
try:
coro.send(None)
except RuntimeError as err:
assert str(err) == "cannot reuse already awaited coroutine"
else:
assert False;
"#;
py_run!(py, panic, &handle_windows(test));
})
}
#[test]
fn test_async_method_receiver() {
#[pyclass]
struct Counter(usize);
#[pymethods]
impl Counter {
#[new]
fn new() -> Self {
Self(0)
}
async fn get(&self, resolve: bool) -> usize {
if !resolve {
// hang the future to test borrow checking
std::future::pending().await
}
self.0
}
async fn incr(&mut self, resolve: bool) -> usize {
if !resolve {
// hang the future to test borrow checking
std::future::pending().await
}
self.0 += 1;
self.0
}
}
static IS_DROPPED: AtomicBool = AtomicBool::new(false);
impl Drop for Counter {
fn drop(&mut self) {
IS_DROPPED.store(true, Ordering::SeqCst);
}
}
Python::attach(|py| {
let test = r#"
import asyncio
obj = Counter()
assert asyncio.run(obj.get(True)) == 0
assert asyncio.run(obj.incr(True)) == 1
for left in [obj.get, obj.incr]:
for right in [obj.get, obj.incr]:
# first future will not resolve to hold the borrow
coro1 = left(False)
coro2 = right(True)
try:
asyncio.run(asyncio.gather(coro1, coro2))
except RuntimeError as err:
ran = False
else:
ran = True
if left is obj.incr or right is obj.incr:
assert not ran, "mutable method calls should not run concurrently with other method calls"
"#;
let locals = [("Counter", py.get_type::<Counter>())]
.into_py_dict(py)
.unwrap();
py_run!(py, *locals, test);
});
assert!(IS_DROPPED.load(Ordering::SeqCst));
}
#[test]
fn test_async_method_receiver_with_other_args() {
#[pyclass]
struct Value(i32);
#[pymethods]
impl Value {
#[new]
fn new() -> Self {
Self(0)
}
async fn get_value_plus_with(&self, v1: i32, v2: i32) -> i32 {
self.0 + v1 + v2
}
async fn set_value(&mut self, new_value: i32) -> i32 {
self.0 = new_value;
self.0
}
}
Python::attach(|py| {
let test = r#"
import asyncio
v = Value()
assert asyncio.run(v.get_value_plus_with(3, 0)) == 3
assert asyncio.run(v.set_value(10)) == 10
assert asyncio.run(v.get_value_plus_with(1, 1)) == 12
"#;
let locals = [("Value", py.get_type::<Value>())]
.into_py_dict(py)
.unwrap();
py_run!(py, *locals, test);
});
}
#[test]
fn test_async_fn_borrowed_values() {
#[pyclass]
struct Data {
value: String,
}
#[pymethods]
impl Data {
#[new]
fn new(value: String) -> Self {
Self { value }
}
async fn borrow_value(&self) -> &str {
&self.value
}
async fn borrow_value_or_default<'a>(&'a self, default: &'a str) -> &'a str {
if self.value.is_empty() {
default
} else {
&self.value
}
}
}
Python::attach(|py| {
let test = r#"
import asyncio
v = Data('hello')
assert asyncio.run(v.borrow_value()) == 'hello'
assert asyncio.run(v.borrow_value_or_default('')) == 'hello'
v_empty = Data('')
assert asyncio.run(v_empty.borrow_value_or_default('default')) == 'default'
"#;
let locals = [("Data", py.get_type::<Data>())].into_py_dict(py).unwrap();
py_run!(py, *locals, test);
});
}
#[test]
fn test_async_fn_class_values() {
#[pyclass]
struct Value(i32);
#[pymethods]
impl Value {
#[new]
fn new(x: i32) -> Self {
Self(x)
}
#[getter]
fn value(&self) -> i32 {
self.0
}
}
#[pyfunction]
async fn add_two_values(obj: &Value, obj2: &Value) -> Value {
Value(obj.0 + obj2.0)
}
Python::attach(|py| {
let test = r#"
import asyncio
v1 = Value(1)
v2 = Value(2)
assert asyncio.run(add_two_values(v1, v2)).value == 3
"#;
let locals = [
("Value", py.get_type::<Value>().into_any()),
(
"add_two_values",
wrap_pyfunction!(add_two_values, py).unwrap().into_any(),
),
]
.into_py_dict(py)
.unwrap();
py_run!(py, *locals, test);
});
}
#[test]
fn test_async_function_args_kwargs() {
#[pyfunction(signature = (*args, **kwargs))]
async fn args_kwargs(
args: Py<PyTuple>,
kwargs: Option<Py<PyDict>>,
) -> (Py<PyTuple>, Option<Py<PyDict>>) {
(args, kwargs)
}
Python::attach(|py| {
let test = r#"
import asyncio
assert asyncio.run(args_kwargs(1, 2, a=3, b=4)) == ((1, 2), {'a': 3, 'b': 4})
"#;
let locals = [(
"args_kwargs",
wrap_pyfunction!(args_kwargs, py).unwrap().into_any(),
)]
.into_py_dict(py)
.unwrap();
py_run!(py, *locals, test);
});
}
+226
View File
@@ -0,0 +1,226 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(not(Py_LIMITED_API))]
use pyo3::prelude::*;
use pyo3::types::{IntoPyDict, PyDate, PyDateTime, PyTime, PyTzInfo};
use pyo3_ffi::PyDateTime_IMPORT;
use std::ffi::CString;
fn _get_subclasses<'py>(
py: Python<'py>,
py_type: &str,
args: &str,
) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>)> {
// Import the class from Python and create some subclasses
let datetime = py.import("datetime")?;
let locals = [(py_type, datetime.getattr(py_type)?)]
.into_py_dict(py)
.unwrap();
let make_subclass_py = CString::new(format!("class Subklass({py_type}):\n pass"))?;
let make_sub_subclass_py = c"class SubSubklass(Subklass):\n pass";
py.run(&make_subclass_py, None, Some(&locals))?;
py.run(make_sub_subclass_py, None, Some(&locals))?;
// Construct an instance of the base class
let obj = py.eval(
&CString::new(format!("{py_type}({args})"))?,
None,
Some(&locals),
)?;
// Construct an instance of the subclass
let sub_obj = py.eval(
&CString::new(format!("Subklass({args})"))?,
None,
Some(&locals),
)?;
// Construct an instance of the sub-subclass
let sub_sub_obj = py.eval(
&CString::new(format!("SubSubklass({args})"))?,
None,
Some(&locals),
)?;
Ok((obj, sub_obj, sub_sub_obj))
}
macro_rules! assert_check_exact {
($check_func:ident, $check_func_exact:ident, $obj: expr) => {
unsafe {
use pyo3::ffi::*;
assert_ne!($check_func(($obj).as_ptr()), 0);
assert_ne!($check_func_exact(($obj).as_ptr()), 0);
}
};
}
macro_rules! assert_check_only {
($check_func:ident, $check_func_exact:ident, $obj: expr) => {
unsafe {
use pyo3::ffi::*;
assert_ne!($check_func(($obj).as_ptr()), 0);
assert_eq!($check_func_exact(($obj).as_ptr()), 0);
}
};
}
#[test]
fn test_date_check() {
Python::attach(|py| {
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "date", "2018, 1, 1").unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_exact!(PyDate_Check, PyDate_CheckExact, obj);
assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_obj);
assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_sub_obj);
assert!(obj.is_instance_of::<PyDate>());
assert!(!obj.is_instance_of::<PyTime>());
assert!(!obj.is_instance_of::<PyDateTime>());
});
}
#[test]
fn test_time_check() {
Python::attach(|py| {
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "time", "12, 30, 15").unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_exact!(PyTime_Check, PyTime_CheckExact, obj);
assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_obj);
assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_sub_obj);
assert!(!obj.is_instance_of::<PyDate>());
assert!(obj.is_instance_of::<PyTime>());
assert!(!obj.is_instance_of::<PyDateTime>());
});
}
#[test]
fn test_datetime_check() {
Python::attach(|py| {
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "datetime", "2018, 1, 1, 13, 30, 15")
.map_err(|e| e.display(py))
.unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_only!(PyDate_Check, PyDate_CheckExact, obj);
assert_check_exact!(PyDateTime_Check, PyDateTime_CheckExact, obj);
assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_obj);
assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_sub_obj);
assert!(obj.is_instance_of::<PyDate>());
assert!(!obj.is_instance_of::<PyTime>());
assert!(obj.is_instance_of::<PyDateTime>());
});
}
#[test]
fn test_delta_check() {
Python::attach(|py| {
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "timedelta", "1, -3").unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_exact!(PyDelta_Check, PyDelta_CheckExact, obj);
assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_obj);
assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_sub_obj);
});
}
#[test]
fn test_datetime_utc() {
use assert_approx_eq::assert_approx_eq;
use pyo3::types::PyDateTime;
Python::attach(|py| {
let utc = PyTzInfo::utc(py).unwrap();
let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
let locals = [("dt", dt)].into_py_dict(py).unwrap();
let offset: f32 = py
.eval(c"dt.utcoffset().total_seconds()", None, Some(&locals))
.unwrap()
.extract()
.unwrap();
assert_approx_eq!(offset, 0f32);
});
}
static INVALID_DATES: &[(i32, u8, u8)] = &[
(-1, 1, 1),
(0, 1, 1),
(10000, 1, 1),
(2 << 30, 1, 1),
(2018, 0, 1),
(2018, 13, 1),
(2018, 1, 0),
(2017, 2, 29),
(2018, 1, 32),
];
static INVALID_TIMES: &[(u8, u8, u8, u32)] =
&[(25, 0, 0, 0), (255, 0, 0, 0), (0, 60, 0, 0), (0, 0, 61, 0)];
#[test]
fn test_pydate_out_of_bounds() {
use pyo3::types::PyDate;
Python::attach(|py| {
for val in INVALID_DATES {
let (year, month, day) = val;
let dt = PyDate::new(py, *year, *month, *day);
dt.unwrap_err();
}
});
}
#[test]
fn test_pytime_out_of_bounds() {
use pyo3::types::PyTime;
Python::attach(|py| {
for val in INVALID_TIMES {
let (hour, minute, second, microsecond) = val;
let dt = PyTime::new(py, *hour, *minute, *second, *microsecond, None);
dt.unwrap_err();
}
});
}
#[test]
fn test_pydatetime_out_of_bounds() {
use pyo3::types::PyDateTime;
use std::iter;
Python::attach(|py| {
let valid_time = (0, 0, 0, 0);
let valid_date = (2018, 1, 1);
let invalid_dates = INVALID_DATES.iter().zip(iter::repeat(&valid_time));
let invalid_times = iter::repeat(&valid_date).zip(INVALID_TIMES.iter());
let vals = invalid_dates.chain(invalid_times);
for val in vals {
let (date, time) = val;
let (year, month, day) = date;
let (hour, minute, second, microsecond) = time;
let dt = PyDateTime::new(
py,
*year,
*month,
*day,
*hour,
*minute,
*second,
*microsecond,
None,
);
dt.unwrap_err();
}
});
}
+28
View File
@@ -0,0 +1,28 @@
#![cfg(not(Py_LIMITED_API))]
use pyo3::{prelude::*, types::PyDate};
use tempfile::Builder;
#[test]
#[should_panic(expected = "module 'datetime' has no attribute 'datetime_CAPI'")]
fn test_bad_datetime_module_panic() {
// Create an empty temporary directory
// with an empty "datetime" module which we'll put on the sys.path
let tmpdir = Builder::new()
.prefix("pyo3_test_data_check")
.tempdir()
.unwrap();
std::fs::File::create(tmpdir.path().join("datetime.py")).unwrap();
Python::attach(|py: Python<'_>| {
let sys = py.import("sys").unwrap();
sys.getattr("path")
.unwrap()
.call_method1("insert", (0, tmpdir.path().as_os_str()))
.unwrap();
// This should panic because the "datetime" module is empty
PyDate::new(py, 2018, 1, 1).unwrap();
});
tmpdir.close().unwrap();
}
+273
View File
@@ -0,0 +1,273 @@
#![cfg(feature = "macros")]
use std::sync::OnceLock;
use pyo3::create_exception;
use pyo3::exceptions::PyException;
use pyo3::prelude::*;
use pyo3::sync::OnceLockExt;
mod test_utils;
mod some_module {
use pyo3::create_exception;
use pyo3::exceptions::PyException;
use pyo3::prelude::*;
#[pyclass]
pub struct SomePyClass;
create_exception!(some_module, SomeException, PyException);
}
#[pyclass]
struct ValueClass {
value: usize,
}
#[pymethods]
impl ValueClass {
#[new]
fn new(value: usize) -> Self {
Self { value }
}
}
#[pyclass(module = "module")]
pub struct LocatedClass {}
#[pyfunction]
fn double(x: usize) -> usize {
x * 2
}
create_exception!(
declarative_module,
MyError,
PyException,
"Some description."
);
#[pymodule(submodule)]
mod external_submodule {}
/// A module written using declarative syntax.
#[pymodule]
mod declarative_module {
#[pymodule_export]
use super::declarative_submodule;
#[pymodule_export]
// This is not a real constraint but to test cfg attribute support
#[cfg(not(Py_LIMITED_API))]
use super::LocatedClass;
use super::*;
#[pymodule_export]
use super::{declarative_module2, double, MyError, ValueClass as Value};
// test for #4036
#[pymodule_export]
use super::some_module::SomePyClass;
// test for #4036
#[pymodule_export]
use super::some_module::SomeException;
#[pymodule_export]
use super::external_submodule;
#[pymodule_export]
const FOO: u32 = 42;
#[pymodule_export]
#[cfg(Py_LIMITED_API)]
const BAR: &str = "BAR";
#[pymodule_export]
#[expect(non_upper_case_globals)]
const r#type: char = '!';
#[expect(unused)]
const NOT_EXPORTED: &str = "not exported";
#[pymodule]
mod inner {
use super::*;
#[pyfunction]
fn triple(x: usize) -> usize {
x * 3
}
#[pyclass(name = "Struct")]
struct Struct;
#[pymethods]
impl Struct {
#[new]
fn new() -> Self {
Self
}
}
#[pyclass(module = "foo")]
struct StructInCustomModule;
#[pyclass(eq, eq_int, name = "Enum")]
#[derive(PartialEq)]
enum Enum {
A,
B,
}
#[pyclass(eq, eq_int, module = "foo")]
#[derive(PartialEq)]
enum EnumInCustomModule {
A,
B,
}
}
#[pymodule]
#[pyo3(module = "custom_root")]
mod inner_custom_root {
use super::*;
#[pyclass]
struct Struct;
}
#[pyo3::prelude::pymodule]
mod full_path_inner {}
#[pymodule_init]
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add("double2", m.getattr("double")?)
}
}
#[pyfunction]
fn double_value(v: &ValueClass) -> usize {
v.value * 2
}
#[pymodule(module = "declarative_module")]
mod declarative_submodule {
#[pymodule_export]
use super::{double, double_value};
}
#[pymodule(name = "declarative_module_renamed")]
mod declarative_module2 {
#[pymodule_export]
use super::double;
}
fn declarative_module(py: Python<'_>) -> &Bound<'_, PyModule> {
static MODULE: OnceLock<Py<PyModule>> = OnceLock::new();
MODULE
.get_or_init_py_attached(py, || pyo3::wrap_pymodule!(declarative_module)(py))
.bind(py)
}
#[test]
fn test_declarative_module() {
Python::attach(|py| {
let m = declarative_module(py);
py_assert!(
py,
m,
"m.__doc__ == 'A module written using declarative syntax.'"
);
py_assert!(py, m, "m.double(2) == 4");
py_assert!(py, m, "m.inner.triple(3) == 9");
py_assert!(py, m, "m.declarative_submodule.double(4) == 8");
py_assert!(
py,
m,
"m.declarative_submodule.double_value(m.ValueClass(1)) == 2"
);
py_assert!(py, m, "str(m.MyError('foo')) == 'foo'");
py_assert!(py, m, "m.declarative_module_renamed.double(2) == 4");
#[cfg(Py_LIMITED_API)]
py_assert!(py, m, "not hasattr(m, 'LocatedClass')");
#[cfg(not(Py_LIMITED_API))]
py_assert!(py, m, "hasattr(m, 'LocatedClass')");
py_assert!(py, m, "isinstance(m.inner.Struct(), m.inner.Struct)");
py_assert!(py, m, "isinstance(m.inner.Enum.A, m.inner.Enum)");
py_assert!(py, m, "hasattr(m, 'external_submodule')");
py_assert!(py, m, "m.FOO == 42");
#[cfg(Py_LIMITED_API)]
py_assert!(py, m, "m.BAR == 'BAR'");
#[cfg(not(Py_LIMITED_API))]
py_assert!(py, m, "not hasattr(m, 'BAR')");
py_assert!(py, m, "m.type == '!'");
py_assert!(py, m, "not hasattr(m, 'NOT_EXPORTED')");
// submodule dunder name and attribute name
// declarative_module.inner is declared inside
py_assert!(py, m, "m.inner.__name__ == 'declarative_module.inner'");
py_assert!(py, m, "'inner' in m.__dict__");
py_assert!(py, m, "'declarative_module.inner' not in m.__dict__");
// since declarative_submodule is declared outside, but the parent module name is passed
py_assert!(
py,
m,
"m.declarative_submodule.__name__ == 'declarative_module.declarative_submodule'"
);
py_assert!(py, m, "'declarative_submodule' in m.__dict__");
py_assert!(
py,
m,
"'declarative_module.declarative_submodule' not in m.__dict__"
);
})
}
#[pymodule]
mod r#type {
#[pymodule_export]
use super::double;
}
#[test]
fn test_raw_ident_module() {
Python::attach(|py| {
let m = pyo3::wrap_pymodule!(r#type)(py).into_bound(py);
py_assert!(py, m, "m.double(2) == 4");
})
}
#[test]
fn test_module_names() {
Python::attach(|py| {
let m = declarative_module(py);
py_assert!(
py,
m,
"m.inner.Struct.__module__ == 'declarative_module.inner'"
);
py_assert!(py, m, "m.inner.StructInCustomModule.__module__ == 'foo'");
py_assert!(
py,
m,
"m.inner.Enum.__module__ == 'declarative_module.inner'"
);
py_assert!(py, m, "m.inner.EnumInCustomModule.__module__ == 'foo'");
py_assert!(
py,
m,
"m.inner_custom_root.Struct.__module__ == 'custom_root.inner_custom_root'"
);
})
}
#[test]
fn test_inner_module_full_path() {
Python::attach(|py| {
let m = declarative_module(py);
py_assert!(py, m, "m.full_path_inner");
})
}
+45
View File
@@ -0,0 +1,45 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
mod test_utils;
// Test default generated __repr__.
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum TestDefaultRepr {
Var,
}
#[test]
fn test_default_slot_exists() {
Python::attach(|py| {
let test_object = Py::new(py, TestDefaultRepr::Var).unwrap();
py_assert!(
py,
test_object,
"repr(test_object) == 'TestDefaultRepr.Var'"
);
})
}
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum OverrideSlot {
Var,
}
#[pymethods]
impl OverrideSlot {
fn __repr__(&self) -> &str {
"overridden"
}
}
#[test]
fn test_override_slot() {
Python::attach(|py| {
let test_object = Py::new(py, OverrideSlot::Var).unwrap();
py_assert!(py, test_object, "repr(test_object) == 'overridden'");
})
}
+459
View File
@@ -0,0 +1,459 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::PyString;
mod test_utils;
#[pyclass(eq, eq_int, from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum MyEnum {
Variant,
OtherVariant,
}
#[test]
fn test_enum_class_attr() {
Python::attach(|py| {
let my_enum = py.get_type::<MyEnum>();
let var = Py::new(py, MyEnum::Variant).unwrap();
py_assert!(py, my_enum var, "my_enum.Variant == var");
})
}
#[test]
fn test_enum_intopyobject() {
// Variant of the above that goes via `.into_pyobject()`
// - regression test for https://github.com/PyO3/pyo3/issues/5927
//
#[pyclass(eq, eq_int, from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum MyEnum {
A,
}
Python::attach(|py| {
let var = MyEnum::A.into_pyobject(py).unwrap();
// NB important to call this after `into_pyobject` - this ensures
// that `.into_pyobject()` has to lazily initialize the enum type object
let my_enum = py.get_type::<MyEnum>();
py_assert!(py, my_enum var, "my_enum.A == var");
});
}
#[test]
fn test_enum_eq_enum() {
Python::attach(|py| {
let var1 = Py::new(py, MyEnum::Variant).unwrap();
let var2 = Py::new(py, MyEnum::Variant).unwrap();
let other_var = Py::new(py, MyEnum::OtherVariant).unwrap();
py_assert!(py, var1 var2, "var1 == var2");
py_assert!(py, var1 other_var, "var1 != other_var");
py_assert!(py, var1 var2, "(var1 != var2) == False");
})
}
#[test]
fn test_enum_eq_incomparable() {
Python::attach(|py| {
let var1 = Py::new(py, MyEnum::Variant).unwrap();
py_assert!(py, var1, "(var1 == 'foo') == False");
py_assert!(py, var1, "(var1 != 'foo') == True");
})
}
#[pyfunction]
fn return_enum() -> MyEnum {
MyEnum::Variant
}
#[test]
fn test_return_enum() {
Python::attach(|py| {
let f = wrap_pyfunction!(return_enum)(py).unwrap();
let mynum = py.get_type::<MyEnum>();
py_run!(py, f mynum, "assert f() == mynum.Variant");
py_run!(py, f mynum, "assert f() is mynum.Variant");
});
}
#[pyfunction]
fn enum_arg(e: MyEnum) {
assert_eq!(MyEnum::OtherVariant, e)
}
#[test]
fn test_enum_arg() {
Python::attach(|py| {
let f = wrap_pyfunction!(enum_arg)(py).unwrap();
let mynum = py.get_type::<MyEnum>();
py_run!(py, f mynum, "f(mynum.OtherVariant)")
})
}
#[pyclass(eq, eq_int, skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone)]
enum CustomDiscriminant {
One = 1,
Two = 2,
}
#[test]
fn test_custom_discriminant() {
Python::attach(|py| {
#[allow(non_snake_case)]
let CustomDiscriminant = py.get_type::<CustomDiscriminant>();
let one = Py::new(py, CustomDiscriminant::One).unwrap();
let two = Py::new(py, CustomDiscriminant::Two).unwrap();
py_run!(py, CustomDiscriminant one two, r#"
assert CustomDiscriminant.One == one
assert CustomDiscriminant.Two == two
assert CustomDiscriminant.One == 1
assert CustomDiscriminant.Two == 2
assert one != two
assert CustomDiscriminant.One != 2
assert CustomDiscriminant.Two != 1
"#);
})
}
#[test]
fn test_enum_to_int() {
Python::attach(|py| {
let one = Py::new(py, CustomDiscriminant::One).unwrap();
py_assert!(py, one, "int(one) == 1");
let v = Py::new(py, MyEnum::Variant).unwrap();
let v_value = MyEnum::Variant as isize;
py_run!(py, v v_value, "int(v) == v_value");
})
}
#[test]
fn test_enum_compare_int() {
Python::attach(|py| {
let one = Py::new(py, CustomDiscriminant::One).unwrap();
py_run!(
py,
one,
r#"
assert one == 1
assert 1 == one
assert one != 2
"#
)
})
}
#[pyclass(eq, eq_int, skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(u8)]
enum SmallEnum {
V = 1,
}
#[test]
fn test_enum_compare_int_no_throw_when_overflow() {
Python::attach(|py| {
let v = Py::new(py, SmallEnum::V).unwrap();
py_assert!(py, v, "v != 1<<30")
})
}
#[pyclass(eq, eq_int, skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(usize)]
#[allow(clippy::enum_clike_unportable_variant)]
enum BigEnum {
V = usize::MAX,
}
#[test]
fn test_big_enum_no_overflow() {
Python::attach(|py| {
let usize_max = usize::MAX;
let v = Py::new(py, BigEnum::V).unwrap();
py_assert!(py, usize_max v, "v == usize_max");
py_assert!(py, usize_max v, "int(v) == usize_max");
})
}
#[pyclass(eq, eq_int, skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(u16, align(8))]
enum TestReprParse {
V,
}
#[test]
fn test_repr_parse() {
assert_eq!(std::mem::align_of::<TestReprParse>(), 8);
}
#[pyclass(eq, eq_int, name = "MyEnum", skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum RenameEnum {
Variant,
}
#[test]
fn test_rename_enum_repr_correct() {
Python::attach(|py| {
let var1 = Py::new(py, RenameEnum::Variant).unwrap();
py_assert!(py, var1, "repr(var1) == 'MyEnum.Variant'");
})
}
#[pyclass(eq, eq_int, skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum RenameVariantEnum {
#[pyo3(name = "VARIANT")]
Variant,
}
#[test]
fn test_rename_variant_repr_correct() {
Python::attach(|py| {
let var1 = Py::new(py, RenameVariantEnum::Variant).unwrap();
py_assert!(py, var1, "repr(var1) == 'RenameVariantEnum.VARIANT'");
})
}
#[pyclass(eq, eq_int, rename_all = "SCREAMING_SNAKE_CASE", skip_from_py_object)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[expect(clippy::enum_variant_names)]
enum RenameAllVariantsEnum {
VariantOne,
VariantTwo,
#[pyo3(name = "VariantThree")]
VariantFour,
}
#[test]
fn test_renaming_all_enum_variants() {
Python::attach(|py| {
let enum_obj = py.get_type::<RenameAllVariantsEnum>();
py_assert!(py, enum_obj, "enum_obj.VARIANT_ONE == enum_obj.VARIANT_ONE");
py_assert!(py, enum_obj, "enum_obj.VARIANT_TWO == enum_obj.VARIANT_TWO");
py_assert!(
py,
enum_obj,
"enum_obj.VariantThree == enum_obj.VariantThree"
);
});
}
#[pyclass(module = "custom_module")]
#[derive(Debug)]
enum CustomModuleComplexEnum {
Variant(),
Py(Py<PyAny>),
}
#[test]
fn test_custom_module() {
Python::attach(|py| {
let enum_obj = py.get_type::<CustomModuleComplexEnum>();
py_assert!(
py,
enum_obj,
"enum_obj.Variant.__module__ == 'custom_module'"
);
});
}
#[pyclass(eq, skip_from_py_object)]
#[derive(Debug, Clone, PartialEq)]
pub enum EqOnly {
VariantA,
VariantB,
}
#[test]
fn test_simple_enum_eq_only() {
Python::attach(|py| {
let var1 = Py::new(py, EqOnly::VariantA).unwrap();
let var2 = Py::new(py, EqOnly::VariantA).unwrap();
let var3 = Py::new(py, EqOnly::VariantB).unwrap();
py_assert!(py, var1 var2, "var1 == var2");
py_assert!(py, var1 var3, "var1 != var3");
})
}
#[pyclass(frozen, eq, eq_int, hash)]
#[derive(PartialEq, Hash)]
enum SimpleEnumWithHash {
A,
B,
}
#[test]
fn test_simple_enum_with_hash() {
Python::attach(|py| {
use pyo3::types::IntoPyDict;
let class = SimpleEnumWithHash::A;
let hash = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
class.hash(&mut hasher);
hasher.finish() as isize
};
let env = [
("obj", Py::new(py, class).unwrap().into_any()),
("hsh", hash.into_pyobject(py).unwrap().into_any().unbind()),
]
.into_py_dict(py)
.unwrap();
py_assert!(py, *env, "hash(obj) == hsh");
});
}
#[pyclass(eq, hash)]
#[derive(PartialEq, Hash)]
enum ComplexEnumWithHash {
A(u32),
B { msg: String },
}
#[test]
fn test_complex_enum_with_hash() {
Python::attach(|py| {
use pyo3::types::IntoPyDict;
let class = ComplexEnumWithHash::B {
msg: String::from("Hello"),
};
let hash = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
class.hash(&mut hasher);
hasher.finish() as isize
};
let env = [
("obj", Py::new(py, class).unwrap().into_any()),
("hsh", hash.into_pyobject(py).unwrap().into_any().unbind()),
]
.into_py_dict(py)
.unwrap();
py_assert!(py, *env, "hash(obj) == hsh");
});
}
#[test]
fn custom_eq() {
#[pyclass(frozen)]
#[derive(PartialEq)]
pub enum CustomPyEq {
A,
B,
}
#[pymethods]
impl CustomPyEq {
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(rhs) = other.cast::<PyString>() {
rhs.to_cow().is_ok_and(|rhs| self.__str__() == rhs)
} else if let Ok(rhs) = other.cast::<Self>() {
self == rhs.get()
} else {
false
}
}
fn __str__(&self) -> String {
match self {
CustomPyEq::A => "A".to_string(),
CustomPyEq::B => "B".to_string(),
}
}
}
Python::attach(|py| {
let a = Bound::new(py, CustomPyEq::A).unwrap();
let b = Bound::new(py, CustomPyEq::B).unwrap();
assert!(a.as_any().eq(&a).unwrap());
assert!(a.as_any().eq("A").unwrap());
assert!(a.as_any().ne(&b).unwrap());
assert!(a.as_any().ne("B").unwrap());
assert!(b.as_any().eq(&b).unwrap());
assert!(b.as_any().eq("B").unwrap());
assert!(b.as_any().ne(&a).unwrap());
assert!(b.as_any().ne("A").unwrap());
})
}
#[pyclass(skip_from_py_object)]
#[derive(Clone, Copy)]
pub enum ComplexEnumWithRaw {
Raw { r#type: i32 },
}
// Cover simple field lookups with raw identifiers
#[test]
fn complex_enum_with_raw() {
Python::attach(|py| {
let complex = ComplexEnumWithRaw::Raw { r#type: 314159 };
py_assert!(py, complex, "complex.type == 314159");
});
}
// Cover pattern matching with raw identifiers
#[test]
#[cfg(Py_3_10)]
fn complex_enum_with_raw_pattern_match() {
Python::attach(|py| {
let complex = ComplexEnumWithRaw::Raw { r#type: 314159 };
let cls = py.get_type::<ComplexEnumWithRaw>();
// Cover destructuring by pattern matching
py_run!(py, cls complex, r#"
match complex:
case cls.Raw(type=ty):
assert ty == 314159
case _:
assert False, "no matching variant found"
"#);
});
}
#[test]
fn complex_enum_variant_qualname() {
#[pyclass(skip_from_py_object)]
pub enum ComplexEnum {
A(i32),
B { msg: String },
}
Python::attach(|py| {
let cls = py.get_type::<ComplexEnum>();
py_assert!(py, cls, "cls.A.__qualname__ == 'ComplexEnum.A'");
py_assert!(py, cls, "cls.B.__qualname__ == 'ComplexEnum.B'");
});
}
#[test]
fn complex_enum_renamed_variant_qualname() {
#[pyclass(name = "ComplexEnum", skip_from_py_object)]
pub enum PyComplexEnum {
#[pyo3(name = "A")]
PyA(i32),
B {
msg: String,
},
}
Python::attach(|py| {
let cls = py.get_type::<PyComplexEnum>();
py_assert!(py, cls, "cls.A.__qualname__ == 'ComplexEnum.A'");
py_assert!(py, cls, "cls.B.__qualname__ == 'ComplexEnum.B'");
});
}
+124
View File
@@ -0,0 +1,124 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::{exceptions, py_run};
use std::error::Error;
use std::fmt;
#[cfg(not(target_os = "windows"))]
use std::fs::File;
mod test_utils;
#[pyfunction]
#[cfg(not(target_os = "windows"))]
fn fail_to_open_file() -> PyResult<()> {
File::open("not_there.txt")?;
Ok(())
}
#[test]
#[cfg_attr(target_arch = "wasm32", ignore)] // Not sure why this fails.
#[cfg(not(target_os = "windows"))]
fn test_filenotfounderror() {
Python::attach(|py| {
let fail_to_open_file = wrap_pyfunction!(fail_to_open_file)(py).unwrap();
py_run!(
py,
fail_to_open_file,
r#"
try:
fail_to_open_file()
except FileNotFoundError as e:
assert str(e) == "No such file or directory (os error 2)"
"#
);
});
}
#[derive(Debug)]
struct CustomError;
impl Error for CustomError {}
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Oh no!")
}
}
impl std::convert::From<CustomError> for PyErr {
fn from(err: CustomError) -> PyErr {
exceptions::PyOSError::new_err(err.to_string())
}
}
fn fail_with_custom_error() -> Result<(), CustomError> {
Err(CustomError)
}
#[pyfunction]
fn call_fail_with_custom_error() -> PyResult<()> {
fail_with_custom_error()?;
Ok(())
}
#[test]
fn test_custom_error() {
Python::attach(|py| {
let call_fail_with_custom_error =
wrap_pyfunction!(call_fail_with_custom_error)(py).unwrap();
py_run!(
py,
call_fail_with_custom_error,
r#"
try:
call_fail_with_custom_error()
except OSError as e:
assert str(e) == "Oh no!"
"#
);
});
}
#[test]
fn test_exception_nosegfault() {
use std::net::TcpListener;
fn io_err() -> PyResult<()> {
TcpListener::bind("no:address")?;
Ok(())
}
fn parse_int() -> PyResult<()> {
"@_@".parse::<i64>()?;
Ok(())
}
assert!(io_err().is_err());
assert!(parse_int().is_err());
}
#[test]
fn test_write_unraisable() {
use pyo3::{exceptions::PyRuntimeError, types::PyNotImplemented};
use test_utils::UnraisableCapture;
Python::attach(|py| {
UnraisableCapture::enter(py, |capture| {
let err = PyRuntimeError::new_err("foo");
err.write_unraisable(py, None);
let (err, object) = capture.take_capture().unwrap();
assert_eq!(err.to_string(), "RuntimeError: foo");
assert!(object.is_none());
let err = PyRuntimeError::new_err("bar");
err.write_unraisable(py, Some(&PyNotImplemented::get(py)));
let (err, object) = capture.take_capture().unwrap();
assert_eq!(err.to_string(), "RuntimeError: bar");
assert!(object.is(PyNotImplemented::get(py)));
});
});
}
+53
View File
@@ -0,0 +1,53 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
#[pyclass]
struct CfgClass {
#[pyo3(get, set)]
#[cfg(any())]
pub a: u32,
#[pyo3(get, set)]
// This is always true
#[cfg(any(
target_family = "unix",
target_family = "windows",
target_family = "wasm"
))]
pub b: u32,
}
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum CfgSimpleEnum {
#[cfg(any())]
DisabledVariant,
#[cfg(not(any()))]
EnabledVariant,
}
#[test]
fn test_cfg() {
Python::attach(|py| {
let cfg = CfgClass { b: 3 };
let py_cfg = Py::new(py, cfg).unwrap();
assert!(py_cfg.bind(py).getattr("a").is_err());
let b: u32 = py_cfg.bind(py).getattr("b").unwrap().extract().unwrap();
assert_eq!(b, 3);
});
}
#[test]
fn test_cfg_simple_enum() {
Python::attach(|py| {
let simple = py.get_type::<CfgSimpleEnum>();
pyo3::py_run!(
py,
simple,
r#"
assert hasattr(simple, "EnabledVariant")
assert not hasattr(simple, "DisabledVariant")
"#
);
})
}
+288
View File
@@ -0,0 +1,288 @@
#![cfg(feature = "macros")]
use pyo3::types::{PyDict, PyString};
use pyo3::{prelude::*, IntoPyObject, IntoPyObjectExt, IntoPyObjectRef};
use std::collections::HashMap;
use std::hash::Hash;
#[macro_use]
mod test_utils;
#[derive(Debug, Clone, IntoPyObject, IntoPyObjectRef, FromPyObject)]
pub struct A<'py> {
#[pyo3(item)]
s: String,
#[pyo3(item)]
t: Bound<'py, PyString>,
#[pyo3(item("foo"))]
p: Bound<'py, PyAny>,
}
#[test]
fn test_named_fields_struct() {
Python::attach(|py| {
let a = A {
s: "Hello".into(),
t: PyString::new(py, "World"),
p: 42i32.into_pyobject(py).unwrap().into_any(),
};
let pya = (&a).into_pyobject(py).unwrap();
let new_a = pya.extract::<A<'_>>().unwrap();
assert_eq!(a.s, new_a.s);
assert_eq!(a.t.to_cow().unwrap(), new_a.t.to_cow().unwrap());
assert_eq!(
a.p.extract::<i32>().unwrap(),
new_a.p.extract::<i32>().unwrap()
);
let pya = a.clone().into_pyobject(py).unwrap();
let new_a = pya.extract::<A<'_>>().unwrap();
assert_eq!(a.s, new_a.s);
assert_eq!(a.t.to_cow().unwrap(), new_a.t.to_cow().unwrap());
assert_eq!(
a.p.extract::<i32>().unwrap(),
new_a.p.extract::<i32>().unwrap()
);
});
}
#[derive(Debug, Clone, PartialEq, IntoPyObject, IntoPyObjectRef, FromPyObject)]
#[pyo3(transparent)]
pub struct B {
test: String,
}
#[test]
fn test_transparent_named_field_struct() {
Python::attach(|py| {
let b = B {
test: "test".into(),
};
let pyb = (&b).into_pyobject(py).unwrap();
let new_b = pyb.extract::<B>().unwrap();
assert_eq!(b, new_b);
let pyb = b.clone().into_pyobject(py).unwrap();
let new_b = pyb.extract::<B>().unwrap();
assert_eq!(b, new_b);
});
}
#[derive(Debug, Clone, PartialEq, IntoPyObject, IntoPyObjectRef, FromPyObject)]
#[pyo3(transparent)]
pub struct D<T> {
test: T,
}
#[test]
fn test_generic_transparent_named_field_struct() {
Python::attach(|py| {
let d = D {
test: String::from("test"),
};
let pyd = (&d).into_pyobject(py).unwrap();
let new_d = pyd.extract::<D<String>>().unwrap();
assert_eq!(d, new_d);
let d = D { test: 1usize };
let pyd = (&d).into_pyobject(py).unwrap();
let new_d = pyd.extract::<D<usize>>().unwrap();
assert_eq!(d, new_d);
let d = D {
test: String::from("test"),
};
let pyd = d.clone().into_pyobject(py).unwrap();
let new_d = pyd.extract::<D<String>>().unwrap();
assert_eq!(d, new_d);
let d = D { test: 1usize };
let pyd = d.clone().into_pyobject(py).unwrap();
let new_d = pyd.extract::<D<usize>>().unwrap();
assert_eq!(d, new_d);
});
}
#[derive(Debug, IntoPyObject, IntoPyObjectRef, FromPyObject)]
pub struct GenericWithBound<K: Hash + Eq, V>(HashMap<K, V>);
#[test]
fn test_generic_with_bound() {
Python::attach(|py| {
let mut hash_map = HashMap::<String, i32>::new();
hash_map.insert("1".into(), 1);
hash_map.insert("2".into(), 2);
let map = GenericWithBound(hash_map);
let py_map = (&map).into_pyobject(py).unwrap();
assert_eq!(py_map.len(), 2);
assert_eq!(
py_map
.get_item("1")
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap(),
1
);
assert_eq!(
py_map
.get_item("2")
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap(),
2
);
assert!(py_map.get_item("3").unwrap().is_none());
let py_map = map.into_pyobject(py).unwrap();
assert_eq!(py_map.len(), 2);
assert_eq!(
py_map
.get_item("1")
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap(),
1
);
assert_eq!(
py_map
.get_item("2")
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap(),
2
);
assert!(py_map.get_item("3").unwrap().is_none());
});
}
#[derive(Debug, Clone, PartialEq, IntoPyObject, IntoPyObjectRef, FromPyObject)]
pub struct Tuple(String, usize);
#[test]
fn test_tuple_struct() {
Python::attach(|py| {
let tup = Tuple(String::from("test"), 1);
let tuple = (&tup).into_pyobject(py).unwrap();
let new_tup = tuple.extract::<Tuple>().unwrap();
assert_eq!(tup, new_tup);
let tuple = tup.clone().into_pyobject(py).unwrap();
let new_tup = tuple.extract::<Tuple>().unwrap();
assert_eq!(tup, new_tup);
});
}
#[derive(Debug, Clone, PartialEq, IntoPyObject, IntoPyObjectRef, FromPyObject)]
pub struct TransparentTuple(String);
#[test]
fn test_transparent_tuple_struct() {
Python::attach(|py| {
let tup = TransparentTuple(String::from("test"));
let tuple = (&tup).into_pyobject(py).unwrap();
let new_tup = tuple.extract::<TransparentTuple>().unwrap();
assert_eq!(tup, new_tup);
let tuple = tup.clone().into_pyobject(py).unwrap();
let new_tup = tuple.extract::<TransparentTuple>().unwrap();
assert_eq!(tup, new_tup);
});
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct IntWrapper(u32);
fn int_wrapper_into_py<'py>(
v: std::borrow::Cow<'_, IntWrapper>,
py: Python<'py>,
) -> PyResult<Bound<'py, PyAny>> {
v.0.into_bound_py_any(py)
}
fn int_wrapper_from_py(v: &Bound<'_, PyAny>) -> PyResult<IntWrapper> {
v.extract().map(IntWrapper)
}
#[derive(Debug, Clone, PartialEq, IntoPyObject, IntoPyObjectRef, FromPyObject)]
pub enum Foo {
TupleVar(usize, String),
StructVar {
#[pyo3(item)]
test: char,
#[pyo3(item, into_py_with=int_wrapper_into_py, from_py_with=int_wrapper_from_py)]
int: IntWrapper,
},
#[pyo3(transparent)]
TransparentTuple(usize),
#[pyo3(transparent)]
TransparentStructVar {
a: Option<String>,
},
#[pyo3(rename_all = "camelCase", from_item_all)]
RenameAll {
long_field_name: [u16; 2],
other_field: Option<String>,
},
}
#[test]
fn test_enum() {
Python::attach(|py| {
let tuple_var = Foo::TupleVar(1, "test".into());
let foo = (&tuple_var).into_pyobject(py).unwrap();
assert_eq!(tuple_var, foo.extract::<Foo>().unwrap());
let foo = tuple_var.clone().into_pyobject(py).unwrap();
assert_eq!(tuple_var, foo.extract::<Foo>().unwrap());
let struct_var = Foo::StructVar {
test: 'b',
int: IntWrapper(42),
};
let foo = (&struct_var)
.into_pyobject(py)
.unwrap()
.cast_into::<PyDict>()
.unwrap();
assert_eq!(struct_var, foo.extract::<Foo>().unwrap());
let foo = struct_var
.clone()
.into_pyobject(py)
.unwrap()
.cast_into::<PyDict>()
.unwrap();
assert_eq!(struct_var, foo.extract::<Foo>().unwrap());
let transparent_tuple = Foo::TransparentTuple(1);
let foo = (&transparent_tuple).into_pyobject(py).unwrap();
assert_eq!(transparent_tuple, foo.extract::<Foo>().unwrap());
let foo = transparent_tuple.clone().into_pyobject(py).unwrap();
assert_eq!(transparent_tuple, foo.extract::<Foo>().unwrap());
let transparent_struct_var = Foo::TransparentStructVar { a: None };
let foo = (&transparent_struct_var).into_pyobject(py).unwrap();
assert_eq!(transparent_struct_var, foo.extract::<Foo>().unwrap());
let foo = transparent_struct_var.clone().into_pyobject(py).unwrap();
assert_eq!(transparent_struct_var, foo.extract::<Foo>().unwrap());
let rename_all_struct_var = Foo::RenameAll {
long_field_name: [1, 2],
other_field: None,
};
let foo = (&rename_all_struct_var).into_pyobject(py).unwrap();
assert_eq!(rename_all_struct_var, foo.extract::<Foo>().unwrap());
let foo = rename_all_struct_var.clone().into_pyobject(py).unwrap();
assert_eq!(rename_all_struct_var, foo.extract::<Foo>().unwrap());
});
}
+879
View File
@@ -0,0 +1,879 @@
#![cfg(feature = "macros")]
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::{IntoPyDict, PyDict, PyList, PyString, PyTuple};
#[macro_use]
mod test_utils;
/// Helper function that concatenates the error message from
/// each error in the traceback into a single string that can
/// be tested.
fn extract_traceback(py: Python<'_>, mut error: PyErr) -> String {
let mut error_msg = error.to_string();
while let Some(cause) = error.cause(py) {
error_msg.push_str(": ");
error_msg.push_str(&cause.to_string());
error = cause
}
error_msg
}
#[derive(Debug, FromPyObject)]
pub struct A<'py> {
#[pyo3(attribute)]
s: String,
#[pyo3(item)]
t: Bound<'py, PyString>,
#[pyo3(attribute("foo"))]
p: Bound<'py, PyAny>,
}
#[pyclass]
pub struct PyA {
#[pyo3(get)]
s: String,
#[pyo3(get)]
foo: Option<String>,
}
#[pymethods]
impl PyA {
fn __getitem__(&self, key: String) -> pyo3::PyResult<String> {
if key == "t" {
Ok("bar".into())
} else {
Err(PyValueError::new_err("Failed"))
}
}
}
#[test]
fn test_named_fields_struct() {
Python::attach(|py| {
let pya = PyA {
s: "foo".into(),
foo: None,
};
let py_c = Py::new(py, pya).unwrap();
let a = py_c
.extract::<A<'_>>(py)
.expect("Failed to extract A from PyA");
assert_eq!(a.s, "foo");
assert_eq!(a.t.to_string_lossy(), "bar");
assert!(a.p.is_none());
});
}
#[derive(Debug, FromPyObject)]
#[pyo3(transparent)]
pub struct B {
test: String,
}
#[test]
fn test_transparent_named_field_struct() {
Python::attach(|py| {
let test = "test".into_pyobject(py).unwrap();
let b = test
.extract::<B>()
.expect("Failed to extract B from String");
assert_eq!(b.test, "test");
let test = 1i32.into_pyobject(py).unwrap();
let b = test.extract::<B>();
assert!(b.is_err());
});
}
#[derive(Debug, FromPyObject)]
#[pyo3(transparent)]
pub struct D<T> {
test: T,
}
#[test]
fn test_generic_transparent_named_field_struct() {
Python::attach(|py| {
let test = "test".into_pyobject(py).unwrap();
let d = test
.extract::<D<String>>()
.expect("Failed to extract D<String> from String");
assert_eq!(d.test, "test");
let test = 1usize.into_pyobject(py).unwrap();
let d = test
.extract::<D<usize>>()
.expect("Failed to extract D<usize> from String");
assert_eq!(d.test, 1);
});
}
#[derive(Debug, FromPyObject)]
pub struct GenericWithBound<K: std::hash::Hash + Eq, V>(std::collections::HashMap<K, V>);
#[test]
fn test_generic_with_bound() {
Python::attach(|py| {
let dict = [("1", 1), ("2", 2)].into_py_dict(py).unwrap();
let map = dict.extract::<GenericWithBound<String, i32>>().unwrap().0;
assert_eq!(map.len(), 2);
assert_eq!(map["1"], 1);
assert_eq!(map["2"], 2);
assert!(!map.contains_key("3"));
});
}
#[derive(Debug, FromPyObject)]
pub struct E<T, T2> {
test: T,
test2: T2,
}
#[pyclass(skip_from_py_object)]
#[derive(Clone)]
pub struct PyE {
#[pyo3(get)]
test: String,
#[pyo3(get)]
test2: usize,
}
#[test]
fn test_generic_named_fields_struct() {
Python::attach(|py| {
let pye = PyE {
test: "test".into(),
test2: 2,
}
.into_pyobject(py)
.unwrap();
let e = pye
.extract::<E<String, usize>>()
.expect("Failed to extract E<String, usize> from PyE");
assert_eq!(e.test, "test");
assert_eq!(e.test2, 2);
let e = pye.extract::<E<usize, usize>>();
assert!(e.is_err());
});
}
#[derive(Debug, FromPyObject)]
pub struct C {
#[pyo3(attribute("test"))]
test: String,
}
#[test]
fn test_named_field_with_ext_fn() {
Python::attach(|py| {
let pyc = PyE {
test: "foo".into(),
test2: 0,
}
.into_pyobject(py)
.unwrap();
let c = pyc.extract::<C>().expect("Failed to extract C from PyE");
assert_eq!(c.test, "foo");
});
}
#[derive(Debug, FromPyObject)]
pub struct Tuple(String, usize);
#[test]
fn test_tuple_struct() {
Python::attach(|py| {
let tup = PyTuple::new(
py,
&[
1i32.into_pyobject(py).unwrap().into_any(),
"test".into_pyobject(py).unwrap().into_any(),
],
)
.unwrap();
let tup = tup.extract::<Tuple>();
assert!(tup.is_err());
let tup = PyTuple::new(
py,
&[
"test".into_pyobject(py).unwrap().into_any(),
1i32.into_pyobject(py).unwrap().into_any(),
],
)
.unwrap();
let tup = tup
.extract::<Tuple>()
.expect("Failed to extract Tuple from PyTuple");
assert_eq!(tup.0, "test");
assert_eq!(tup.1, 1);
});
}
#[derive(Debug, FromPyObject)]
pub struct TransparentTuple(String);
#[test]
fn test_transparent_tuple_struct() {
Python::attach(|py| {
let tup = 1i32.into_pyobject(py).unwrap();
let tup = tup.extract::<TransparentTuple>();
assert!(tup.is_err());
let test = "test".into_pyobject(py).unwrap();
let tup = test
.extract::<TransparentTuple>()
.expect("Failed to extract TransparentTuple from PyTuple");
assert_eq!(tup.0, "test");
});
}
#[pyclass]
struct PyBaz {
#[pyo3(get)]
tup: (String, String),
#[pyo3(get)]
e: PyE,
}
#[derive(Debug, FromPyObject)]
#[expect(dead_code)]
struct Baz<U, T> {
e: E<U, T>,
tup: Tuple,
}
#[test]
fn test_struct_nested_type_errors() {
Python::attach(|py| {
let pybaz = PyBaz {
tup: ("test".into(), "test".into()),
e: PyE {
test: "foo".into(),
test2: 0,
},
}
.into_pyobject(py)
.unwrap();
let test = pybaz.extract::<Baz<String, usize>>();
assert!(test.is_err());
assert_eq!(
extract_traceback(py,test.unwrap_err()),
"TypeError: failed to extract field Baz.tup: TypeError: failed to extract field Tuple.1: \
TypeError: \'str\' object cannot be interpreted as an integer"
);
});
}
#[test]
fn test_struct_nested_type_errors_with_generics() {
Python::attach(|py| {
let pybaz = PyBaz {
tup: ("test".into(), "test".into()),
e: PyE {
test: "foo".into(),
test2: 0,
},
}
.into_pyobject(py)
.unwrap();
let test = pybaz.extract::<Baz<usize, usize>>();
assert!(test.is_err());
assert_eq!(
extract_traceback(py, test.unwrap_err()),
"TypeError: failed to extract field Baz.e: TypeError: failed to extract field E.test: \
TypeError: \'str\' object cannot be interpreted as an integer",
);
});
}
#[test]
fn test_transparent_struct_error_message() {
Python::attach(|py| {
let tup = 1i32.into_pyobject(py).unwrap();
let tup = tup.extract::<B>();
assert!(tup.is_err());
assert_eq!(
extract_traceback(py, tup.unwrap_err()),
"TypeError: failed to extract field B.test: TypeError: \'int\' object is not an \
instance of \'str\'"
);
});
}
#[test]
fn test_tuple_struct_error_message() {
Python::attach(|py| {
let tup = (1, "test").into_pyobject(py).unwrap();
let tup = tup.extract::<Tuple>();
assert!(tup.is_err());
assert_eq!(
extract_traceback(py, tup.unwrap_err()),
"TypeError: failed to extract field Tuple.0: TypeError: \'int\' object is not an \
instance of \'str\'"
);
});
}
#[test]
fn test_transparent_tuple_error_message() {
Python::attach(|py| {
let tup = 1i32.into_pyobject(py).unwrap();
let tup = tup.extract::<TransparentTuple>();
assert!(tup.is_err());
assert_eq!(
extract_traceback(py, tup.unwrap_err()),
"TypeError: failed to extract field TransparentTuple.0: TypeError: 'int' object \
is not an instance of 'str'",
);
});
}
#[pyclass]
struct RenameAllCls {}
#[pymethods]
impl RenameAllCls {
#[getter]
#[pyo3(name = "someField")]
fn some_field(&self) -> &'static str {
"Foo"
}
#[getter]
#[pyo3(name = "customNumber")]
fn custom_number(&self) -> i32 {
42
}
fn __getitem__(&self, key: &str) -> PyResult<f32> {
match key {
"otherField" => Ok(42.0),
_ => Err(pyo3::exceptions::PyKeyError::new_err("foo")),
}
}
}
#[test]
fn test_struct_rename_all() {
#[derive(FromPyObject)]
#[pyo3(rename_all = "camelCase")]
struct RenameAll {
some_field: String,
#[pyo3(item)]
other_field: f32,
#[pyo3(attribute("customNumber"))]
custom_name: i32,
}
Python::attach(|py| {
let RenameAll {
some_field,
other_field,
custom_name,
} = RenameAllCls {}
.into_pyobject(py)
.unwrap()
.extract()
.unwrap();
assert_eq!(some_field, "Foo");
assert_eq!(other_field, 42.0);
assert_eq!(custom_name, 42);
});
}
#[test]
fn test_enum_rename_all() {
#[derive(FromPyObject)]
#[pyo3(rename_all = "camelCase")]
enum RenameAll {
Foo {
some_field: String,
#[pyo3(item)]
other_field: f32,
#[pyo3(attribute("customNumber"))]
custom_name: i32,
},
}
Python::attach(|py| {
let RenameAll::Foo {
some_field,
other_field,
custom_name,
} = RenameAllCls {}
.into_pyobject(py)
.unwrap()
.extract()
.unwrap();
assert_eq!(some_field, "Foo");
assert_eq!(other_field, 42.0);
assert_eq!(custom_name, 42);
});
}
#[derive(Debug, FromPyObject)]
pub enum Foo<'py> {
TupleVar(usize, String),
StructVar {
test: Bound<'py, PyString>,
},
#[pyo3(transparent)]
TransparentTuple(usize),
#[pyo3(transparent)]
TransparentStructVar {
a: Option<String>,
},
StructVarGetAttrArg {
#[pyo3(attribute("bla"))]
a: bool,
},
StructWithGetItem {
#[pyo3(item)]
a: String,
},
StructWithGetItemArg {
#[pyo3(item("foo"))]
a: String,
},
}
#[pyclass]
pub struct PyBool {
#[pyo3(get)]
bla: bool,
}
#[test]
fn test_enum() {
Python::attach(|py| {
let tup = PyTuple::new(
py,
&[
1i32.into_pyobject(py).unwrap().into_any(),
"test".into_pyobject(py).unwrap().into_any(),
],
)
.unwrap();
let f = tup
.extract::<Foo<'_>>()
.expect("Failed to extract Foo from tuple");
match f {
Foo::TupleVar(test, test2) => {
assert_eq!(test, 1);
assert_eq!(test2, "test");
}
_ => panic!("Expected extracting Foo::TupleVar, got {f:?}"),
}
let pye = PyE {
test: "foo".into(),
test2: 0,
}
.into_pyobject(py)
.unwrap();
let f = pye
.extract::<Foo<'_>>()
.expect("Failed to extract Foo from PyE");
match f {
Foo::StructVar { test } => assert_eq!(test.to_string_lossy(), "foo"),
_ => panic!("Expected extracting Foo::StructVar, got {f:?}"),
}
let int = 1i32.into_pyobject(py).unwrap();
let f = int
.extract::<Foo<'_>>()
.expect("Failed to extract Foo from int");
match f {
Foo::TransparentTuple(test) => assert_eq!(test, 1),
_ => panic!("Expected extracting Foo::TransparentTuple, got {f:?}"),
}
let none = py.None();
let f = none
.extract::<Foo<'_>>(py)
.expect("Failed to extract Foo from int");
match f {
Foo::TransparentStructVar { a } => assert!(a.is_none()),
_ => panic!("Expected extracting Foo::TransparentStructVar, got {f:?}"),
}
let pybool = PyBool { bla: true }.into_pyobject(py).unwrap();
let f = pybool
.extract::<Foo<'_>>()
.expect("Failed to extract Foo from PyBool");
match f {
Foo::StructVarGetAttrArg { a } => assert!(a),
_ => panic!("Expected extracting Foo::StructVarGetAttrArg, got {f:?}"),
}
let dict = PyDict::new(py);
dict.set_item("a", "test").expect("Failed to set item");
let f = dict
.extract::<Foo<'_>>()
.expect("Failed to extract Foo from dict");
match f {
Foo::StructWithGetItem { a } => assert_eq!(a, "test"),
_ => panic!("Expected extracting Foo::StructWithGetItem, got {f:?}"),
}
let dict = PyDict::new(py);
dict.set_item("foo", "test").expect("Failed to set item");
let f = dict
.extract::<Foo<'_>>()
.expect("Failed to extract Foo from dict");
match f {
Foo::StructWithGetItemArg { a } => assert_eq!(a, "test"),
_ => panic!("Expected extracting Foo::StructWithGetItemArg, got {f:?}"),
}
});
}
#[test]
fn test_enum_error() {
Python::attach(|py| {
let dict = PyDict::new(py);
let err = dict.extract::<Foo<'_>>().unwrap_err();
assert_eq!(
err.to_string(),
"\
TypeError: failed to extract enum Foo ('TupleVar | StructVar | TransparentTuple | TransparentStructVar | StructVarGetAttrArg | StructWithGetItem | StructWithGetItemArg')
- variant TupleVar (TupleVar): TypeError: 'dict' object is not an instance of 'tuple'
- variant StructVar (StructVar): AttributeError: 'dict' object has no attribute 'test'
- variant TransparentTuple (TransparentTuple): TypeError: failed to extract field Foo::TransparentTuple.0, caused by TypeError: 'dict' object cannot be interpreted as an integer
- variant TransparentStructVar (TransparentStructVar): TypeError: failed to extract field Foo::TransparentStructVar.a, caused by TypeError: 'dict' object is not an instance of 'str'
- variant StructVarGetAttrArg (StructVarGetAttrArg): AttributeError: 'dict' object has no attribute 'bla'
- variant StructWithGetItem (StructWithGetItem): KeyError: 'a'
- variant StructWithGetItemArg (StructWithGetItemArg): KeyError: 'foo'"
);
let tup = PyTuple::empty(py);
let err = tup.extract::<Foo<'_>>().unwrap_err();
assert_eq!(
err.to_string(),
"\
TypeError: failed to extract enum Foo ('TupleVar | StructVar | TransparentTuple | TransparentStructVar | StructVarGetAttrArg | StructWithGetItem | StructWithGetItemArg')
- variant TupleVar (TupleVar): ValueError: expected tuple of length 2, but got tuple of length 0
- variant StructVar (StructVar): AttributeError: 'tuple' object has no attribute 'test'
- variant TransparentTuple (TransparentTuple): TypeError: failed to extract field Foo::TransparentTuple.0, caused by TypeError: 'tuple' object cannot be interpreted as an integer
- variant TransparentStructVar (TransparentStructVar): TypeError: failed to extract field Foo::TransparentStructVar.a, caused by TypeError: 'tuple' object is not an instance of 'str'
- variant StructVarGetAttrArg (StructVarGetAttrArg): AttributeError: 'tuple' object has no attribute 'bla'
- variant StructWithGetItem (StructWithGetItem): TypeError: tuple indices must be integers or slices, not str
- variant StructWithGetItemArg (StructWithGetItemArg): TypeError: tuple indices must be integers or slices, not str"
);
});
}
#[derive(Debug, FromPyObject)]
enum EnumWithCatchAll<'py> {
#[expect(dead_code)]
#[pyo3(transparent)]
Foo(Foo<'py>),
#[pyo3(transparent)]
CatchAll(Bound<'py, PyAny>),
}
#[test]
fn test_enum_catch_all() {
Python::attach(|py| {
let dict = PyDict::new(py);
let f = dict
.extract::<EnumWithCatchAll<'_>>()
.expect("Failed to extract EnumWithCatchAll from dict");
match f {
EnumWithCatchAll::CatchAll(any) => {
let d = any.extract::<Bound<'_, PyDict>>().expect("Expected pydict");
assert!(d.is_empty());
}
_ => panic!("Expected extracting EnumWithCatchAll::CatchAll, got {f:?}"),
}
});
}
#[derive(Debug, FromPyObject)]
pub enum Bar {
#[pyo3(annotation = "str")]
A(String),
#[pyo3(annotation = "uint")]
B(usize),
#[pyo3(annotation = "int", transparent)]
C(isize),
}
#[test]
fn test_err_rename() {
Python::attach(|py| {
let dict = PyDict::new(py);
let f = dict.extract::<Bar>();
assert!(f.is_err());
assert_eq!(
f.unwrap_err().to_string(),
"\
TypeError: failed to extract enum Bar ('str | uint | int')
- variant A (str): TypeError: failed to extract field Bar::A.0, caused by TypeError: 'dict' object is not an instance of 'str'
- variant B (uint): TypeError: failed to extract field Bar::B.0, caused by TypeError: 'dict' object cannot be interpreted as an integer
- variant C (int): TypeError: failed to extract field Bar::C.0, caused by TypeError: 'dict' object cannot be interpreted as an integer"
);
});
}
#[derive(Debug, FromPyObject)]
pub struct Zap {
#[pyo3(item)]
name: String,
#[pyo3(from_py_with = Bound::<'_, PyAny>::len, item("my_object"))]
some_object_length: usize,
}
#[test]
fn test_from_py_with() {
Python::attach(|py| {
let py_zap = py
.eval(
cr#"{"name": "whatever", "my_object": [1, 2, 3]}"#,
None,
None,
)
.expect("failed to create dict");
let zap = py_zap.extract::<Zap>().unwrap();
assert_eq!(zap.name, "whatever");
assert_eq!(zap.some_object_length, 3usize);
});
}
#[derive(Debug, FromPyObject)]
pub struct ZapTuple(
String,
#[pyo3(from_py_with = Bound::<'_, PyAny>::len)] usize,
);
#[test]
fn test_from_py_with_tuple_struct() {
Python::attach(|py| {
let py_zap = py
.eval(cr#"("whatever", [1, 2, 3])"#, None, None)
.expect("failed to create tuple");
let zap = py_zap.extract::<ZapTuple>().unwrap();
assert_eq!(zap.0, "whatever");
assert_eq!(zap.1, 3usize);
});
}
#[test]
fn test_from_py_with_tuple_struct_error() {
Python::attach(|py| {
let py_zap = py
.eval(cr#"("whatever", [1, 2, 3], "third")"#, None, None)
.expect("failed to create tuple");
let f = py_zap.extract::<ZapTuple>();
assert!(f.is_err());
assert_eq!(
f.unwrap_err().to_string(),
"ValueError: expected tuple of length 2, but got tuple of length 3"
);
});
}
#[derive(Debug, FromPyObject, PartialEq, Eq)]
pub enum ZapEnum {
Zip(#[pyo3(from_py_with = Bound::<'_, PyAny>::len)] usize),
Zap(
String,
#[pyo3(from_py_with = Bound::<'_, PyAny>::len)] usize,
),
}
#[test]
fn test_from_py_with_enum() {
Python::attach(|py| {
let py_zap = py
.eval(cr#"("whatever", [1, 2, 3])"#, None, None)
.expect("failed to create tuple");
let zap = py_zap.extract::<ZapEnum>().unwrap();
let expected_zap = ZapEnum::Zip(2);
assert_eq!(zap, expected_zap);
});
}
#[derive(Debug, FromPyObject, PartialEq, Eq)]
#[pyo3(transparent)]
pub struct TransparentFromPyWith {
#[pyo3(from_py_with = Bound::<'_, PyAny>::len)]
len: usize,
}
#[test]
fn test_transparent_from_py_with() {
Python::attach(|py| {
let result = PyList::new(py, [1, 2, 3])
.unwrap()
.extract::<TransparentFromPyWith>()
.unwrap();
let expected = TransparentFromPyWith { len: 3 };
assert_eq!(result, expected);
});
}
#[derive(Debug, FromPyObject, PartialEq, Eq)]
pub struct WithKeywordAttr {
r#box: usize,
}
#[pyclass]
pub struct WithKeywordAttrC {
#[pyo3(get)]
r#box: usize,
}
#[test]
fn test_with_keyword_attr() {
Python::attach(|py| {
let cls = WithKeywordAttrC { r#box: 3 }.into_pyobject(py).unwrap();
let result = cls.extract::<WithKeywordAttr>().unwrap();
let expected = WithKeywordAttr { r#box: 3 };
assert_eq!(result, expected);
});
}
#[derive(Debug, FromPyObject, PartialEq, Eq)]
pub struct WithKeywordItem {
#[pyo3(item)]
r#box: usize,
}
#[test]
fn test_with_keyword_item() {
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("box", 3).unwrap();
let result = dict.extract::<WithKeywordItem>().unwrap();
let expected = WithKeywordItem { r#box: 3 };
assert_eq!(result, expected);
});
}
#[derive(Debug, FromPyObject, PartialEq, Eq)]
pub struct WithDefaultItem {
#[pyo3(item, default)]
opt: Option<usize>,
#[pyo3(item)]
value: usize,
}
#[test]
fn test_with_default_item() {
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("value", 3).unwrap();
let result = dict.extract::<WithDefaultItem>().unwrap();
let expected = WithDefaultItem {
value: 3,
opt: None,
};
assert_eq!(result, expected);
});
}
#[derive(Debug, FromPyObject, PartialEq, Eq)]
pub struct WithExplicitDefaultItem {
#[pyo3(item, default = 1)]
opt: usize,
#[pyo3(item)]
value: usize,
}
#[test]
fn test_with_explicit_default_item() {
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("value", 3).unwrap();
let result = dict.extract::<WithExplicitDefaultItem>().unwrap();
let expected = WithExplicitDefaultItem { value: 3, opt: 1 };
assert_eq!(result, expected);
});
}
#[derive(Debug, FromPyObject, PartialEq, Eq)]
pub struct WithDefaultItemAndConversionFunction {
#[pyo3(item, default, from_py_with = Bound::<'_, PyAny>::len)]
opt: usize,
#[pyo3(item)]
value: usize,
}
#[test]
fn test_with_default_item_and_conversion_function() {
Python::attach(|py| {
// Filled case
let dict = PyDict::new(py);
dict.set_item("opt", (1,)).unwrap();
dict.set_item("value", 3).unwrap();
let result = dict
.extract::<WithDefaultItemAndConversionFunction>()
.unwrap();
let expected = WithDefaultItemAndConversionFunction { opt: 1, value: 3 };
assert_eq!(result, expected);
// Empty case
let dict = PyDict::new(py);
dict.set_item("value", 3).unwrap();
let result = dict
.extract::<WithDefaultItemAndConversionFunction>()
.unwrap();
let expected = WithDefaultItemAndConversionFunction { opt: 0, value: 3 };
assert_eq!(result, expected);
// Error case
let dict = PyDict::new(py);
dict.set_item("value", 3).unwrap();
dict.set_item("opt", 1).unwrap();
assert!(dict
.extract::<WithDefaultItemAndConversionFunction>()
.is_err());
});
}
#[derive(Debug, FromPyObject, PartialEq, Eq)]
pub enum WithDefaultItemEnum {
#[pyo3(from_item_all)]
Foo {
a: usize,
#[pyo3(default)]
b: usize,
},
NeverUsedA {
a: usize,
},
}
#[test]
fn test_with_default_item_enum() {
Python::attach(|py| {
// A and B filled
let dict = PyDict::new(py);
dict.set_item("a", 1).unwrap();
dict.set_item("b", 2).unwrap();
let result = dict.extract::<WithDefaultItemEnum>().unwrap();
let expected = WithDefaultItemEnum::Foo { a: 1, b: 2 };
assert_eq!(result, expected);
// A filled
let dict = PyDict::new(py);
dict.set_item("a", 1).unwrap();
let result = dict.extract::<WithDefaultItemEnum>().unwrap();
let expected = WithDefaultItemEnum::Foo { a: 1, b: 0 };
assert_eq!(result, expected);
});
}
+994
View File
@@ -0,0 +1,994 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(feature = "macros")]
use pyo3::class::PyTraverseError;
use pyo3::class::PyVisit;
use pyo3::ffi;
use pyo3::prelude::*;
#[cfg(not(Py_GIL_DISABLED))]
use pyo3::py_run;
#[cfg(not(target_arch = "wasm32"))]
use std::cell::Cell;
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use std::sync::Once;
use std::sync::{Arc, Mutex};
mod test_utils;
#[pyclass(freelist = 2)]
struct ClassWithFreelist {}
#[test]
fn class_with_freelist() {
let ptr = Python::attach(|py| {
let inst = Py::new(py, ClassWithFreelist {}).unwrap();
let _inst2 = Py::new(py, ClassWithFreelist {}).unwrap();
let ptr = inst.as_ptr();
drop(inst);
ptr
});
Python::attach(|py| {
let inst3 = Py::new(py, ClassWithFreelist {}).unwrap();
assert_eq!(ptr, inst3.as_ptr());
let inst4 = Py::new(py, ClassWithFreelist {}).unwrap();
assert_ne!(ptr, inst4.as_ptr())
});
}
#[pyclass(freelist = 2)]
#[cfg(not(target_arch = "wasm32"))]
struct ClassWithFreelistAndData {
data: Option<usize>,
}
#[cfg(not(target_arch = "wasm32"))]
fn spin_freelist(py: Python<'_>, data: usize) {
for _ in 0..500 {
let inst1 = Py::new(py, ClassWithFreelistAndData { data: Some(data) }).unwrap();
let inst2 = Py::new(py, ClassWithFreelistAndData { data: Some(data) }).unwrap();
assert_eq!(inst1.borrow(py).data, Some(data));
assert_eq!(inst2.borrow(py).data, Some(data));
}
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn multithreaded_class_with_freelist() {
std::thread::scope(|s| {
s.spawn(|| {
Python::attach(|py| spin_freelist(py, 12));
});
s.spawn(|| {
Python::attach(|py| spin_freelist(py, 0x4d3d3d3));
});
});
}
/// Helper function to create a pair of objects that can be used to test drops;
/// the first object is a guard that records when it has been dropped, the second
/// object is a check that can be used to assert that the guard has been dropped.
fn drop_check() -> (DropGuard, DropCheck) {
let flag = Arc::new(Once::new());
(DropGuard(flag.clone()), DropCheck(flag))
}
/// Helper structure that records when it has been dropped in the cor
struct DropGuard(Arc<Once>);
impl Drop for DropGuard {
fn drop(&mut self) {
self.0.call_once(|| ());
}
}
struct DropCheck(Arc<Once>);
impl DropCheck {
#[track_caller]
fn assert_not_dropped(&self) {
assert!(!self.0.is_completed());
}
#[track_caller]
fn assert_dropped(&self) {
assert!(self.0.is_completed());
}
#[track_caller]
fn assert_drops_with_gc(&self, object: *mut pyo3::ffi::PyObject) {
// running the GC might take a few cycles to collect an object
for _ in 0..100 {
if self.0.is_completed() {
return;
}
Python::attach(|py| {
py.run(c"import gc; gc.collect()", None, None).unwrap();
});
#[cfg(Py_GIL_DISABLED)]
{
// on the free-threaded build, the GC might be running in a separate thread, allow
// some time for this
std::thread::sleep(std::time::Duration::from_millis(5));
}
}
panic!(
"Object was not dropped after 100 GC cycles, refcount is {}",
// this could be garbage, but it's in a test and we're just printing the value
unsafe { ffi::Py_REFCNT(object) }
);
}
}
#[test]
fn data_is_dropped() {
#[pyclass]
struct DataIsDropped {
_guard1: DropGuard,
_guard2: DropGuard,
}
let (guard1, check1) = drop_check();
let (guard2, check2) = drop_check();
Python::attach(|py| {
let data_is_dropped = DataIsDropped {
_guard1: guard1,
_guard2: guard2,
};
let inst = Py::new(py, data_is_dropped).unwrap();
check1.assert_not_dropped();
check2.assert_not_dropped();
drop(inst);
});
check1.assert_dropped();
check2.assert_dropped();
}
#[pyclass(subclass)]
struct CycleWithClear {
cycle: Option<Py<PyAny>>,
_guard: DropGuard,
}
#[pymethods]
impl CycleWithClear {
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
visit.call(&self.cycle)
}
fn __clear__(slf: &Bound<'_, Self>) {
slf.borrow_mut().cycle = None;
}
}
#[test]
fn test_cycle_clear() {
let (guard, check) = drop_check();
let ptr = Python::attach(|py| {
let inst = Bound::new(
py,
CycleWithClear {
cycle: None,
_guard: guard,
},
)
.unwrap();
inst.borrow_mut().cycle = Some(inst.clone().into_any().unbind());
// gc.get_objects can create references to partially initialized objects,
// leading to races on the free-threaded build.
// see https://github.com/python/cpython/issues/130421#issuecomment-2682924142
#[cfg(not(Py_GIL_DISABLED))]
py_run!(py, inst, "import gc; assert inst in gc.get_objects()");
check.assert_not_dropped();
inst.as_ptr()
});
check.assert_drops_with_gc(ptr);
}
/// Test that traversing `None` of `Option<Py<T>>` does not cause a segfault
#[test]
fn gc_null_traversal() {
#[pyclass]
struct GcNullTraversal {
cycle: Option<Py<Self>>,
null: Option<Py<Self>>,
}
#[pymethods]
impl GcNullTraversal {
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
visit.call(&self.cycle)?;
visit.call(&self.null)?; // Should not segfault
Ok(())
}
fn __clear__(&mut self) {
self.cycle = None;
self.null = None;
}
}
Python::attach(|py| {
let obj = Py::new(
py,
GcNullTraversal {
cycle: None,
null: None,
},
)
.unwrap();
obj.borrow_mut(py).cycle = Some(obj.clone_ref(py));
// the object doesn't have to be cleaned up, it just needs to be traversed.
py.run(c"import gc; gc.collect()", None, None).unwrap();
});
}
#[test]
fn inheritance_with_new_methods_with_drop() {
#[pyclass(subclass)]
struct BaseClassWithDrop {
guard: Option<DropGuard>,
}
#[pymethods]
impl BaseClassWithDrop {
#[new]
fn new() -> BaseClassWithDrop {
BaseClassWithDrop { guard: None }
}
}
#[pyclass(extends = BaseClassWithDrop)]
struct SubClassWithDrop {
guard: Option<DropGuard>,
}
#[pymethods]
impl SubClassWithDrop {
#[new]
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(BaseClassWithDrop { guard: None })
.add_subclass(SubClassWithDrop { guard: None })
}
}
let (guard_base, check_base) = drop_check();
let (guard_sub, check_sub) = drop_check();
Python::attach(|py| {
let typeobj = py.get_type::<SubClassWithDrop>();
let inst = typeobj
.call((), None)
.unwrap()
.cast_into::<SubClassWithDrop>()
.unwrap();
inst.as_super().borrow_mut().guard = Some(guard_base);
inst.borrow_mut().guard = Some(guard_sub);
check_base.assert_not_dropped();
check_sub.assert_not_dropped();
});
check_base.assert_dropped();
check_sub.assert_dropped();
}
#[test]
fn gc_during_borrow() {
#[pyclass]
struct TraversableClass {
traversed: AtomicBool,
}
impl TraversableClass {
fn new() -> Self {
Self {
traversed: AtomicBool::new(false),
}
}
}
#[pymethods]
impl TraversableClass {
fn __clear__(&mut self) {}
#[expect(clippy::unnecessary_wraps)]
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
self.traversed.store(true, Ordering::Relaxed);
Ok(())
}
}
Python::attach(|py| {
// get the traverse function
let ty = py.get_type::<TraversableClass>();
let traverse = unsafe { get_type_traverse(ty.as_type_ptr()).unwrap() };
// create an object and check that traversing it works normally
// when it's not borrowed
let cell = Bound::new(py, TraversableClass::new()).unwrap();
assert!(!cell.borrow().traversed.load(Ordering::Relaxed));
unsafe { traverse(cell.as_ptr(), novisit, std::ptr::null_mut()) };
assert!(cell.borrow().traversed.load(Ordering::Relaxed));
// create an object and check that it is not traversed if the GC
// is invoked while it is already borrowed mutably
let cell2 = Bound::new(py, TraversableClass::new()).unwrap();
let guard = cell2.borrow_mut();
assert!(!guard.traversed.load(Ordering::Relaxed));
unsafe { traverse(cell2.as_ptr(), novisit, std::ptr::null_mut()) };
assert!(!guard.traversed.load(Ordering::Relaxed));
drop(guard);
});
}
#[test]
fn traverse_partial() {
#[pyclass]
struct PartialTraverse {
member: Py<PyAny>,
}
impl PartialTraverse {
fn new(py: Python<'_>) -> Self {
Self { member: py.None() }
}
}
#[pymethods]
impl PartialTraverse {
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
visit.call(&self.member)?;
// In the test, we expect this to never be hit
unreachable!()
}
}
Python::attach(|py| {
// get the traverse function
let ty = py.get_type::<PartialTraverse>();
let traverse = unsafe { get_type_traverse(ty.as_type_ptr()).unwrap() };
// confirm that traversing errors
let obj = Py::new(py, PartialTraverse::new(py)).unwrap();
assert_eq!(
unsafe { traverse(obj.as_ptr(), visit_error, std::ptr::null_mut()) },
-1
);
})
}
#[test]
#[cfg(panic = "unwind")]
fn traverse_panic() {
#[pyclass]
struct PanickyTraverse {
member: Py<PyAny>,
}
impl PanickyTraverse {
fn new(py: Python<'_>) -> Self {
Self { member: py.None() }
}
}
#[pymethods]
impl PanickyTraverse {
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
visit.call(&self.member)?;
panic!("at the disco");
}
}
Python::attach(|py| {
// get the traverse function
let ty = py.get_type::<PanickyTraverse>();
let traverse = unsafe { get_type_traverse(ty.as_type_ptr()).unwrap() };
// confirm that traversing errors
let obj = Py::new(py, PanickyTraverse::new(py)).unwrap();
assert_eq!(
unsafe { traverse(obj.as_ptr(), novisit, std::ptr::null_mut()) },
-1
);
})
}
#[test]
#[cfg(panic = "unwind")]
fn tries_gil_in_traverse() {
#[pyclass]
struct TriesGILInTraverse {}
#[pymethods]
impl TriesGILInTraverse {
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
Python::attach(|_py| Ok(()))
}
}
Python::attach(|py| {
// get the traverse function
let ty = py.get_type::<TriesGILInTraverse>();
let traverse = unsafe { get_type_traverse(ty.as_type_ptr()).unwrap() };
// confirm that traversing panicks
let obj = Py::new(py, TriesGILInTraverse {}).unwrap();
assert_eq!(
unsafe { traverse(obj.as_ptr(), novisit, std::ptr::null_mut()) },
-1
);
})
}
#[test]
fn traverse_cannot_be_hijacked() {
#[pyclass]
struct HijackedTraverse {
traversed: AtomicBool,
hijacked: AtomicBool,
}
impl HijackedTraverse {
fn new() -> Self {
Self {
traversed: AtomicBool::new(false),
hijacked: AtomicBool::new(false),
}
}
fn traversed_and_hijacked(&self) -> (bool, bool) {
(
self.traversed.load(Ordering::Acquire),
self.hijacked.load(Ordering::Acquire),
)
}
}
#[pymethods]
impl HijackedTraverse {
#[expect(clippy::unnecessary_wraps)]
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
self.traversed.store(true, Ordering::Release);
Ok(())
}
}
#[allow(dead_code, reason = "used to test the trait method is not used")]
trait Traversable {
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>;
}
impl Traversable for PyRef<'_, HijackedTraverse> {
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
self.hijacked.store(true, Ordering::Release);
Ok(())
}
}
Python::attach(|py| {
// get the traverse function
let ty = py.get_type::<HijackedTraverse>();
let traverse = unsafe { get_type_traverse(ty.as_type_ptr()).unwrap() };
let cell = Bound::new(py, HijackedTraverse::new()).unwrap();
assert_eq!(cell.borrow().traversed_and_hijacked(), (false, false));
unsafe { traverse(cell.as_ptr(), novisit, std::ptr::null_mut()) };
assert_eq!(cell.borrow().traversed_and_hijacked(), (true, false));
})
}
#[pyclass]
struct DropDuringTraversal {
cycle: Mutex<Option<Py<Self>>>,
_guard: DropGuard,
}
#[pymethods]
impl DropDuringTraversal {
#[expect(clippy::unnecessary_wraps)]
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
let mut cycle_ref = self.cycle.lock().unwrap();
*cycle_ref = None;
Ok(())
}
}
#[cfg(not(pyo3_disable_reference_pool))]
#[test]
fn drop_during_traversal_with_gil() {
let (guard, check) = drop_check();
let ptr = Python::attach(|py| {
let cycle = Mutex::new(None);
let inst = Py::new(
py,
DropDuringTraversal {
cycle,
_guard: guard,
},
)
.unwrap();
*inst.borrow_mut(py).cycle.lock().unwrap() = Some(inst.clone_ref(py));
check.assert_not_dropped();
let ptr = inst.as_ptr();
drop(inst); // drop the object while holding the GIL
#[cfg(not(Py_GIL_DISABLED))]
{
// other thread might have caused GC on free-threaded build
check.assert_not_dropped();
}
ptr
});
check.assert_drops_with_gc(ptr);
}
#[cfg(not(pyo3_disable_reference_pool))]
#[test]
fn drop_during_traversal_without_gil() {
let (guard, check) = drop_check();
let inst = Python::attach(|py| {
let cycle = Mutex::new(None);
let inst = Py::new(
py,
DropDuringTraversal {
cycle,
_guard: guard,
},
)
.unwrap();
*inst.borrow_mut(py).cycle.lock().unwrap() = Some(inst.clone_ref(py));
check.assert_not_dropped();
inst
});
let ptr = inst.as_ptr();
drop(inst); // drop the object without holding the GIL
check.assert_drops_with_gc(ptr);
}
#[test]
#[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
fn unsendable_are_not_traversed_on_foreign_thread() {
#[pyclass(unsendable)]
struct UnsendableTraversal {
traversed: Cell<bool>,
}
#[pymethods]
impl UnsendableTraversal {
fn __clear__(&mut self) {}
#[expect(clippy::unnecessary_wraps)]
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
self.traversed.set(true);
Ok(())
}
}
#[derive(Clone, Copy)]
struct SendablePtr(*mut pyo3::ffi::PyObject);
unsafe impl Send for SendablePtr {}
Python::attach(|py| {
let ty = py.get_type::<UnsendableTraversal>();
let traverse = unsafe { get_type_traverse(ty.as_type_ptr()).unwrap() };
let obj = Bound::new(
py,
UnsendableTraversal {
traversed: Cell::new(false),
},
)
.unwrap();
let ptr = SendablePtr(obj.as_ptr());
std::thread::spawn(move || {
// traversal on foreign thread is a no-op
assert_eq!(
unsafe { traverse({ ptr }.0, novisit, std::ptr::null_mut()) },
0
);
})
.join()
.unwrap();
assert!(!obj.borrow().traversed.get());
// traversal on home thread still works
assert_eq!(
unsafe { traverse({ ptr }.0, novisit, std::ptr::null_mut()) },
0
);
assert!(obj.borrow().traversed.get());
});
}
#[test]
fn test_traverse_subclass() {
#[pyclass(extends = CycleWithClear)]
struct SubOverrideTraverse {}
#[pymethods]
impl SubOverrideTraverse {
#[expect(clippy::unnecessary_wraps)]
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
// subclass traverse overrides the base class traverse
Ok(())
}
}
let (guard, check) = drop_check();
Python::attach(|py| {
let base = CycleWithClear {
cycle: None,
_guard: guard,
};
let obj = Bound::new(
py,
PyClassInitializer::from(base).add_subclass(SubOverrideTraverse {}),
)
.unwrap();
obj.borrow_mut().as_super().cycle = Some(obj.clone().into_any().unbind());
check.assert_not_dropped();
let ptr = obj.as_ptr();
drop(obj);
#[cfg(not(Py_GIL_DISABLED))]
{
// other thread might have caused GC on free-threaded build
check.assert_not_dropped();
}
check.assert_drops_with_gc(ptr);
});
}
#[test]
fn test_traverse_subclass_override_clear() {
#[pyclass(extends = CycleWithClear)]
struct SubOverrideClear {}
#[pymethods]
impl SubOverrideClear {
#[expect(clippy::unnecessary_wraps)]
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
// subclass traverse overrides the base class traverse, necessary for
// the sub clear to be called
// FIXME: should this really need to be the case?
Ok(())
}
fn __clear__(&self) {
// subclass clear overrides the base class clear
}
}
let (guard, check) = drop_check();
Python::attach(|py| {
let base = CycleWithClear {
cycle: None,
_guard: guard,
};
let obj = Bound::new(
py,
PyClassInitializer::from(base).add_subclass(SubOverrideClear {}),
)
.unwrap();
obj.borrow_mut().as_super().cycle = Some(obj.clone().into_any().unbind());
check.assert_not_dropped();
let ptr = obj.as_ptr();
drop(obj);
#[cfg(not(Py_GIL_DISABLED))]
{
// other thread might have caused GC on free-threaded build
check.assert_not_dropped();
}
check.assert_drops_with_gc(ptr);
});
}
// Manual traversal utilities
unsafe fn get_type_traverse(tp: *mut pyo3::ffi::PyTypeObject) -> Option<pyo3::ffi::traverseproc> {
unsafe { std::mem::transmute(pyo3::ffi::PyType_GetSlot(tp, pyo3::ffi::Py_tp_traverse)) }
}
// a dummy visitor function
extern "C" fn novisit(
_object: *mut pyo3::ffi::PyObject,
_arg: *mut core::ffi::c_void,
) -> std::ffi::c_int {
0
}
// a visitor function which errors (returns nonzero code)
extern "C" fn visit_error(
_object: *mut pyo3::ffi::PyObject,
_arg: *mut core::ffi::c_void,
) -> std::ffi::c_int {
-1
}
// the fields visited below, set before driving the traversal
static BASE_FIELD: AtomicPtr<pyo3::ffi::PyObject> = AtomicPtr::new(std::ptr::null_mut());
static CHILD_FIELD: AtomicPtr<pyo3::ffi::PyObject> = AtomicPtr::new(std::ptr::null_mut());
static BASE_VISITED: AtomicBool = AtomicBool::new(false);
static CHILD_VISITED: AtomicBool = AtomicBool::new(false);
// a visitor function which errors on `BASE_FIELD` only
extern "C" fn visit_error_on_base_field(
object: *mut pyo3::ffi::PyObject,
_arg: *mut core::ffi::c_void,
) -> std::ffi::c_int {
if object == CHILD_FIELD.load(Ordering::SeqCst) {
CHILD_VISITED.store(true, Ordering::SeqCst);
}
if object == BASE_FIELD.load(Ordering::SeqCst) {
BASE_VISITED.store(true, Ordering::SeqCst);
return -1;
}
0
}
#[test]
#[cfg(any(not(Py_LIMITED_API), Py_3_11))] // buffer availability
fn test_drop_buffer_during_traversal_without_gil() {
use pyo3::buffer::PyBuffer;
use pyo3::types::PyBytes;
// `PyBuffer` has a drop method which attempts to attach to the Python interpreter,
// if the thread is during traverse we leak it for safety. This should _never_ be happening
// so it's purely a user bug, but we leak to be safe.
#[pyclass]
struct BufferDropDuringTraversal {
inner: Mutex<Option<(DropGuard, PyBuffer<u8>)>>,
cycle: Option<Py<PyAny>>,
}
#[pymethods]
impl BufferDropDuringTraversal {
#[expect(clippy::unnecessary_wraps)]
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
self.inner.lock().unwrap().take();
Ok(())
}
fn __clear__(&mut self) {
self.cycle = None;
}
}
let (guard, check) = drop_check();
Python::attach(|py| {
let obj = Py::new(
py,
BufferDropDuringTraversal {
inner: Mutex::new(Some((
guard,
PyBuffer::get(&PyBytes::new(py, b"test")).unwrap(),
))),
cycle: None,
},
)
.unwrap();
obj.borrow_mut(py).cycle = Some(obj.clone_ref(py).into_any());
let ptr = obj.as_ptr();
drop(obj);
check.assert_drops_with_gc(ptr);
});
}
#[test]
fn test_super_traverse_early_return_does_not_abort() {
#[pyclass(subclass)]
struct TraverseBase {
field: Py<PyAny>,
}
#[pymethods]
impl TraverseBase {
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
visit.call(&self.field)
}
}
#[pyclass(extends=TraverseBase)]
struct TraverseChild {
field: Py<PyAny>,
}
#[pymethods]
impl TraverseChild {
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
visit.call(&self.field)
}
}
Python::attach(|py| {
let base_field = pyo3::types::PyList::empty(py).into_any().unbind();
let child_field = pyo3::types::PyList::empty(py).into_any().unbind();
let initializer = PyClassInitializer::from(TraverseBase {
field: base_field.clone_ref(py),
})
.add_subclass(TraverseChild {
field: child_field.clone_ref(py),
});
let child = Bound::new(py, initializer).unwrap();
BASE_FIELD.store(base_field.as_ptr(), Ordering::SeqCst);
CHILD_FIELD.store(child_field.as_ptr(), Ordering::SeqCst);
let traverse =
unsafe { get_type_traverse(py.get_type::<TraverseChild>().as_type_ptr()).unwrap() };
let retval = unsafe {
traverse(
child.as_ptr(),
visit_error_on_base_field,
std::ptr::null_mut(),
)
};
assert!(
BASE_VISITED.load(Ordering::SeqCst),
"super-type traverse never visited its field, so the early return was not exercised"
);
assert_eq!(
retval, -1,
"traverse did not propagate the non-zero return of the super-type traverse"
);
assert!(
!CHILD_VISITED.load(Ordering::SeqCst),
"child __traverse__ ran despite the super-type traverse returning non-zero"
);
});
}
// A `#[pyclass(dict)]` can form a reference cycle through its instance `__dict__`
// (`obj.attr = obj`). The tests below cover each valid combination of user-defined
// `__traverse__` / `__clear__`; a `__clear__` without a `__traverse__` is rejected at
// type-creation time.
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[test]
fn dict_class_is_a_gc_type() {
Python::attach(|py| {
let ty = py.get_type::<DictCycleNoTraverse>();
let flags = unsafe { ffi::PyType_GetFlags(ty.as_type_ptr()) };
assert_ne!(flags & ffi::Py_TPFLAGS_HAVE_GC, 0);
});
}
/// `#[pyclass(dict)]` with neither `__traverse__` nor `__clear__`: both slots are synthesized.
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[pyclass(dict)]
struct DictCycleNoTraverse {
_guard: DropGuard,
}
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[test]
fn dict_cycle_collected_without_traverse() {
let (guard, check) = drop_check();
let ptr = Python::attach(|py| {
let inst = Bound::new(py, DictCycleNoTraverse { _guard: guard }).unwrap();
// Reference cycle through the instance `__dict__`: inst.__dict__["cycle"] -> inst.
inst.setattr("cycle", &inst).unwrap();
check.assert_not_dropped();
inst.as_ptr()
});
check.assert_drops_with_gc(ptr);
}
/// `#[pyclass(dict)]` with `__traverse__` but no `__clear__`: the `__dict__` is visited by
/// `_call_traverse` and cleared by a synthesized `tp_clear`.
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[pyclass(dict)]
struct DictCycleTraverseOnly {
_guard: DropGuard,
}
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[pymethods]
impl DictCycleTraverseOnly {
#[expect(clippy::unnecessary_wraps)]
fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
// No Rust references to visit; the `__dict__` is visited by `_call_traverse` itself.
Ok(())
}
}
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[test]
fn dict_cycle_collected_with_traverse_only() {
let (guard, check) = drop_check();
let ptr = Python::attach(|py| {
let inst = Bound::new(py, DictCycleTraverseOnly { _guard: guard }).unwrap();
inst.setattr("cycle", &inst).unwrap();
check.assert_not_dropped();
inst.as_ptr()
});
check.assert_drops_with_gc(ptr);
}
/// `#[pyclass(dict)]` with both `__traverse__` and `__clear__`: the `__dict__` is folded into
/// the user-defined slots by `_call_traverse` / `_call_clear`.
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[pyclass(dict)]
struct DictCycleTraverseAndClear {
_guard: DropGuard,
field: Option<Py<PyAny>>,
}
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[pymethods]
impl DictCycleTraverseAndClear {
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
if let Some(field) = &self.field {
visit.call(field)?;
}
Ok(())
}
fn __clear__(&mut self) {
self.field = None;
}
}
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
#[test]
fn dict_cycle_collected_with_traverse_and_clear() {
let (guard, check) = drop_check();
let ptr = Python::attach(|py| {
let inst = Bound::new(
py,
DictCycleTraverseAndClear {
_guard: guard,
field: None,
},
)
.unwrap();
inst.setattr("cycle", &inst).unwrap();
check.assert_not_dropped();
inst.as_ptr()
});
check.assert_drops_with_gc(ptr);
}
+356
View File
@@ -0,0 +1,356 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::PyString;
use pyo3::types::{IntoPyDict, PyList};
use std::cell::Cell;
use std::convert::Infallible;
mod test_utils;
#[pyclass]
struct ClassWithProperties {
num: i32,
}
#[pymethods]
impl ClassWithProperties {
fn get_num(&self) -> i32 {
self.num
}
#[getter(DATA)]
/// a getter for data
fn get_data(&self) -> i32 {
self.num
}
#[setter(DATA)]
fn set_data(&mut self, value: i32) {
self.num = value;
}
#[getter]
/// a getter with a type un-wrapped by PyResult
fn get_unwrapped(&self) -> i32 {
self.num
}
#[setter]
fn set_unwrapped(&mut self, value: i32) {
self.num = value;
}
#[setter]
fn set_from_len(&mut self, #[pyo3(from_py_with = extract_len)] value: i32) {
self.num = value;
}
#[setter]
fn set_from_any(&mut self, value: &Bound<'_, PyAny>) -> PyResult<()> {
self.num = value.extract()?;
Ok(())
}
#[getter]
fn get_data_list<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
PyList::new(py, [self.num])
}
}
fn extract_len(any: &Bound<'_, PyAny>) -> PyResult<i32> {
any.len().map(|len| len as i32)
}
#[test]
fn class_with_properties() {
Python::attach(|py| {
let inst = Py::new(py, ClassWithProperties { num: 10 }).unwrap();
py_run!(py, inst, "assert inst.get_num() == 10");
py_run!(py, inst, "assert inst.get_num() == inst.DATA");
py_run!(py, inst, "inst.DATA = 20");
py_run!(py, inst, "assert inst.get_num() == 20 == inst.DATA");
py_expect_exception!(py, inst, "del inst.DATA", PyAttributeError);
py_run!(py, inst, "assert inst.get_num() == inst.unwrapped == 20");
py_run!(py, inst, "inst.unwrapped = 42");
py_run!(py, inst, "assert inst.get_num() == inst.unwrapped == 42");
py_run!(py, inst, "assert inst.data_list == [42]");
py_run!(py, inst, "inst.from_len = [0, 0, 0]");
py_run!(py, inst, "assert inst.get_num() == 3");
py_run!(py, inst, "inst.from_any = 15");
py_run!(py, inst, "assert inst.get_num() == 15");
let d = [("C", py.get_type::<ClassWithProperties>())]
.into_py_dict(py)
.unwrap();
py_assert!(py, *d, "C.DATA.__doc__ == 'a getter for data'");
});
}
#[pyclass]
struct GetterSetter {
#[pyo3(get, set)]
num: i32,
#[pyo3(get, set)]
text: String,
}
#[pymethods]
impl GetterSetter {
fn get_num2(&self) -> i32 {
self.num
}
}
#[test]
fn getter_setter_autogen() {
Python::attach(|py| {
let inst = Py::new(
py,
GetterSetter {
num: 10,
text: "Hello".to_string(),
},
)
.unwrap();
py_run!(py, inst, "assert inst.num == 10");
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
py_run!(
py,
inst,
"assert inst.text == 'Hello'; inst.text = 'There'; assert inst.text == 'There'"
);
});
}
#[pyclass]
struct RefGetterSetter {
num: i32,
}
#[pymethods]
impl RefGetterSetter {
#[getter]
fn get_num(slf: PyRef<'_, Self>) -> i32 {
slf.num
}
#[setter]
fn set_num(mut slf: PyRefMut<'_, Self>, value: i32) {
slf.num = value;
}
}
#[test]
fn ref_getter_setter() {
// Regression test for #837
Python::attach(|py| {
let inst = Py::new(py, RefGetterSetter { num: 10 }).unwrap();
py_run!(py, inst, "assert inst.num == 10");
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
});
}
#[pyclass]
struct TupleClassGetterSetter(i32);
#[pymethods]
impl TupleClassGetterSetter {
#[getter(num)]
fn get_num(&self) -> i32 {
self.0
}
#[setter(num)]
fn set_num(&mut self, value: i32) {
self.0 = value;
}
}
#[test]
fn tuple_struct_getter_setter() {
Python::attach(|py| {
let inst = Py::new(py, TupleClassGetterSetter(10)).unwrap();
py_assert!(py, inst, "inst.num == 10");
py_run!(py, inst, "inst.num = 20");
py_assert!(py, inst, "inst.num == 20");
});
}
#[pyclass(get_all, set_all)]
struct All {
num: i32,
}
#[test]
fn get_set_all() {
Python::attach(|py| {
let inst = Py::new(py, All { num: 10 }).unwrap();
py_run!(py, inst, "assert inst.num == 10");
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
});
}
#[pyclass(get_all)]
struct All2 {
#[pyo3(set)]
num: i32,
}
#[test]
fn get_all_and_set() {
Python::attach(|py| {
let inst = Py::new(py, All2 { num: 10 }).unwrap();
py_run!(py, inst, "assert inst.num == 10");
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
});
}
#[pyclass(unsendable)]
struct CellGetterSetter {
#[pyo3(get, set)]
cell_inner: Cell<i32>,
}
#[test]
fn cell_getter_setter() {
let c = CellGetterSetter {
cell_inner: Cell::new(10),
};
Python::attach(|py| {
let inst = Py::new(py, c).unwrap();
let cell = Cell::new(20i32).into_pyobject(py).unwrap();
py_run!(py, cell, "assert cell == 20");
py_run!(py, inst, "assert inst.cell_inner == 10");
py_run!(
py,
inst,
"inst.cell_inner = 20; assert inst.cell_inner == 20"
);
});
}
#[test]
fn borrowed_value_with_lifetime_of_self() {
#[pyclass]
struct BorrowedValue {}
#[pymethods]
impl BorrowedValue {
#[getter]
fn value(&self) -> &str {
"value"
}
}
Python::attach(|py| {
let inst = Py::new(py, BorrowedValue {}).unwrap();
py_run!(py, inst, "assert inst.value == 'value'");
});
}
#[test]
fn frozen_py_field_get() {
#[pyclass(frozen)]
struct FrozenPyField {
#[pyo3(get)]
value: Py<PyString>,
}
Python::attach(|py| {
let inst = Py::new(
py,
FrozenPyField {
value: "value".into_pyobject(py).unwrap().unbind(),
},
)
.unwrap();
py_run!(py, inst, "assert inst.value == 'value'");
});
}
#[test]
fn test_optional_setter() {
#[pyclass]
struct SimpleClass {
field: Option<u32>,
}
#[pymethods]
impl SimpleClass {
#[getter]
fn get_field(&self) -> Option<u32> {
self.field
}
#[setter]
fn set_field(&mut self, field: Option<u32>) {
self.field = field;
}
}
Python::attach(|py| {
let instance = Py::new(py, SimpleClass { field: None }).unwrap();
py_run!(py, instance, "assert instance.field is None");
py_run!(
py,
instance,
"instance.field = 42; assert instance.field == 42"
);
py_run!(
py,
instance,
"instance.field = None; assert instance.field is None"
);
})
}
#[test]
fn test_ref_only_getter() {
#[derive(Clone)]
struct RefOnly;
impl<'py> IntoPyObject<'py> for &RefOnly {
type Target = PyString;
type Output = Bound<'py, PyString>;
type Error = Infallible;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyString::new(py, "value"))
}
}
#[pyclass]
struct Container {
#[pyo3(get)]
value: RefOnly,
}
}
#[test]
fn test_unit_getter() {
#[derive(Clone)]
#[pyclass(skip_from_py_object)]
struct Container {
#[pyo3(get)]
value: (),
}
Python::attach(|py| {
let instance = Py::new(py, Container { value: () }).unwrap();
py_run!(py, instance, "assert instance.value is ()");
})
}
+541
View File
@@ -0,0 +1,541 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::IntoPyDict;
mod test_utils;
/// Macro to generate refcount leak tests for types.
/// Ensures that creating and destroying instances doesn't leak references to the type.
/// Regression test for issues #1363 and #6223.
#[cfg(not(Py_GIL_DISABLED))]
macro_rules! assert_type_refcount_stable {
// Simple case: type with parameterless constructor
($type_name:ty) => {
assert_type_refcount_stable!($type_name, stringify!($type_name), "Type()");
};
// With custom constructor
($type_name:ty, $test_name:expr, $ctor:expr) => {{
Python::attach(|py| {
#[expect(non_snake_case)]
let Type = py.get_type::<$type_name>();
let ctor_code = $ctor;
py_run!(
py,
Type,
&format!(
r#"
import gc
import sys
gc.collect()
count = sys.getrefcount(Type)
for i in range(1000):
obj = {}
del obj
gc.collect()
after = sys.getrefcount(Type)
assert after == count, f"Type ref count leaked: {{after}} vs {{count}}"
"#,
ctor_code
)
);
});
}};
}
#[pyclass(subclass)]
struct BaseClass {
#[pyo3(get)]
val1: usize,
}
#[pyclass(subclass)]
struct SubclassAble {}
#[test]
fn subclass() {
Python::attach(|py| {
let d = [("SubclassAble", py.get_type::<SubclassAble>())]
.into_py_dict(py)
.unwrap();
py.run(
c"class A(SubclassAble): pass\nassert issubclass(A, SubclassAble)",
None,
Some(&d),
)
.map_err(|e| e.display(py))
.unwrap();
});
}
#[pymethods]
impl BaseClass {
#[new]
fn new() -> Self {
BaseClass { val1: 10 }
}
fn base_method(&self, x: usize) -> usize {
x * self.val1
}
fn base_set(&mut self, fn_: &Bound<'_, PyAny>) -> PyResult<()> {
let value: usize = fn_.call0()?.extract()?;
self.val1 = value;
Ok(())
}
}
#[pyclass(extends=BaseClass)]
struct SubClass {
#[pyo3(get)]
val2: usize,
}
#[pymethods]
impl SubClass {
#[new]
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(BaseClass { val1: 10 }).add_subclass(SubClass { val2: 5 })
}
fn sub_method(&self, x: usize) -> usize {
x * self.val2
}
fn sub_set_and_ret(&mut self, x: usize) -> usize {
self.val2 = x;
x
}
}
#[test]
fn inheritance_with_new_methods() {
Python::attach(|py| {
let typeobj = py.get_type::<SubClass>();
let inst = typeobj.call((), None).unwrap();
py_run!(py, inst, "assert inst.val1 == 10; assert inst.val2 == 5");
});
}
#[test]
fn call_base_and_sub_methods() {
Python::attach(|py| {
let obj = Py::new(py, SubClass::new()).unwrap();
py_run!(
py,
obj,
r#"
assert obj.base_method(10) == 100
assert obj.sub_method(10) == 50
"#
);
});
}
#[test]
fn mutation_fails() {
Python::attach(|py| {
let obj = Py::new(py, SubClass::new()).unwrap();
let global = [("obj", obj)].into_py_dict(py).unwrap();
let e = py
.run(
c"obj.base_set(lambda: obj.sub_set_and_ret(1))",
Some(&global),
None,
)
.unwrap_err();
assert_eq!(&e.to_string(), "RuntimeError: Already borrowed");
});
}
#[test]
fn is_subclass_and_is_instance() {
Python::attach(|py| {
let sub_ty = py.get_type::<SubClass>();
let base_ty = py.get_type::<BaseClass>();
assert!(sub_ty.is_subclass_of::<BaseClass>().unwrap());
assert!(sub_ty.is_subclass(&base_ty).unwrap());
let obj = Bound::new(py, SubClass::new()).unwrap().into_any();
assert!(obj.is_instance_of::<SubClass>());
assert!(obj.is_instance_of::<BaseClass>());
assert!(obj.is_instance(&sub_ty).unwrap());
assert!(obj.is_instance(&base_ty).unwrap());
});
}
#[pyclass(subclass)]
struct BaseClassWithResult {
_val: usize,
}
#[pymethods]
impl BaseClassWithResult {
#[new]
fn new(value: isize) -> PyResult<Self> {
Ok(Self {
_val: std::convert::TryFrom::try_from(value)?,
})
}
}
#[pyclass(extends=BaseClassWithResult)]
struct SubClass2 {}
#[pymethods]
impl SubClass2 {
#[new]
fn new(value: isize) -> PyResult<PyClassInitializer<Self>> {
let base = BaseClassWithResult::new(value)?;
Ok(PyClassInitializer::from(base).add_subclass(Self {}))
}
}
#[test]
fn handle_result_in_new() {
Python::attach(|py| {
let subclass = py.get_type::<SubClass2>();
py_run!(
py,
subclass,
r#"
try:
subclass(-10)
assert False
except ValueError as e:
pass
except Exception as e:
raise e
"#
);
});
}
// Subclassing builtin types is not possible in the LIMITED API before 3.12
#[cfg(any(not(Py_LIMITED_API), Py_3_12))]
mod inheriting_native_type {
use super::*;
use pyo3::exceptions::PyException;
#[cfg(not(GraalPy))]
use {
pyo3::types::{PyCapsule, PyDict},
std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
#[cfg(not(any(PyPy, GraalPy)))]
use pyo3::types::PySet;
#[cfg(not(any(PyPy, GraalPy)))]
#[pyclass(extends=PySet)]
#[derive(Debug)]
pub struct SetWithName {
#[pyo3(get, name = "name")]
_name: &'static str,
}
#[cfg(not(any(PyPy, GraalPy)))]
#[pymethods]
impl SetWithName {
#[new]
fn new() -> Self {
SetWithName { _name: "Hello :)" }
}
}
#[cfg(not(any(PyPy, GraalPy)))]
#[test]
fn inherit_set() {
Python::attach(|py| {
let set_sub = pyo3::Py::new(py, SetWithName::new()).unwrap();
py_run!(
py,
set_sub,
r#"set_sub.add(10); assert list(set_sub) == [10]; assert set_sub.name == "Hello :)""#
);
});
}
#[cfg(not(GraalPy))]
#[pyclass(extends=PyDict)]
#[derive(Debug)]
struct DictWithName {
#[pyo3(get, name = "name")]
_name: &'static str,
}
#[cfg(not(GraalPy))]
#[pymethods]
impl DictWithName {
#[new]
fn new() -> Self {
DictWithName { _name: "Hello :)" }
}
}
#[cfg(not(GraalPy))]
#[test]
fn inherit_dict() {
Python::attach(|py| {
let dict_sub = pyo3::Py::new(py, DictWithName::new()).unwrap();
py_run!(
py,
dict_sub,
r#"dict_sub[0] = 1; assert dict_sub[0] == 1; assert dict_sub.name == "Hello :)""#
);
});
}
#[cfg(not(GraalPy))]
#[test]
fn inherit_dict_drop() {
Python::attach(|py| {
let dropped = Arc::new(AtomicBool::new(false));
let destructor_drop = Arc::clone(&dropped);
let item = PyCapsule::new_with_value_and_destructor(
py,
0,
c"inherit_dict_drop",
move |_, _| destructor_drop.store(true, Ordering::Relaxed),
)
.unwrap();
let dict_sub = pyo3::Py::new(py, DictWithName::new()).unwrap();
dict_sub.bind(py).set_item("foo", &item).unwrap();
drop(item);
assert!(!dropped.load(Ordering::Relaxed));
drop(dict_sub);
assert!(dropped.load(Ordering::Relaxed));
})
}
#[pyclass(extends=PyException)]
struct CustomException {
#[pyo3(get)]
context: &'static str,
}
#[pymethods]
impl CustomException {
#[new]
fn new(_exc_arg: &Bound<'_, PyAny>) -> Self {
CustomException {
context: "Hello :)",
}
}
}
#[test]
fn custom_exception() {
Python::attach(|py| {
let cls = py.get_type::<CustomException>();
let dict = [("cls", &cls)].into_py_dict(py).unwrap();
let res = py.run(
c"e = cls('hello'); assert str(e) == 'hello'; assert e.context == 'Hello :)'; raise e",
None,
Some(&dict)
);
let err = res.unwrap_err();
assert!(err.matches(py, &cls).unwrap(), "{}", err);
// catching the exception in Python also works:
py_run!(
py,
cls,
r#"
try:
raise cls("foo")
except cls:
pass
"#
)
})
}
#[cfg(Py_3_12)]
#[pyclass(extends=pyo3::types::PyTzInfo)]
struct TzInfoWithName {
#[pyo3(get)]
name: &'static str,
}
#[cfg(Py_3_12)]
#[pymethods]
impl TzInfoWithName {
#[new]
fn new() -> Self {
Self { name: "Hello :)" }
}
#[pyo3(signature = (_dt, /))]
fn utcoffset<'py>(
&self,
_dt: Option<&Bound<'_, pyo3::types::PyDateTime>>,
py: Python<'py>,
) -> PyResult<Bound<'py, pyo3::types::PyDelta>> {
pyo3::types::PyDelta::new(py, 0, 3600, 0, true)
}
}
#[cfg(Py_3_12)]
#[test]
fn inherit_tzinfo() {
Python::attach(|py| {
let tz = pyo3::Py::new(py, TzInfoWithName::new()).unwrap();
py_run!(
py,
tz,
r#"
import datetime
assert isinstance(tz, datetime.tzinfo)
assert tz.name == "Hello :)"
dt = datetime.datetime(2024, 1, 1, tzinfo=tz)
assert dt.utcoffset() == datetime.timedelta(hours=1)
"#
);
});
}
#[cfg(Py_3_12)]
#[pyclass(extends=pyo3::types::PyList, subclass)]
struct ListWithName {
#[pyo3(get)]
name: &'static str,
}
#[cfg(Py_3_12)]
#[pymethods]
impl ListWithName {
#[new]
fn new() -> Self {
Self { name: "Hello :)" }
}
}
#[cfg(Py_3_12)]
#[pyclass(extends=ListWithName)]
struct SubListWithName {
#[pyo3(get)]
sub_name: &'static str,
}
#[cfg(Py_3_12)]
#[pymethods]
impl SubListWithName {
#[new]
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(ListWithName::new()).add_subclass(Self {
sub_name: "Sublist",
})
}
}
#[cfg(Py_3_12)]
#[test]
fn inherit_list() {
Python::attach(|py| {
let list_with_name = pyo3::Bound::new(py, ListWithName::new()).unwrap();
let sub_list_with_name = pyo3::Bound::new(py, SubListWithName::new()).unwrap();
py_run!(
py,
list_with_name sub_list_with_name,
r#"
list_with_name.append(1)
assert list_with_name[0] == 1
assert list_with_name.name == "Hello :)", list_with_name.name
sub_list_with_name.append(1)
assert sub_list_with_name[0] == 1
assert sub_list_with_name.name == "Hello :)", sub_list_with_name.name
assert sub_list_with_name.sub_name == "Sublist", sub_list_with_name.sub_name
"#
);
});
}
// Refcount tests for native type classes
#[cfg(not(any(PyPy, GraalPy, Py_GIL_DISABLED)))]
#[test]
fn test_setwitname_ref_counts() {
assert_type_refcount_stable!(SetWithName);
}
#[cfg(not(any(GraalPy, Py_GIL_DISABLED)))]
#[test]
fn test_dictwithname_ref_counts() {
assert_type_refcount_stable!(DictWithName);
}
#[cfg(not(Py_GIL_DISABLED))]
#[test]
fn test_customexception_ref_counts() {
assert_type_refcount_stable!(CustomException, "custom_exception", r#"Type('test')"#);
}
#[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))]
#[test]
fn test_tzinfowithname_ref_counts() {
assert_type_refcount_stable!(TzInfoWithName);
}
#[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))]
#[test]
fn test_listwithname_ref_counts() {
assert_type_refcount_stable!(ListWithName);
}
#[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))]
#[test]
fn test_sublistwithname_ref_counts() {
assert_type_refcount_stable!(SubListWithName);
}
}
#[pyclass(subclass)]
struct SimpleClass {}
#[pymethods]
impl SimpleClass {
#[new]
fn new() -> Self {
Self {}
}
}
// Generate refcount tests for all top-level types
#[cfg(not(Py_GIL_DISABLED))]
#[test]
fn test_baseclass_ref_counts() {
assert_type_refcount_stable!(BaseClass);
}
#[cfg(not(Py_GIL_DISABLED))]
#[test]
fn test_subclass_ref_counts() {
assert_type_refcount_stable!(SubClass);
}
#[cfg(not(Py_GIL_DISABLED))]
#[test]
fn test_base_class_with_result_ref_counts() {
assert_type_refcount_stable!(BaseClassWithResult, "base_class_with_result", "Type(10)");
}
#[cfg(not(Py_GIL_DISABLED))]
#[test]
fn test_subclass2_ref_counts() {
assert_type_refcount_stable!(SubClass2, "subclass2", "Type(10)");
}
#[cfg(not(Py_GIL_DISABLED))]
#[test]
fn test_simpleclass_ref_counts() {
assert_type_refcount_stable!(SimpleClass);
}
+46
View File
@@ -0,0 +1,46 @@
#![cfg(not(any(PyPy, GraalPy)))]
#![cfg(not(target_arch = "wasm32"))]
use pyo3::types::PyAnyMethods;
// Regression test for `try_attach` fast-path returning before site.py had
// finished initializing.
#[test]
fn test_concurrent_init_site_race() {
let tmpdir = std::env::temp_dir().join("pyo3_test_init_race");
std::fs::create_dir_all(&tmpdir).unwrap();
std::fs::write(
tmpdir.join("sitecustomize.py"),
"import sys, time\n\
sys._pyo3_site_done = False\n\
time.sleep(2)\n\
sys._pyo3_site_done = True\n",
)
.unwrap();
std::env::set_var("PYTHONPATH", &tmpdir);
std::thread::scope(|s| {
s.spawn(|| {
pyo3::Python::initialize();
});
s.spawn(|| loop {
let result = pyo3::Python::try_attach(|py| {
let done = py
.import("sys")
.unwrap()
.getattr("_pyo3_site_done")
.unwrap()
.extract::<bool>()
.unwrap();
assert!(done);
});
if result.is_some() {
break;
}
std::hint::spin_loop();
});
});
}
+288
View File
@@ -0,0 +1,288 @@
#![cfg(feature = "macros")]
use pyo3::types::{PyDict, PyList, PyString};
use pyo3::{prelude::*, py_run, IntoPyObject, IntoPyObjectExt};
use std::collections::HashMap;
use std::hash::Hash;
#[macro_use]
mod test_utils;
#[derive(Debug, IntoPyObject)]
pub struct A<'py> {
s: String,
t: Bound<'py, PyString>,
p: Bound<'py, PyAny>,
}
#[test]
fn test_named_fields_struct() {
Python::attach(|py| {
let a = A {
s: "Hello".into(),
t: PyString::new(py, "World"),
p: 42i32.into_pyobject(py).unwrap().into_any(),
};
let pya = a.into_pyobject(py).unwrap();
assert_eq!(
pya.get_item("s")
.unwrap()
.unwrap()
.cast::<PyString>()
.unwrap(),
"Hello"
);
assert_eq!(
pya.get_item("t")
.unwrap()
.unwrap()
.cast::<PyString>()
.unwrap(),
"World"
);
assert_eq!(
pya.get_item("p")
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap(),
42
);
});
}
#[derive(Debug, IntoPyObject)]
#[pyo3(transparent)]
pub struct B<'a> {
test: &'a str,
}
#[test]
fn test_transparent_named_field_struct() {
Python::attach(|py| {
let pyb = B { test: "test" }.into_pyobject(py).unwrap();
let b = pyb.extract::<String>().unwrap();
assert_eq!(b, "test");
});
}
#[derive(Debug, IntoPyObject)]
#[pyo3(transparent)]
pub struct D<T> {
test: T,
}
#[test]
fn test_generic_transparent_named_field_struct() {
Python::attach(|py| {
let pyd = D {
test: String::from("test"),
}
.into_pyobject(py)
.unwrap();
let d = pyd.extract::<String>().unwrap();
assert_eq!(d, "test");
let pyd = D { test: 1usize }.into_pyobject(py).unwrap();
let d = pyd.extract::<usize>().unwrap();
assert_eq!(d, 1);
});
}
#[derive(Debug, IntoPyObject)]
pub struct GenericWithBound<K: Hash + Eq, V>(HashMap<K, V>);
#[test]
fn test_generic_with_bound() {
Python::attach(|py| {
let mut hash_map = HashMap::<String, i32>::new();
hash_map.insert("1".into(), 1);
hash_map.insert("2".into(), 2);
let map = GenericWithBound(hash_map).into_pyobject(py).unwrap();
assert_eq!(map.len(), 2);
assert_eq!(
map.get_item("1")
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap(),
1
);
assert_eq!(
map.get_item("2")
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap(),
2
);
assert!(map.get_item("3").unwrap().is_none());
});
}
#[derive(Debug, IntoPyObject)]
pub struct Tuple(String, usize);
#[test]
fn test_tuple_struct() {
Python::attach(|py| {
let tup = Tuple(String::from("test"), 1).into_pyobject(py).unwrap();
assert!(tup.extract::<(usize, String)>().is_err());
let tup = tup.extract::<(String, usize)>().unwrap();
assert_eq!(tup.0, "test");
assert_eq!(tup.1, 1);
});
}
#[derive(Debug, IntoPyObject)]
pub struct TransparentTuple(String);
#[test]
fn test_transparent_tuple_struct() {
Python::attach(|py| {
let tup = TransparentTuple(String::from("test"))
.into_pyobject(py)
.unwrap();
assert!(tup.extract::<(String,)>().is_err());
let tup = tup.extract::<String>().unwrap();
assert_eq!(tup, "test");
});
}
fn phantom_into_py<'py, T>(
_: std::borrow::Cow<'_, std::marker::PhantomData<T>>,
py: Python<'py>,
) -> PyResult<Bound<'py, PyAny>> {
std::any::type_name::<T>().into_bound_py_any(py)
}
#[derive(Debug, IntoPyObject, IntoPyObjectRef)]
pub enum Foo<'py> {
TupleVar(
usize,
String,
#[pyo3(into_py_with = phantom_into_py::<()>)] std::marker::PhantomData<()>,
),
StructVar {
test: Bound<'py, PyString>,
},
#[pyo3(transparent)]
TransparentTuple(usize),
#[pyo3(transparent)]
TransparentStructVar {
a: Option<String>,
},
}
#[test]
fn test_enum() {
Python::attach(|py| {
let foo = Foo::TupleVar(1, "test".into(), std::marker::PhantomData)
.into_pyobject(py)
.unwrap();
assert_eq!(
foo.extract::<(usize, String, String)>().unwrap(),
(1, String::from("test"), String::from("()"))
);
let foo = Foo::StructVar {
test: PyString::new(py, "test"),
}
.into_pyobject(py)
.unwrap()
.cast_into::<PyDict>()
.unwrap();
assert_eq!(
foo.get_item("test")
.unwrap()
.unwrap()
.cast_into::<PyString>()
.unwrap(),
"test"
);
let foo = Foo::TransparentTuple(1).into_pyobject(py).unwrap();
assert_eq!(foo.extract::<usize>().unwrap(), 1);
let foo = Foo::TransparentStructVar { a: None }
.into_pyobject(py)
.unwrap();
assert!(foo.is_none());
});
}
#[derive(Debug, IntoPyObject, IntoPyObjectRef)]
pub struct Zap {
#[pyo3(item)]
name: String,
#[pyo3(into_py_with = zap_into_py, item("my_object"))]
some_object_length: usize,
}
fn zap_into_py<'py>(
len: std::borrow::Cow<'_, usize>,
py: Python<'py>,
) -> PyResult<Bound<'py, PyAny>> {
Ok(PyList::new(py, 1..*len + 1)?.into_any())
}
#[test]
fn test_into_py_with() {
Python::attach(|py| {
let zap = Zap {
name: "whatever".into(),
some_object_length: 3,
};
let py_zap_ref = (&zap).into_pyobject(py).unwrap();
let py_zap = zap.into_pyobject(py).unwrap();
py_run!(
py,
py_zap_ref,
"assert py_zap_ref == {'name': 'whatever', 'my_object': [1, 2, 3]},f'{py_zap_ref}'"
);
py_run!(
py,
py_zap,
"assert py_zap == {'name': 'whatever', 'my_object': [1, 2, 3]},f'{py_zap}'"
);
});
}
#[test]
fn test_struct_into_py_rename_all() {
#[derive(IntoPyObject, IntoPyObjectRef)]
#[pyo3(rename_all = "camelCase")]
struct Foo {
foo_bar: String,
#[pyo3(item("BAZ"))]
baz: usize,
#[pyo3(item)]
long_field_name: f32,
}
let foo = Foo {
foo_bar: "foobar".into(),
baz: 42,
long_field_name: 0.0,
};
Python::attach(|py| {
let py_foo_ref = (&foo).into_pyobject(py).unwrap();
let py_foo = foo.into_pyobject(py).unwrap();
py_run!(
py,
py_foo_ref,
"assert py_foo_ref == {'fooBar': 'foobar', 'BAZ': 42, 'longFieldName': 0},f'{py_foo_ref}'"
);
py_run!(
py,
py_foo,
"assert py_foo == {'fooBar': 'foobar', 'BAZ': 42, 'longFieldName': 0},f'{py_foo}'"
);
});
}
+39
View File
@@ -0,0 +1,39 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::types::IntoPyDict;
#[macro_use]
mod test_utils;
#[pyclass]
/// The MacroDocs class.
#[doc = concat!("Some macro ", "class ", "docs.")]
/// A very interesting type!
struct MacroDocs {}
#[pymethods]
impl MacroDocs {
#[doc = concat!("A macro ", "example.")]
/// With mixed doc types.
fn macro_doc(&self) {}
}
#[test]
fn meth_doc() {
Python::attach(|py| {
let d = [("C", py.get_type::<MacroDocs>())]
.into_py_dict(py)
.unwrap();
py_assert!(
py,
*d,
"C.__doc__ == 'The MacroDocs class.\\nSome macro class docs.\\nA very interesting type!'"
);
py_assert!(
py,
*d,
"C.macro_doc.__doc__ == 'A macro example.\\nWith mixed doc types.'"
);
});
}
+93
View File
@@ -0,0 +1,93 @@
#![cfg(feature = "macros")]
//! Ensure that pyo3 macros can be used inside macro_rules!
use pyo3::prelude::*;
#[macro_use]
mod test_utils;
macro_rules! make_struct_using_macro {
// Ensure that one doesn't need to fall back on the escape type: tt
// in order to macro create pyclass.
($class_name:ident, $py_name:literal) => {
#[pyclass(name=$py_name, subclass)]
struct $class_name {}
};
}
make_struct_using_macro!(MyBaseClass, "MyClass");
macro_rules! set_extends_via_macro {
($class_name:ident, $base_class:path) => {
// Try and pass a variable into the extends parameter
#[allow(dead_code)]
#[pyclass(extends=$base_class)]
struct $class_name {}
};
}
set_extends_via_macro!(MyClass2, MyBaseClass);
//
// Check that pyfunctions and text_signature can be called with macro arguments.
//
macro_rules! fn_macro {
($sig:literal, $a_exp:expr, $b_exp:expr, $c_exp: expr) => {
// Try and pass a variable into the signature parameter
#[pyfunction(signature = ($a_exp, $b_exp, *, $c_exp))]
#[pyo3(text_signature = $sig)]
fn my_function_in_macro(a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
}
};
}
fn_macro!("(a, b=None, *, c=42)", a, b = None, c = 42);
macro_rules! property_rename_via_macro {
($prop_name:ident) => {
#[pyclass]
struct ClassWithProperty {
member: u64,
}
#[pymethods]
impl ClassWithProperty {
#[getter($prop_name)]
fn get_member(&self) -> u64 {
self.member
}
#[setter($prop_name)]
fn set_member(&mut self, member: u64) {
self.member = member;
}
}
};
}
property_rename_via_macro!(my_new_property_name);
#[test]
fn test_macro_rules_interactions() {
Python::attach(|py| {
let my_base = py.get_type::<MyBaseClass>();
py_assert!(py, my_base, "my_base.__name__ == 'MyClass'");
let my_func = wrap_pyfunction!(my_function_in_macro, py).unwrap();
py_assert!(
py,
my_func,
"my_func.__text_signature__ == '(a, b=None, *, c=42)'"
);
let renamed_prop = py.get_type::<ClassWithProperty>();
py_assert!(
py,
renamed_prop,
"hasattr(renamed_prop, 'my_new_property_name')"
);
});
}
+137
View File
@@ -0,0 +1,137 @@
#![cfg(feature = "macros")]
use std::collections::HashMap;
use pyo3::exceptions::PyKeyError;
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::IntoPyDict;
use pyo3::types::PyList;
use pyo3::types::PyMapping;
use pyo3::types::PySequence;
mod test_utils;
#[pyclass(mapping)]
struct Mapping {
index: HashMap<String, usize>,
}
#[pymethods]
impl Mapping {
#[new]
#[pyo3(signature=(elements=None))]
fn new(elements: Option<&Bound<'_, PyList>>) -> PyResult<Self> {
if let Some(pylist) = elements {
let mut elems = HashMap::with_capacity(pylist.len());
for (i, pyelem) in pylist.into_iter().enumerate() {
let elem = pyelem.extract()?;
elems.insert(elem, i);
}
Ok(Self { index: elems })
} else {
Ok(Self {
index: HashMap::new(),
})
}
}
fn __len__(&self) -> usize {
self.index.len()
}
fn __getitem__(&self, query: String) -> PyResult<usize> {
self.index
.get(&query)
.copied()
.ok_or_else(|| PyKeyError::new_err("unknown key"))
}
fn __setitem__(&mut self, key: String, value: usize) {
self.index.insert(key, value);
}
fn __delitem__(&mut self, key: String) -> PyResult<()> {
if self.index.remove(&key).is_none() {
Err(PyKeyError::new_err("unknown key"))
} else {
Ok(())
}
}
#[pyo3(signature=(key, default=None))]
fn get(
&self,
py: Python<'_>,
key: &str,
default: Option<Py<PyAny>>,
) -> PyResult<Option<Py<PyAny>>> {
match self.index.get(key) {
Some(value) => Ok(Some(value.into_pyobject(py)?.into_any().unbind())),
None => Ok(default),
}
}
}
/// Return a dict with `m = Mapping(['1', '2', '3'])`.
fn map_dict(py: Python<'_>) -> Bound<'_, pyo3::types::PyDict> {
let d = [("Mapping", py.get_type::<Mapping>())]
.into_py_dict(py)
.unwrap();
py_run!(py, *d, "m = Mapping(['1', '2', '3'])");
d
}
#[test]
fn test_getitem() {
Python::attach(|py| {
let d = map_dict(py);
py_assert!(py, *d, "m['1'] == 0");
py_assert!(py, *d, "m['2'] == 1");
py_assert!(py, *d, "m['3'] == 2");
py_expect_exception!(py, *d, "print(m['4'])", PyKeyError);
});
}
#[test]
fn test_setitem() {
Python::attach(|py| {
let d = map_dict(py);
py_run!(py, *d, "m['1'] = 4; assert m['1'] == 4");
py_run!(py, *d, "m['0'] = 0; assert m['0'] == 0");
py_assert!(py, *d, "len(m) == 4");
py_expect_exception!(py, *d, "m[0] = 'hello'", PyTypeError);
py_expect_exception!(py, *d, "m[0] = -1", PyTypeError);
});
}
#[test]
fn test_delitem() {
Python::attach(|py| {
let d = map_dict(py);
py_run!(
py,
*d,
"del m['1']; assert len(m) == 2 and m['2'] == 1 and m['3'] == 2"
);
py_expect_exception!(py, *d, "del m[-1]", PyTypeError);
py_expect_exception!(py, *d, "del m['4']", PyKeyError);
});
}
#[test]
fn mapping_is_not_sequence() {
Python::attach(|py| {
let mut index = HashMap::new();
index.insert("Foo".into(), 1);
index.insert("Bar".into(), 2);
let m = Py::new(py, Mapping { index }).unwrap();
PyMapping::register::<Mapping>(py).unwrap();
assert!(m.bind(py).cast::<PyMapping>().is_ok());
assert!(m.bind(py).cast::<PySequence>().is_err());
});
}
+1569
View File
File diff suppressed because it is too large. Load diff
+565
View File
@@ -0,0 +1,565 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::PyString;
use pyo3::types::{IntoPyDict, PyDict, PyTuple};
mod test_utils;
#[pyclass]
struct AnonClass {}
#[pyclass]
struct ValueClass {
value: usize,
}
#[pymethods]
impl ValueClass {
#[new]
fn new(value: usize) -> ValueClass {
ValueClass { value }
}
}
#[pyclass(module = "module")]
struct LocatedClass {}
#[pyfunction]
/// Doubles the given value
fn double(x: usize) -> usize {
x * 2
}
#[test]
fn test_module_with_functions() {
use pyo3::wrap_pymodule;
/// This module is implemented in Rust.
#[pymodule]
mod module_with_functions {
use super::*;
#[pymodule_export]
use super::{AnonClass, ValueClass};
#[pymodule_init]
fn init_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<LocatedClass>()?;
m.add("foo", "bar")?;
m.add_function(wrap_pyfunction!(double, m)?)?;
m.add("also_double", wrap_pyfunction!(double, m)?)?;
Ok(())
}
#[pyfunction]
#[pyo3(name = "no_parameters")]
fn function_with_name() -> usize {
42
}
#[pyfunction]
#[pyo3(pass_module)]
fn with_module<'py>(module: &Bound<'py, PyModule>) -> PyResult<Bound<'py, PyString>> {
module.name()
}
#[pyfunction]
fn double_value(v: &ValueClass) -> usize {
v.value * 2
}
}
Python::attach(|py| {
let d = [(
"module_with_functions",
wrap_pymodule!(module_with_functions)(py),
)]
.into_py_dict(py)
.unwrap();
py_assert!(
py,
*d,
"module_with_functions.__doc__ == 'This module is implemented in Rust.'"
);
py_assert!(py, *d, "module_with_functions.no_parameters() == 42");
py_assert!(py, *d, "module_with_functions.foo == 'bar'");
py_assert!(py, *d, "module_with_functions.AnonClass != None");
py_assert!(py, *d, "module_with_functions.LocatedClass != None");
py_assert!(
py,
*d,
"module_with_functions.LocatedClass.__module__ == 'module'"
);
py_assert!(py, *d, "module_with_functions.double(3) == 6");
py_assert!(
py,
*d,
"module_with_functions.double.__doc__ == 'Doubles the given value'"
);
py_assert!(py, *d, "module_with_functions.also_double(3) == 6");
py_assert!(
py,
*d,
"module_with_functions.also_double.__doc__ == 'Doubles the given value'"
);
py_assert!(
py,
*d,
"module_with_functions.double_value(module_with_functions.ValueClass(1)) == 2"
);
py_assert!(
py,
*d,
"module_with_functions.with_module() == 'module_with_functions'"
);
});
}
#[test]
#[allow(deprecated)]
fn test_module_with_pyfn() {
use pyo3::wrap_pymodule;
/// This module is implemented in Rust.
#[pymodule]
fn module_with_pyfn(m: &Bound<'_, PyModule>) -> PyResult<()> {
#[pyfn(m)]
#[pyo3(name = "no_parameters")]
fn function_with_name() -> usize {
42
}
#[pyfn(m)]
#[pyo3(pass_module)]
fn with_module<'py>(module: &Bound<'py, PyModule>) -> PyResult<Bound<'py, PyString>> {
module.name()
}
#[pyfn(m)]
fn double_value(v: &ValueClass) -> usize {
v.value * 2
}
m.add_class::<AnonClass>()?;
m.add_class::<ValueClass>()?;
m.add_class::<LocatedClass>()?;
m.add("foo", "bar")?;
m.add_function(wrap_pyfunction!(double, m)?)?;
m.add("also_double", wrap_pyfunction!(double, m)?)?;
Ok(())
}
Python::attach(|py| {
let d = [("module_with_pyfn", wrap_pymodule!(module_with_pyfn)(py))]
.into_py_dict(py)
.unwrap();
py_assert!(
py,
*d,
"module_with_pyfn.__doc__ == 'This module is implemented in Rust.'"
);
py_assert!(py, *d, "module_with_pyfn.no_parameters() == 42");
py_assert!(py, *d, "module_with_pyfn.foo == 'bar'");
py_assert!(py, *d, "module_with_pyfn.AnonClass != None");
py_assert!(py, *d, "module_with_pyfn.LocatedClass != None");
py_assert!(
py,
*d,
"module_with_pyfn.LocatedClass.__module__ == 'module'"
);
py_assert!(py, *d, "module_with_pyfn.double(3) == 6");
py_assert!(
py,
*d,
"module_with_pyfn.double.__doc__ == 'Doubles the given value'"
);
py_assert!(py, *d, "module_with_pyfn.also_double(3) == 6");
py_assert!(
py,
*d,
"module_with_pyfn.also_double.__doc__ == 'Doubles the given value'"
);
py_assert!(
py,
*d,
"module_with_pyfn.double_value(module_with_pyfn.ValueClass(1)) == 2"
);
py_assert!(
py,
*d,
"module_with_pyfn.with_module() == 'module_with_pyfn'"
);
});
}
/// This module uses a legacy two-argument module function.
#[pymodule]
fn module_with_explicit_py_arg(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(double, m)?)?;
Ok(())
}
#[test]
fn test_module_with_explicit_py_arg() {
use pyo3::wrap_pymodule;
Python::attach(|py| {
let d = [(
"module_with_explicit_py_arg",
wrap_pymodule!(module_with_explicit_py_arg)(py),
)]
.into_py_dict(py)
.unwrap();
py_assert!(py, *d, "module_with_explicit_py_arg.double(3) == 6");
});
}
#[pymodule(name = "other_name")]
fn some_name(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add("other_name", "other_name")?;
Ok(())
}
#[test]
fn test_module_renaming() {
use pyo3::wrap_pymodule;
Python::attach(|py| {
let d = [("different_name", wrap_pymodule!(some_name)(py))]
.into_py_dict(py)
.unwrap();
py_run!(py, *d, "assert different_name.__name__ == 'other_name'");
});
}
#[test]
fn test_module_from_code_bound() {
Python::attach(|py| {
let adder_mod = PyModule::from_code(
py,
c"def add(a,b):\n\treturn a+b",
c"adder_mod.py",
&test_utils::generate_unique_module_name("adder_mod"),
)
.expect("Module code should be loaded");
let add_func = adder_mod
.getattr("add")
.expect("Add function should be in the module");
let ret_value: i32 = add_func
.call1((1, 2))
.expect("A value should be returned")
.extract()
.expect("The value should be able to be converted to an i32");
assert_eq!(ret_value, 3);
});
}
#[pyfunction]
fn r#move() -> usize {
42
}
#[pymodule]
fn raw_ident_module(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_function(wrap_pyfunction!(r#move, module)?)
}
#[test]
fn test_raw_idents() {
use pyo3::wrap_pymodule;
Python::attach(|py| {
let module = wrap_pymodule!(raw_ident_module)(py);
py_assert!(py, module, "module.move() == 42");
});
}
#[pyfunction]
#[pyo3(name = "foobar")]
fn custom_named_fn() -> usize {
42
}
#[test]
fn test_custom_names() {
#[pymodule]
fn custom_names(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(custom_named_fn, m)?)?;
Ok(())
}
Python::attach(|py| {
let module = pyo3::wrap_pymodule!(custom_names)(py);
py_assert!(py, module, "not hasattr(module, 'custom_named_fn')");
py_assert!(py, module, "module.foobar() == 42");
});
}
#[test]
fn test_module_dict() {
#[pymodule]
fn module_dict(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.dict().set_item("yay", "me")?;
Ok(())
}
Python::attach(|py| {
let module = pyo3::wrap_pymodule!(module_dict)(py);
py_assert!(py, module, "module.yay == 'me'");
});
}
#[test]
fn test_module_dunder_all() {
Python::attach(|py| {
#[pymodule]
fn dunder_all(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.dict().set_item("yay", "me")?;
m.add_function(wrap_pyfunction!(custom_named_fn, m)?)?;
Ok(())
}
let module = pyo3::wrap_pymodule!(dunder_all)(py);
py_assert!(py, module, "module.__all__ == ['foobar']");
});
}
#[pyfunction]
fn subfunction() -> String {
"Subfunction".to_string()
}
fn submodule(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_function(wrap_pyfunction!(subfunction, module)?)?;
Ok(())
}
#[pymodule]
fn submodule_with_init_fn(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_function(wrap_pyfunction!(subfunction, module)?)?;
Ok(())
}
#[pyfunction]
fn superfunction() -> String {
"Superfunction".to_string()
}
#[pymodule]
fn supermodule(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_function(wrap_pyfunction!(superfunction, module)?)?;
let module_to_add = PyModule::new(module.py(), "supermodule.submodule")?;
submodule(&module_to_add)?;
module.add_submodule(&module_to_add)?;
let module_to_add = PyModule::new(module.py(), "supermodule.submodule_with_init_fn")?;
submodule_with_init_fn(&module_to_add)?;
module.add_submodule(&module_to_add)?;
Ok(())
}
#[test]
fn test_module_nesting() {
use pyo3::wrap_pymodule;
Python::attach(|py| {
let supermodule = wrap_pymodule!(supermodule)(py);
py_assert!(
py,
supermodule,
"supermodule.superfunction() == 'Superfunction'"
);
py_assert!(
py,
supermodule,
"supermodule.submodule.subfunction() == 'Subfunction'"
);
py_assert!(
py,
supermodule,
"supermodule.submodule_with_init_fn.subfunction() == 'Subfunction'"
);
// submodule dunder name and attribute name
py_assert!(
py,
supermodule,
"supermodule.submodule.__name__ == 'supermodule.submodule'"
);
py_assert!(py, supermodule, "'submodule' in supermodule.__dict__");
py_assert!(
py,
supermodule,
"'supermodule.submodule' not in supermodule.__dict__"
);
// submodule_with_init_fn dunder name and attribute name
py_assert!(
py,
supermodule,
"supermodule.submodule_with_init_fn.__name__ == 'supermodule.submodule_with_init_fn'"
);
py_assert!(
py,
supermodule,
"'submodule_with_init_fn' in supermodule.__dict__"
);
py_assert!(
py,
supermodule,
"'supermodule.submodule_with_init_fn' not in supermodule.__dict__"
);
});
}
#[test]
fn test_module_with_constant() {
// Regression test for #1102
#[pymodule]
fn module_with_constant(m: &Bound<'_, PyModule>) -> PyResult<()> {
const ANON: AnonClass = AnonClass {};
m.add("ANON", ANON)?;
m.add_class::<AnonClass>()?;
Ok(())
}
Python::attach(|py| {
let m = pyo3::wrap_pymodule!(module_with_constant)(py);
py_assert!(py, m, "isinstance(m.ANON, m.AnonClass)");
});
}
#[pyfunction]
#[pyo3(pass_module)]
fn pyfunction_with_module<'py>(module: &Bound<'py, PyModule>) -> PyResult<Bound<'py, PyString>> {
module.name()
}
#[pyfunction]
#[pyo3(pass_module)]
fn pyfunction_with_module_owned(
module: Py<PyModule>,
py: Python<'_>,
) -> PyResult<Bound<'_, PyString>> {
module.bind(py).name()
}
#[pyfunction]
#[pyo3(pass_module)]
fn pyfunction_with_module_and_py<'py>(
module: &Bound<'py, PyModule>,
_python: Python<'py>,
) -> PyResult<Bound<'py, PyString>> {
module.name()
}
#[pyfunction]
#[pyo3(pass_module)]
fn pyfunction_with_module_and_arg<'py>(
module: &Bound<'py, PyModule>,
string: String,
) -> PyResult<(Bound<'py, PyString>, String)> {
module.name().map(|s| (s, string))
}
#[pyfunction(signature = (string="foo"))]
#[pyo3(pass_module)]
fn pyfunction_with_module_and_default_arg<'py>(
module: &Bound<'py, PyModule>,
string: &str,
) -> PyResult<(Bound<'py, PyString>, String)> {
module.name().map(|s| (s, string.into()))
}
#[pyfunction(signature = (*args, **kwargs))]
#[pyo3(pass_module)]
fn pyfunction_with_module_and_args_kwargs<'py>(
module: &Bound<'py, PyModule>,
args: &Bound<'py, PyTuple>,
kwargs: Option<&Bound<'py, PyDict>>,
) -> PyResult<(Bound<'py, PyString>, usize, Option<usize>)> {
module
.name()
.map(|s| (s, args.len(), kwargs.map(|d| d.len())))
}
#[pymodule]
fn module_with_functions_with_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(pyfunction_with_module, m)?)?;
m.add_function(wrap_pyfunction!(pyfunction_with_module_owned, m)?)?;
m.add_function(wrap_pyfunction!(pyfunction_with_module_and_py, m)?)?;
m.add_function(wrap_pyfunction!(pyfunction_with_module_and_arg, m)?)?;
m.add_function(wrap_pyfunction!(pyfunction_with_module_and_default_arg, m)?)?;
m.add_function(wrap_pyfunction!(pyfunction_with_module_and_args_kwargs, m)?)?;
m.add_function(wrap_pyfunction!(pyfunction_with_module, m)?)?;
Ok(())
}
#[test]
fn test_module_functions_with_module() {
Python::attach(|py| {
let m = pyo3::wrap_pymodule!(module_with_functions_with_module)(py);
py_assert!(
py,
m,
"m.pyfunction_with_module() == 'module_with_functions_with_module'"
);
py_assert!(
py,
m,
"m.pyfunction_with_module_owned() == 'module_with_functions_with_module'"
);
py_assert!(
py,
m,
"m.pyfunction_with_module_and_py() == 'module_with_functions_with_module'"
);
py_assert!(
py,
m,
"m.pyfunction_with_module_and_default_arg() \
== ('module_with_functions_with_module', 'foo')"
);
py_assert!(
py,
m,
"m.pyfunction_with_module_and_args_kwargs(1, x=1, y=2) \
== ('module_with_functions_with_module', 1, 2)"
);
});
}
#[test]
fn test_module_doc_hidden() {
#[doc(hidden)]
#[expect(clippy::unnecessary_wraps)]
#[pymodule]
fn my_module(_m: &Bound<'_, PyModule>) -> PyResult<()> {
Ok(())
}
Python::attach(|py| {
let m = pyo3::wrap_pymodule!(my_module)(py);
py_assert!(py, m, "m.__doc__ == ''");
})
}
+75
View File
@@ -0,0 +1,75 @@
#![cfg(feature = "multiple-pymethods")]
use pyo3::prelude::*;
use pyo3::types::PyType;
#[macro_use]
mod test_utils;
#[pyclass]
struct PyClassWithMultiplePyMethods {}
#[pymethods]
impl PyClassWithMultiplePyMethods {
#[new]
fn new() -> Self {
Self {}
}
}
#[pymethods]
impl PyClassWithMultiplePyMethods {
fn __call__(&self) -> &'static str {
"call"
}
}
#[pymethods]
impl PyClassWithMultiplePyMethods {
fn method(&self) -> &'static str {
"method"
}
}
#[pymethods]
impl PyClassWithMultiplePyMethods {
#[classmethod]
fn classmethod(_ty: &Bound<'_, PyType>) -> &'static str {
"classmethod"
}
}
#[pymethods]
impl PyClassWithMultiplePyMethods {
#[staticmethod]
fn staticmethod() -> &'static str {
"staticmethod"
}
}
#[pymethods]
impl PyClassWithMultiplePyMethods {
#[classattr]
fn class_attribute() -> &'static str {
"class_attribute"
}
}
#[pymethods]
impl PyClassWithMultiplePyMethods {
#[classattr]
const CLASS_ATTRIBUTE: &'static str = "CLASS_ATTRIBUTE";
}
#[test]
fn test_class_with_multiple_pymethods() {
Python::attach(|py| {
let cls = py.get_type::<PyClassWithMultiplePyMethods>();
py_assert!(py, cls, "cls()() == 'call'");
py_assert!(py, cls, "cls().method() == 'method'");
py_assert!(py, cls, "cls.classmethod() == 'classmethod'");
py_assert!(py, cls, "cls.staticmethod() == 'staticmethod'");
py_assert!(py, cls, "cls.class_attribute == 'class_attribute'");
py_assert!(py, cls, "cls.CLASS_ATTRIBUTE == 'CLASS_ATTRIBUTE'");
})
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,38 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
// buffer availability
#![cfg(any(not(Py_LIMITED_API), Py_3_11))]
// cannot control interpreter lifecycle in PyPy or GraalPy
#![cfg(not(any(PyPy, GraalPy)))]
//! Dropping `Py<T>` after the interpreter has been finalized should be sound.
//!
//! See e.g. https://github.com/PyO3/pyo3/issues/4632 for an extension of this problem
//! where the interpreter was finalized before `PyBuffer<T>` was dropped.
//!
//! This test runs in its own process to control the interpreter lifecycle.
use pyo3::{buffer::PyBuffer, types::PyBytes};
#[test]
fn test_pybuffer_drop_without_interpreter() {
// SAFETY: this is knowingly unsafe as we're preserving the `Py<T>` object
// after the Python interpreter has been finalized.
//
// However we should still be able to drop it without causing undefined behavior,
// so that process shutdown is sound.
let obj: PyBuffer<u8> = unsafe {
pyo3::with_embedded_python_interpreter(|py| {
PyBuffer::get(&PyBytes::new(py, b"abcdef")).unwrap()
})
};
// there should be no interpreter outside of the `with_embedded_python_interpreter` block
assert_eq!(unsafe { pyo3_ffi::Py_IsInitialized() }, 0);
// dropping object should be sound
drop(obj);
// dropping object should not re-initialize the interpreter
assert_eq!(unsafe { pyo3_ffi::Py_IsInitialized() }, 0);
}
+61
View File
@@ -0,0 +1,61 @@
use pyo3::prelude::*;
// This test mucks around with sys.modules, so run it separately to prevent it
// from potentially corrupting the state of the python interpreter used in other
// tests.
#[test]
fn err_debug_unformattable() {
// Debug representation should be like the following (without the newlines):
// PyErr {
// type: <class 'Exception'>,
// value: Exception('banana'),
// traceback: Some(\"<unformattable <traceback object at 0x...>>\")
// }
Python::attach(|py| {
// PyTracebackMethods::format uses io.StringIO. Mock it out to trigger a
// formatting failure:
// TypeError: 'Mock' object is not an instance of 'str'
let err = py
.run(
cr#"
import io, sys, unittest.mock
sys.modules['orig_io'] = sys.modules['io']
sys.modules['io'] = unittest.mock.Mock()
raise Exception('banana')"#,
None,
None,
)
.expect_err("raising should have given us an error");
let debug_str = format!("{err:?}");
assert!(debug_str.starts_with("PyErr { "));
assert!(debug_str.ends_with(" }"));
// Strip "PyErr { " and " }". Split into 3 substrings to separate type,
// value, and traceback while not splitting the string within traceback.
let mut fields = debug_str["PyErr { ".len()..debug_str.len() - 2].splitn(3, ", ");
assert_eq!(fields.next().unwrap(), "type: <class 'Exception'>");
assert_eq!(fields.next().unwrap(), "value: Exception('banana')");
let traceback = fields.next().unwrap();
assert!(
traceback.starts_with("traceback: Some(\"<unformattable <traceback object at 0x"),
"assertion failed, actual traceback str: {traceback:?}"
);
assert!(fields.next().is_none());
py.run(
cr#"
import io, sys, unittest.mock
sys.modules['io'] = sys.modules['orig_io']
del sys.modules['orig_io']
"#,
None,
None,
)
.unwrap();
});
}
+807
View File
@@ -0,0 +1,807 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(feature = "macros")]
use std::collections::HashMap;
use std::sync::atomic::{AtomicI32, Ordering};
#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
use pyo3::buffer::PyBuffer;
#[cfg(any(not(Py_LIMITED_API), Py_3_12))]
use pyo3::exceptions::PyWarning;
use pyo3::exceptions::{PyFutureWarning, PyUserWarning};
use pyo3::prelude::*;
use pyo3::types::PyDateTime;
#[cfg(not(PyPy))]
use pyo3::types::PyFunction;
use pyo3::types::{self, PyCFunction};
use pyo3_macros::pyclass;
mod test_utils;
#[pyfunction(name = "struct")]
fn struct_function() {}
#[test]
fn test_rust_keyword_name() {
Python::attach(|py| {
let f = wrap_pyfunction!(struct_function)(py).unwrap();
py_assert!(py, f, "f.__name__ == 'struct'");
});
}
#[pyfunction(signature = (arg = true))]
fn optional_bool(arg: Option<bool>) -> String {
format!("{arg:?}")
}
#[test]
fn test_optional_bool() {
// Regression test for issue #932
Python::attach(|py| {
let f = wrap_pyfunction!(optional_bool)(py).unwrap();
py_assert!(py, f, "f() == 'Some(true)'");
py_assert!(py, f, "f(True) == 'Some(true)'");
py_assert!(py, f, "f(False) == 'Some(false)'");
py_assert!(py, f, "f(None) == 'None'");
});
}
#[test]
fn test_trailing_optional_no_signature() {
// Since PyO3 0.24, trailing optional arguments are treated like any other required argument
// (previously would get an implicit default of `None`)
#[pyfunction]
fn trailing_optional(x: i32, y: Option<i32>) -> String {
format!("x={x:?} y={y:?}")
}
Python::attach(|py| {
let f = wrap_pyfunction!(trailing_optional)(py).unwrap();
py_assert!(py, f, "f(1, 2) == 'x=1 y=Some(2)'");
py_assert!(py, f, "f(2, None) == 'x=2 y=None'");
py_expect_exception!(
py,
f,
"f(3)",
PyTypeError,
"trailing_optional() missing 1 required positional argument: 'y'"
);
});
}
#[pyfunction]
#[pyo3(signature=(arg))]
fn required_optional_str(arg: Option<&str>) -> &str {
arg.unwrap_or("")
}
#[test]
fn test_optional_str() {
// Regression test for issue #4965
Python::attach(|py| {
let f = wrap_pyfunction!(required_optional_str)(py).unwrap();
py_assert!(py, f, "f('') == ''");
py_assert!(py, f, "f('foo') == 'foo'");
py_assert!(py, f, "f(None) == ''");
});
}
#[pyclass]
struct MyClass();
#[pyfunction]
#[pyo3(signature=(arg))]
fn required_optional_class(arg: Option<&MyClass>) {
let _ = arg;
}
#[test]
fn test_required_optional_class() {
// Regression test for issue #4965
Python::attach(|py| {
let f = wrap_pyfunction!(required_optional_class)(py).unwrap();
let val = Bound::new(py, MyClass()).unwrap();
py_assert!(py, f val, "f(val) is None");
py_assert!(py, f, "f(None) is None");
});
}
#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
#[pyfunction]
fn buffer_inplace_add(py: Python<'_>, x: PyBuffer<i32>, y: PyBuffer<i32>) {
let x = x.as_mut_slice(py).unwrap();
let y = y.as_slice(py).unwrap();
for (xi, yi) in x.iter().zip(y) {
let xi_plus_yi = xi.get() + yi.get();
xi.set(xi_plus_yi);
}
}
#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
#[test]
fn test_buffer_add() {
Python::attach(|py| {
let f = wrap_pyfunction!(buffer_inplace_add)(py).unwrap();
py_expect_exception!(
py,
f,
r#"
import array
a = array.array("i", [0, 1, 2, 3])
b = array.array("I", [0, 1, 2, 3])
f(a, b)
"#,
PyBufferError
);
pyo3::py_run!(
py,
f,
r#"
import array
a = array.array("i", [0, 1, 2, 3])
b = array.array("i", [2, 3, 4, 5])
f(a, b)
assert a, array.array("i", [2, 4, 6, 8])
"#
);
});
}
#[cfg(not(PyPy))]
#[pyfunction]
fn function_with_pyfunction_arg<'py>(fun: &Bound<'py, PyFunction>) -> PyResult<Bound<'py, PyAny>> {
fun.call((), None)
}
#[pyfunction]
fn function_with_pycfunction_arg<'py>(
fun: &Bound<'py, PyCFunction>,
) -> PyResult<Bound<'py, PyAny>> {
fun.call((), None)
}
#[test]
fn test_functions_with_function_args() {
Python::attach(|py| {
let py_cfunc_arg = wrap_pyfunction!(function_with_pycfunction_arg)(py).unwrap();
let bool_to_string = wrap_pyfunction!(optional_bool)(py).unwrap();
pyo3::py_run!(
py,
py_cfunc_arg
bool_to_string,
r#"
assert py_cfunc_arg(bool_to_string) == "Some(true)"
"#
);
#[cfg(not(PyPy))]
{
let py_func_arg = wrap_pyfunction!(function_with_pyfunction_arg)(py).unwrap();
pyo3::py_run!(
py,
py_func_arg,
r#"
def foo(): return "bar"
assert py_func_arg(foo) == "bar"
"#
);
}
});
}
fn datetime_to_timestamp(dt: &Bound<'_, PyAny>) -> PyResult<i64> {
let dt = dt.cast::<PyDateTime>()?;
let ts: f64 = dt.call_method0("timestamp")?.extract()?;
Ok(ts as i64)
}
#[pyfunction]
fn function_with_custom_conversion(
#[pyo3(from_py_with = datetime_to_timestamp)] timestamp: i64,
) -> i64 {
timestamp
}
#[test]
fn test_function_with_custom_conversion() {
Python::attach(|py| {
let custom_conv_func = wrap_pyfunction!(function_with_custom_conversion)(py).unwrap();
pyo3::py_run!(
py,
custom_conv_func,
r#"
import datetime
dt = datetime.datetime.fromtimestamp(1612040400)
assert custom_conv_func(dt) == 1612040400
"#
)
});
}
#[test]
fn test_function_with_custom_conversion_error() {
Python::attach(|py| {
let custom_conv_func = wrap_pyfunction!(function_with_custom_conversion)(py).unwrap();
py_expect_exception!(
py,
custom_conv_func,
"custom_conv_func(['a'])",
PyTypeError,
"'list' object is not an instance of 'datetime'",
"while processing 'timestamp'"
);
});
}
#[test]
fn test_from_py_with_defaults() {
fn optional_int(x: &Bound<'_, PyAny>) -> PyResult<Option<i32>> {
if x.is_none() {
Ok(None)
} else {
Some(x.extract()).transpose()
}
}
// issue 2280 combination of from_py_with and Option<T> did not compile
#[pyfunction]
#[pyo3(signature = (int=None))]
fn from_py_with_option(#[pyo3(from_py_with = optional_int)] int: Option<i32>) -> i32 {
int.unwrap_or(0)
}
#[pyfunction(signature = (len=0))]
fn from_py_with_default(
#[pyo3(from_py_with = <Bound<'_, _> as PyAnyMethods>::len)] len: usize,
) -> usize {
len
}
Python::attach(|py| {
let f = wrap_pyfunction!(from_py_with_option)(py).unwrap();
assert_eq!(f.call0().unwrap().extract::<i32>().unwrap(), 0);
assert_eq!(f.call1((123,)).unwrap().extract::<i32>().unwrap(), 123);
assert_eq!(f.call1((999,)).unwrap().extract::<i32>().unwrap(), 999);
let f2 = wrap_pyfunction!(from_py_with_default)(py).unwrap();
assert_eq!(f2.call0().unwrap().extract::<usize>().unwrap(), 0);
assert_eq!(f2.call1(("123",)).unwrap().extract::<usize>().unwrap(), 3);
assert_eq!(f2.call1(("1234",)).unwrap().extract::<usize>().unwrap(), 4);
});
}
#[pyclass]
#[derive(Debug, FromPyObject)]
struct ValueClass {
#[pyo3(get)]
value: usize,
}
#[pyfunction]
#[pyo3(signature=(str_arg, int_arg, tuple_arg, option_arg = None, struct_arg = None))]
fn conversion_error(
str_arg: &str,
int_arg: i64,
tuple_arg: (String, f64),
option_arg: Option<i64>,
struct_arg: Option<ValueClass>,
) {
println!("{str_arg:?} {int_arg:?} {tuple_arg:?} {option_arg:?} {struct_arg:?}");
}
#[test]
fn test_conversion_error() {
Python::attach(|py| {
let conversion_error = wrap_pyfunction!(conversion_error)(py).unwrap();
py_expect_exception!(
py,
conversion_error,
"conversion_error(None, None, None, None, None)",
PyTypeError,
"'None' is not an instance of 'str'",
"while processing 'str_arg'"
);
py_expect_exception!(
py,
conversion_error,
"conversion_error(100, None, None, None, None)",
PyTypeError,
"'int' object is not an instance of 'str'",
"while processing 'str_arg'"
);
py_expect_exception!(
py,
conversion_error,
"conversion_error('string1', 'string2', None, None, None)",
PyTypeError,
"'str' object cannot be interpreted as an integer",
"while processing 'int_arg'"
);
py_expect_exception!(
py,
conversion_error,
"conversion_error('string1', -100, 'string2', None, None)",
PyTypeError,
"'str' object is not an instance of 'tuple'",
"while processing 'tuple_arg'"
);
py_expect_exception!(
py,
conversion_error,
"conversion_error('string1', -100, ('string2', 10.), 'string3', None)",
PyTypeError,
"'str' object cannot be interpreted as an integer",
"while processing 'option_arg'"
);
let exception = py_expect_exception!(
py,
conversion_error,
"
class ValueClass:
def __init__(self, value):
self.value = value
conversion_error('string1', -100, ('string2', 10.), None, ValueClass(\"no_expected_type\"))",
PyTypeError
);
if exception.value(py).hasattr("add_note").unwrap() {
assert_eq!(
exception
.value(py)
.getattr("__notes__")
.unwrap()
.get_item(0)
.unwrap()
.extract::<std::borrow::Cow<'_, str>>()
.unwrap(),
"while processing 'struct_arg'"
);
}
assert_eq!(
extract_traceback(py, exception),
"TypeError: failed to \
extract field ValueClass.value: TypeError: 'str' object cannot be interpreted as an integer"
);
let exception = py_expect_exception!(
py,
conversion_error,
"
class ValueClass:
def __init__(self, value):
self.value = value
conversion_error('string1', -100, ('string2', 10.), None, ValueClass(-5))",
PyTypeError
);
if exception.value(py).hasattr("add_note").unwrap() {
assert_eq!(
exception
.value(py)
.getattr("__notes__")
.unwrap()
.get_item(0)
.unwrap()
.extract::<std::borrow::Cow<'_, str>>()
.unwrap(),
"while processing 'struct_arg'"
);
}
assert_eq!(
extract_traceback(py, exception),
"TypeError: failed to \
extract field ValueClass.value: OverflowError: can't convert negative int to unsigned"
);
});
}
/// Helper function that concatenates the error message from
/// each error in the traceback into a single string that can
/// be tested.
fn extract_traceback(py: Python<'_>, mut error: PyErr) -> String {
let mut error_msg = error.to_string();
while let Some(cause) = error.cause(py) {
error_msg.push_str(": ");
error_msg.push_str(&cause.to_string());
error = cause
}
error_msg
}
#[test]
fn test_pycfunction_new() {
use pyo3::ffi;
Python::attach(|py| {
unsafe extern "C" fn c_fn(
_self: *mut ffi::PyObject,
_args: *mut ffi::PyObject,
) -> *mut ffi::PyObject {
unsafe { ffi::PyLong_FromLong(4200) }
}
let py_fn = PyCFunction::new(
py,
c_fn,
c"py_fn",
c"py_fn for test (this is the docstring)",
None,
)
.unwrap();
py_assert!(py, py_fn, "py_fn() == 4200");
py_assert!(
py,
py_fn,
"py_fn.__doc__ == 'py_fn for test (this is the docstring)'"
);
});
}
#[test]
fn test_pycfunction_new_with_keywords() {
use pyo3::ffi;
use std::ffi::c_long;
use std::ptr;
Python::attach(|py| {
unsafe extern "C" fn c_fn(
_self: *mut ffi::PyObject,
args: *mut ffi::PyObject,
kwds: *mut ffi::PyObject,
) -> *mut ffi::PyObject {
let mut foo: c_long = 0;
let mut bar: c_long = 0;
#[cfg(not(Py_3_13))]
let foo_name = std::ffi::CString::new("foo").unwrap();
#[cfg(not(Py_3_13))]
let kw_bar_name = std::ffi::CString::new("kw_bar").unwrap();
#[cfg(not(Py_3_13))]
let mut args_names = [foo_name.into_raw(), kw_bar_name.into_raw(), ptr::null_mut()];
#[cfg(Py_3_13)]
let args_names = [c"foo".as_ptr(), c"kw_bar".as_ptr(), ptr::null_mut()];
unsafe {
ffi::PyArg_ParseTupleAndKeywords(
args,
kwds,
c"l|l".as_ptr(),
#[cfg(Py_3_13)]
args_names.as_ptr(),
#[cfg(not(Py_3_13))]
args_names.as_mut_ptr(),
&mut foo,
&mut bar,
)
};
#[cfg(not(Py_3_13))]
drop(unsafe { std::ffi::CString::from_raw(args_names[0]) });
#[cfg(not(Py_3_13))]
drop(unsafe { std::ffi::CString::from_raw(args_names[1]) });
unsafe { ffi::PyLong_FromLong(foo * bar) }
}
let py_fn = PyCFunction::new_with_keywords(
py,
c_fn,
c"py_fn",
c"py_fn for test (this is the docstring)",
None,
)
.unwrap();
py_assert!(py, py_fn, "py_fn(42, kw_bar=100) == 4200");
py_assert!(py, py_fn, "py_fn(foo=42, kw_bar=100) == 4200");
py_assert!(
py,
py_fn,
"py_fn.__doc__ == 'py_fn for test (this is the docstring)'"
);
});
}
#[test]
fn test_closure() {
Python::attach(|py| {
let f = |args: &Bound<'_, types::PyTuple>,
_kwargs: Option<&Bound<'_, types::PyDict>>|
-> PyResult<_> {
Python::attach(|py| {
let res: PyResult<Vec<_>> = args
.iter()
.map(|elem| {
if let Ok(i) = elem.extract::<i64>() {
Ok((i + 1).into_pyobject(py)?.into_any().unbind())
} else if let Ok(f) = elem.extract::<f64>() {
Ok((2. * f).into_pyobject(py)?.into_any().unbind())
} else if let Ok(mut s) = elem.extract::<String>() {
s.push_str("-py");
Ok(s.into_pyobject(py)?.into_any().unbind())
} else {
panic!("unexpected argument type for {elem:?}")
}
})
.collect();
res
})
};
let closure_py =
PyCFunction::new_closure(py, Some(c"test_fn"), Some(c"test_fn doc"), f).unwrap();
py_assert!(py, closure_py, "closure_py(42) == [43]");
py_assert!(py, closure_py, "closure_py.__name__ == 'test_fn'");
py_assert!(py, closure_py, "closure_py.__doc__ == 'test_fn doc'");
py_assert!(
py,
closure_py,
"closure_py(42, 3.14, 'foo') == [43, 6.28, 'foo-py']"
);
});
}
#[test]
fn test_closure_counter() {
Python::attach(|py| {
let counter = AtomicI32::new(0);
let counter_fn = move |_args: &Bound<'_, types::PyTuple>,
_kwargs: Option<&Bound<'_, types::PyDict>>|
-> PyResult<i32> {
let prev_count = counter.fetch_add(1, Ordering::SeqCst);
Ok(prev_count + 1)
};
let counter_py = PyCFunction::new_closure(py, None, None, counter_fn).unwrap();
py_assert!(py, counter_py, "counter_py() == 1");
py_assert!(py, counter_py, "counter_py() == 2");
py_assert!(py, counter_py, "counter_py() == 3");
});
}
#[test]
fn use_pyfunction() {
mod function_in_module {
use pyo3::prelude::*;
#[pyfunction]
pub fn foo(x: i32) -> i32 {
x
}
}
Python::attach(|py| {
use function_in_module::foo;
// check imported name can be wrapped
let f = wrap_pyfunction!(foo, py).unwrap();
assert_eq!(f.call1((5,)).unwrap().extract::<i32>().unwrap(), 5);
assert_eq!(f.call1((42,)).unwrap().extract::<i32>().unwrap(), 42);
// check path import can be wrapped
let f2 = wrap_pyfunction!(function_in_module::foo, py).unwrap();
assert_eq!(f2.call1((5,)).unwrap().extract::<i32>().unwrap(), 5);
assert_eq!(f2.call1((42,)).unwrap().extract::<i32>().unwrap(), 42);
})
}
#[pyclass]
struct Key(String);
#[pyclass]
struct Value(i32);
#[pyfunction]
fn return_value_borrows_from_arguments<'py>(
py: Python<'py>,
key: &'py Key,
value: &'py Value,
) -> HashMap<&'py str, i32> {
py.detach(move || {
let mut map = HashMap::new();
map.insert(key.0.as_str(), value.0);
map
})
}
#[test]
fn test_return_value_borrows_from_arguments() {
Python::attach(|py| {
let function = wrap_pyfunction!(return_value_borrows_from_arguments, py).unwrap();
let key = Py::new(py, Key("key".to_owned())).unwrap();
let value = Py::new(py, Value(42)).unwrap();
py_assert!(py, function key value, "function(key, value) == { \"key\": 42 }");
});
}
#[test]
fn test_some_wrap_arguments() {
// Option<T> arguments get special treatment in pyfunction default values where it's
// valid to pass the inner type without wrapping in `Some()`.
//
// See also https://github.com/PyO3/pyo3/issues/3460
const NONE: Option<u8> = None;
#[pyfunction(signature = (a = 1, b = Some(2), c = None, d = NONE))]
fn some_wrap_arguments(
a: Option<u8>,
b: Option<u8>,
c: Option<u8>,
d: Option<u8>,
) -> [Option<u8>; 4] {
[a, b, c, d]
}
Python::attach(|py| {
let function = wrap_pyfunction!(some_wrap_arguments, py).unwrap();
py_assert!(py, function, "function() == [1, 2, None, None]");
})
}
#[test]
fn test_reference_to_bound_arguments() {
#[pyfunction]
#[pyo3(signature = (x, y = None))]
fn reference_args<'py>(
x: &Bound<'py, PyAny>,
y: Option<&Bound<'py, PyAny>>,
) -> PyResult<Bound<'py, PyAny>> {
y.map_or_else(|| Ok(x.clone()), |y| y.add(x))
}
Python::attach(|py| {
let function = wrap_pyfunction!(reference_args, py).unwrap();
py_assert!(py, function, "function(1) == 1");
py_assert!(py, function, "function(1, 2) == 3");
})
}
#[test]
fn test_pyfunction_raw_ident() {
#[pyfunction]
fn r#struct() -> bool {
true
}
#[pyfunction]
#[pyo3(name = "r#enum")]
fn raw_ident() -> bool {
true
}
#[pymodule]
fn m(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(r#struct, m)?)?;
m.add_function(wrap_pyfunction!(raw_ident, m)?)?;
Ok(())
}
Python::attach(|py| {
let m = pyo3::wrap_pymodule!(m)(py);
py_assert!(py, m, "m.struct()");
py_assert!(py, m, "m.enum()");
})
}
#[cfg(any(not(Py_LIMITED_API), Py_3_12))]
#[pyclass(extends=PyWarning)]
pub struct UserDefinedWarning {}
#[cfg(any(not(Py_LIMITED_API), Py_3_12))]
#[pymethods]
impl UserDefinedWarning {
#[new]
#[pyo3(signature = (*_args, **_kwargs))]
fn new(_args: Bound<'_, PyAny>, _kwargs: Option<Bound<'_, PyAny>>) -> Self {
Self {}
}
}
#[test]
fn test_pyfunction_warn() {
#[pyfunction]
#[pyo3(warn(message = "TPW: this function raises warning"))]
fn function_with_warning() {}
py_expect_warning_for_fn!(
function_with_warning,
f,
[("TPW: this function raises warning", PyUserWarning)]
);
#[pyfunction]
#[pyo3(warn(message = "TPW: this function raises warning with category", category = PyFutureWarning))]
fn function_with_warning_with_category() {}
py_expect_warning_for_fn!(
function_with_warning_with_category,
f,
[(
"TPW: this function raises warning with category",
PyFutureWarning
)]
);
#[pyfunction]
#[pyo3(warn(message = "TPW: custom deprecated category", category = pyo3::exceptions::PyDeprecationWarning))]
fn function_with_warning_with_custom_category() {}
py_expect_warning_for_fn!(
function_with_warning_with_custom_category,
f,
[(
"TPW: custom deprecated category",
pyo3::exceptions::PyDeprecationWarning
)]
);
#[cfg(any(not(Py_LIMITED_API), Py_3_12))]
#[pyfunction]
#[pyo3(warn(message = "TPW: this function raises user-defined warning", category = UserDefinedWarning))]
fn function_with_warning_and_user_defined_category() {}
#[cfg(any(not(Py_LIMITED_API), Py_3_12))]
py_expect_warning_for_fn!(
function_with_warning_and_user_defined_category,
f,
[(
"TPW: this function raises user-defined warning",
UserDefinedWarning
)]
);
}
#[test]
fn test_pyfunction_multiple_warnings() {
#[pyfunction]
#[pyo3(warn(message = "TPMW: this function raises warning"))]
#[pyo3(warn(message = "TPMW: this function raises FutureWarning", category = PyFutureWarning))]
fn function_with_multiple_warnings() {}
py_expect_warning_for_fn!(
function_with_multiple_warnings,
f,
[
("TPMW: this function raises warning", PyUserWarning),
("TPMW: this function raises FutureWarning", PyFutureWarning)
]
);
#[cfg(not(Py_LIMITED_API))]
#[pyfunction]
#[pyo3(warn(message = "TPMW: this function raises FutureWarning", category = PyFutureWarning))]
#[pyo3(warn(message = "TPMW: this function raises user-defined warning", category = UserDefinedWarning))]
fn function_with_multiple_custom_warnings() {}
#[cfg(not(Py_LIMITED_API))]
py_expect_warning_for_fn!(
function_with_multiple_custom_warnings,
f,
[
("TPMW: this function raises FutureWarning", PyFutureWarning),
(
"TPMW: this function raises user-defined warning",
UserDefinedWarning
)
]
);
}
+125
View File
@@ -0,0 +1,125 @@
#![cfg(feature = "macros")]
//! Test slf: PyRef/PyMutRef<Self>(especially, slf.into::<Py>) works
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyString};
use std::collections::HashMap;
mod test_utils;
/// Assumes it's a file reader or so.
/// Inspired by https://github.com/jothan/cordoba, thanks.
#[pyclass(skip_from_py_object)]
#[derive(Clone, Debug)]
struct Reader {
inner: HashMap<u8, String>,
}
#[pymethods]
impl Reader {
fn clone_ref<'a, 'py>(slf: &'a Bound<'py, Self>) -> &'a Bound<'py, Self> {
slf
}
fn clone_ref_with_py<'a, 'py>(
slf: &'a Bound<'py, Self>,
_py: Python<'py>,
) -> &'a Bound<'py, Self> {
slf
}
fn get_iter(slf: &Bound<'_, Self>, keys: Py<PyBytes>) -> Iter {
Iter {
reader: slf.clone().unbind(),
keys,
idx: 0,
}
}
fn get_iter_and_reset(
mut slf: PyRefMut<'_, Self>,
keys: Py<PyBytes>,
py: Python<'_>,
) -> PyResult<Iter> {
let reader = Py::new(py, slf.clone())?;
slf.inner.clear();
Ok(Iter {
reader,
keys,
idx: 0,
})
}
}
#[pyclass]
#[derive(Debug)]
struct Iter {
reader: Py<Reader>,
keys: Py<PyBytes>,
idx: usize,
}
#[pymethods]
impl Iter {
#[expect(clippy::self_named_constructors)]
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __next__(mut slf: PyRefMut<'_, Self>) -> PyResult<Option<Py<PyAny>>> {
let bytes = slf.keys.bind(slf.py()).as_bytes();
match bytes.get(slf.idx) {
Some(&b) => {
slf.idx += 1;
let py = slf.py();
let reader = slf.reader.bind(py);
let reader_ref = reader.try_borrow()?;
let res = reader_ref
.inner
.get(&b)
.map(|s| PyString::new(py, s).into());
Ok(res)
}
None => Ok(None),
}
}
}
fn reader() -> Reader {
let reader = [(1, "a"), (2, "b"), (3, "c"), (4, "d"), (5, "e")];
Reader {
inner: reader.iter().map(|(k, v)| (*k, (*v).to_string())).collect(),
}
}
#[test]
fn test_nested_iter() {
Python::attach(|py| {
let reader = reader().into_pyobject(py).unwrap();
py_assert!(
py,
reader,
"list(reader.get_iter(bytes([3, 5, 2]))) == ['c', 'e', 'b']"
);
});
}
#[test]
fn test_clone_ref() {
Python::attach(|py| {
let reader = reader().into_pyobject(py).unwrap();
py_assert!(py, reader, "reader == reader.clone_ref()");
py_assert!(py, reader, "reader == reader.clone_ref_with_py()");
});
}
#[test]
fn test_nested_iter_reset() {
Python::attach(|py| {
let reader = Bound::new(py, reader()).unwrap();
py_assert!(
py,
reader,
"list(reader.get_iter_and_reset(bytes([3, 5, 2]))) == ['c', 'e', 'b']"
);
let reader_ref = reader.borrow();
assert!(reader_ref.inner.is_empty());
});
}
+468
View File
@@ -0,0 +1,468 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(feature = "macros")]
use pyo3::exceptions::{PyIndexError, PyValueError};
use pyo3::types::{IntoPyDict, PyList, PyMapping, PySequence};
use pyo3::{ffi, prelude::*};
use pyo3::py_run;
mod test_utils;
#[pyclass]
struct ByteSequence {
elements: Vec<u8>,
}
#[pymethods]
impl ByteSequence {
#[new]
#[pyo3(signature=(elements = None))]
fn new(elements: Option<&Bound<'_, PyList>>) -> PyResult<Self> {
if let Some(pylist) = elements {
let mut elems = Vec::with_capacity(pylist.len());
for pyelem in pylist {
let elem = pyelem.extract()?;
elems.push(elem);
}
Ok(Self { elements: elems })
} else {
Ok(Self {
elements: Vec::new(),
})
}
}
fn __len__(&self) -> usize {
self.elements.len()
}
fn __getitem__(&self, idx: isize) -> PyResult<u8> {
self.elements
.get(idx as usize)
.copied()
.ok_or_else(|| PyIndexError::new_err("list index out of range"))
}
fn __setitem__(&mut self, idx: isize, value: u8) {
self.elements[idx as usize] = value;
}
fn __delitem__(&mut self, mut idx: isize) -> PyResult<()> {
let self_len = self.elements.len() as isize;
if idx < 0 {
idx += self_len;
}
if (idx < self_len) && (idx >= 0) {
self.elements.remove(idx as usize);
Ok(())
} else {
Err(PyIndexError::new_err("list index out of range"))
}
}
fn __contains__(&self, other: &Bound<'_, PyAny>) -> bool {
match other.extract::<u8>() {
Ok(x) => self.elements.contains(&x),
Err(_) => false,
}
}
fn __concat__(&self, other: &Self) -> Self {
let mut elements = self.elements.clone();
elements.extend_from_slice(&other.elements);
Self { elements }
}
fn __inplace_concat__(mut slf: PyRefMut<'_, Self>, other: &Self) -> Py<Self> {
slf.elements.extend_from_slice(&other.elements);
slf.into()
}
fn __repeat__(&self, count: isize) -> PyResult<Self> {
if count >= 0 {
let mut elements = Vec::with_capacity(self.elements.len() * count as usize);
for _ in 0..count {
elements.extend(&self.elements);
}
Ok(Self { elements })
} else {
Err(PyValueError::new_err("invalid repeat count"))
}
}
fn __inplace_repeat__(mut slf: PyRefMut<'_, Self>, count: isize) -> PyResult<Py<Self>> {
if count >= 0 {
let mut elements = Vec::with_capacity(slf.elements.len() * count as usize);
for _ in 0..count {
elements.extend(&slf.elements);
}
slf.elements = elements;
Ok(slf.into())
} else {
Err(PyValueError::new_err("invalid repeat count"))
}
}
}
/// Return a dict with `s = ByteSequence([1, 2, 3])`.
fn seq_dict(py: Python<'_>) -> Bound<'_, pyo3::types::PyDict> {
let d = [("ByteSequence", py.get_type::<ByteSequence>())]
.into_py_dict(py)
.unwrap();
// Though we can construct `s` in Rust, let's test `__new__` works.
py_run!(py, *d, "s = ByteSequence([1, 2, 3])");
d
}
#[test]
fn test_getitem() {
Python::attach(|py| {
let d = seq_dict(py);
py_assert!(py, *d, "s[0] == 1");
py_assert!(py, *d, "s[1] == 2");
py_assert!(py, *d, "s[2] == 3");
py_expect_exception!(py, *d, "print(s[-4])", PyIndexError);
py_expect_exception!(py, *d, "print(s[4])", PyIndexError);
});
}
#[test]
fn test_setitem() {
Python::attach(|py| {
let d = seq_dict(py);
py_run!(py, *d, "s[0] = 4; assert list(s) == [4, 2, 3]");
py_expect_exception!(py, *d, "s[0] = 'hello'", PyTypeError);
});
}
#[test]
fn test_delitem() {
Python::attach(|py| {
let d = [("ByteSequence", py.get_type::<ByteSequence>())]
.into_py_dict(py)
.unwrap();
py_run!(
py,
*d,
"s = ByteSequence([1, 2, 3]); del s[0]; assert list(s) == [2, 3]"
);
py_run!(
py,
*d,
"s = ByteSequence([1, 2, 3]); del s[1]; assert list(s) == [1, 3]"
);
py_run!(
py,
*d,
"s = ByteSequence([1, 2, 3]); del s[-1]; assert list(s) == [1, 2]"
);
py_run!(
py,
*d,
"s = ByteSequence([1, 2, 3]); del s[-2]; assert list(s) == [1, 3]"
);
py_expect_exception!(
py,
*d,
"s = ByteSequence([1, 2, 3]); del s[-4]; print(list(s))",
PyIndexError
);
py_expect_exception!(
py,
*d,
"s = ByteSequence([1, 2, 3]); del s[4]",
PyIndexError
);
});
}
#[test]
fn test_contains() {
Python::attach(|py| {
let d = seq_dict(py);
py_assert!(py, *d, "1 in s");
py_assert!(py, *d, "2 in s");
py_assert!(py, *d, "3 in s");
py_assert!(py, *d, "4 not in s");
py_assert!(py, *d, "'hello' not in s");
});
}
#[test]
fn test_concat() {
Python::attach(|py| {
let d = seq_dict(py);
py_run!(
py,
*d,
"s1 = ByteSequence([1, 2]); s2 = ByteSequence([3, 4]); assert list(s1 + s2) == [1, 2, 3, 4]"
);
py_expect_exception!(
py,
*d,
"s1 = ByteSequence([1, 2]); s2 = 'hello'; s1 + s2",
PyTypeError
);
});
}
#[test]
fn test_inplace_concat() {
Python::attach(|py| {
let d = seq_dict(py);
py_run!(
py,
*d,
"s += ByteSequence([4, 5]); assert list(s) == [1, 2, 3, 4, 5]"
);
py_expect_exception!(py, *d, "s += 'hello'", PyTypeError);
});
}
#[test]
fn test_repeat() {
Python::attach(|py| {
let d = seq_dict(py);
py_run!(py, *d, "s2 = s * 2; assert list(s2) == [1, 2, 3, 1, 2, 3]");
py_expect_exception!(py, *d, "s2 = s * -1", PyValueError);
});
}
#[test]
fn test_inplace_repeat() {
Python::attach(|py| {
let d = [("ByteSequence", py.get_type::<ByteSequence>())]
.into_py_dict(py)
.unwrap();
py_run!(
py,
*d,
"s = ByteSequence([1, 2]); s *= 3; assert list(s) == [1, 2, 1, 2, 1, 2]"
);
py_expect_exception!(py, *d, "s = ByteSequence([1, 2]); s *= -1", PyValueError);
});
}
#[pyclass(sequence)]
struct SequenceOperators;
#[pymethods]
impl SequenceOperators {
fn __concat__(&self, _other: &Self) -> &'static str {
"concat"
}
fn __inplace_concat__(&self, _other: &Self) -> &'static str {
"inplace_concat"
}
fn __repeat__(&self, _count: isize) -> &'static str {
"repeat"
}
fn __inplace_repeat__(&self, _count: isize) -> &'static str {
"inplace_repeat"
}
}
#[test]
fn sequence_operators_use_distinct_slots() {
Python::attach(|py| {
let d = [
("left", Bound::new(py, SequenceOperators).unwrap()),
("right", Bound::new(py, SequenceOperators).unwrap()),
]
.into_py_dict(py)
.unwrap();
py_assert!(py, *d, "left + right == 'concat'");
py_run!(py, *d, "result = left; result += right");
py_assert!(py, *d, "result == 'inplace_concat'");
py_assert!(py, *d, "left * 2 == 'repeat'");
py_run!(py, *d, "result = left; result *= 2");
py_assert!(py, *d, "result == 'inplace_repeat'");
});
}
// Check that #[pyo3(get, set)] works correctly for Vec<PyObject>
#[pyclass]
struct AnyObjectList {
#[pyo3(get, set)]
items: Vec<Py<PyAny>>,
}
#[test]
fn test_any_object_list_get() {
Python::attach(|py| {
let list = AnyObjectList {
items: [1i32, 2, 3]
.iter()
.map(|i| i.into_pyobject(py).unwrap().into_any().unbind())
.collect(),
}
.into_pyobject(py)
.unwrap();
py_assert!(py, list, "list.items == [1, 2, 3]");
});
}
#[test]
fn test_any_object_list_set() {
Python::attach(|py| {
let list = Bound::new(py, AnyObjectList { items: vec![] }).unwrap();
py_run!(py, list, "list.items = [1, 2, 3]");
assert!(list
.borrow()
.items
.iter()
.zip(&[1u32, 2, 3])
.all(|(a, b)| a.bind(py).eq(b.into_pyobject(py).unwrap()).unwrap()));
});
}
#[pyclass(sequence)]
struct OptionList {
#[pyo3(get, set)]
items: Vec<Option<i64>>,
}
#[pymethods]
impl OptionList {
fn __len__(&self) -> usize {
self.items.len()
}
fn __getitem__(&self, idx: isize) -> PyResult<Option<i64>> {
match self.items.get(idx as usize) {
Some(x) => Ok(*x),
None => Err(PyIndexError::new_err("Index out of bounds")),
}
}
}
#[test]
fn test_option_list_get() {
// Regression test for #798
Python::attach(|py| {
let list = Py::new(
py,
OptionList {
items: vec![Some(1), None],
},
)
.unwrap();
py_assert!(py, list, "list[0] == 1");
py_assert!(py, list, "list[1] == None");
py_expect_exception!(py, list, "list[2]", PyIndexError);
});
}
#[test]
fn sequence_is_not_mapping() {
Python::attach(|py| {
let list = Bound::new(
py,
OptionList {
items: vec![Some(1), None],
},
)
.unwrap()
.into_any();
PySequence::register::<OptionList>(py).unwrap();
assert!(list.cast::<PyMapping>().is_err());
assert!(list.cast::<PySequence>().is_ok());
})
}
#[test]
fn sequence_length() {
Python::attach(|py| {
let list = Bound::new(
py,
OptionList {
items: vec![Some(1), None],
},
)
.unwrap()
.into_any();
assert_eq!(list.len().unwrap(), 2);
assert_eq!(unsafe { ffi::PySequence_Length(list.as_ptr()) }, 2);
assert_eq!(unsafe { ffi::PyMapping_Length(list.as_ptr()) }, -1);
unsafe { ffi::PyErr_Clear() };
})
}
#[cfg(Py_3_10)]
#[pyclass(generic, sequence)]
struct GenericList {
#[pyo3(get, set)]
items: Vec<Py<PyAny>>,
}
#[cfg(Py_3_10)]
#[pymethods]
impl GenericList {
fn __len__(&self) -> usize {
self.items.len()
}
fn __getitem__(&self, idx: isize) -> PyResult<Py<PyAny>> {
match self.items.get(idx as usize) {
Some(x) => pyo3::Python::attach(|py| Ok(x.clone_ref(py))),
None => Err(PyIndexError::new_err("Index out of bounds")),
}
}
}
#[cfg(Py_3_10)]
#[test]
fn test_generic_both_subscriptions_types() {
use pyo3::types::PyInt;
use std::convert::Infallible;
Python::attach(|py| {
let l = Bound::new(
py,
GenericList {
items: [1, 2, 3]
.iter()
.map(|x| -> Py<PyAny> {
let x: Result<Bound<'_, PyInt>, Infallible> = x.into_pyobject(py);
x.unwrap().into_any().unbind()
})
.chain([py.None()])
.collect(),
},
)
.unwrap();
let ty = py.get_type::<GenericList>();
py_assert!(py, l, "l[0] == 1");
py_run!(
py,
ty,
"import types;
import typing;
IntOrNone: typing.TypeAlias = typing.Union[int, None];
assert ty[IntOrNone] == types.GenericAlias(ty, (IntOrNone,))"
);
py_assert!(py, l, "list(reversed(l)) == [None, 3, 2, 1]");
});
}
+79
View File
@@ -0,0 +1,79 @@
#![cfg(feature = "serde")]
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
#[pyclass]
#[derive(Debug, Serialize, Deserialize)]
struct Group {
name: String,
}
#[pyclass]
#[derive(Debug, Serialize, Deserialize)]
struct User {
username: String,
group: Option<Py<Group>>,
friends: Vec<Py<User>>,
}
#[test]
fn test_serialize() {
let friend1 = User {
username: "friend 1".into(),
group: None,
friends: vec![],
};
let friend2 = User {
username: "friend 2".into(),
group: None,
friends: vec![],
};
let user = Python::attach(|py| {
let py_friend1 = Py::new(py, friend1).expect("failed to create friend 1");
let py_friend2 = Py::new(py, friend2).expect("failed to create friend 2");
let friends = vec![py_friend1, py_friend2];
let py_group = Py::new(
py,
Group {
name: "group name".into(),
},
)
.unwrap();
User {
username: "danya".into(),
group: Some(py_group),
friends,
}
});
let serialized = serde_json::to_string(&user).expect("failed to serialize");
assert_eq!(
serialized,
r#"{"username":"danya","group":{"name":"group name"},"friends":[{"username":"friend 1","group":null,"friends":[]},{"username":"friend 2","group":null,"friends":[]}]}"#
);
}
#[test]
fn test_deserialize() {
let serialized = r#"{"username": "danya", "friends":
[{"username": "friend", "group": {"name": "danya's friends"}, "friends": []}]}"#;
let user: User = serde_json::from_str(serialized).expect("failed to deserialize");
assert_eq!(user.username, "danya");
assert!(user.group.is_none());
assert_eq!(user.friends.len(), 1usize);
let friend = user.friends.first().unwrap();
Python::attach(|py| {
assert_eq!(friend.borrow(py).username, "friend");
assert_eq!(
friend.borrow(py).group.as_ref().unwrap().borrow(py).name,
"danya's friends"
)
});
}
+73
View File
@@ -0,0 +1,73 @@
#![cfg(feature = "macros")]
use pyo3::exceptions::PyIndexError;
use pyo3::prelude::*;
use pyo3::types::IntoPyDict;
use pyo3::py_run;
mod test_utils;
#[pyclass]
struct Count5();
#[pymethods]
impl Count5 {
#[new]
fn new() -> Self {
Self()
}
#[staticmethod]
fn __len__() -> usize {
5
}
#[staticmethod]
fn __getitem__(idx: isize) -> PyResult<f64> {
if idx < 0 {
Err(PyIndexError::new_err("Count5 cannot count backwards"))
} else if idx > 4 {
Err(PyIndexError::new_err("Count5 cannot count higher than 5"))
} else {
Ok(idx as f64 + 1.0)
}
}
}
/// Return a dict with `s = Count5()`.
fn test_dict(py: Python<'_>) -> Bound<'_, pyo3::types::PyDict> {
let d = [("Count5", py.get_type::<Count5>())]
.into_py_dict(py)
.unwrap();
// Though we can construct `s` in Rust, let's test `__new__` works.
py_run!(py, *d, "s = Count5()");
d
}
#[test]
fn test_len() {
Python::attach(|py| {
let d = test_dict(py);
py_assert!(py, *d, "len(s) == 5");
});
}
#[test]
fn test_getitem() {
Python::attach(|py| {
let d = test_dict(py);
py_assert!(py, *d, "s[4] == 5.0");
});
}
#[test]
fn test_list() {
Python::attach(|py| {
let d = test_dict(py);
py_assert!(py, *d, "list(s) == [1.0, 2.0, 3.0, 4.0, 5.0]");
});
}
+22
View File
@@ -0,0 +1,22 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
mod test_utils;
#[pyfunction]
fn take_str(_s: &str) {}
#[test]
fn test_unicode_encode_error() {
Python::attach(|py| {
let take_str = wrap_pyfunction!(take_str)(py).unwrap();
py_expect_exception!(
py,
take_str,
"take_str('\\ud800')",
PyUnicodeEncodeError,
"'utf-8' codec can't encode character '\\ud800' in position 0: surrogates not allowed"
);
});
}
+58
View File
@@ -0,0 +1,58 @@
#![cfg(feature = "macros")]
#![cfg(not(any(PyPy, GraalPy)))]
use pyo3::{prelude::*, types::PySuper};
#[pyclass(subclass)]
struct BaseClass {
val1: usize,
}
#[pymethods]
impl BaseClass {
#[new]
fn new() -> Self {
BaseClass { val1: 10 }
}
pub fn method(&self) -> usize {
self.val1
}
}
#[pyclass(extends=BaseClass)]
struct SubClass {}
#[pymethods]
impl SubClass {
#[new]
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(BaseClass::new()).add_subclass(Self {})
}
fn method<'py>(self_: &Bound<'py, Self>) -> PyResult<Bound<'py, PyAny>> {
let super_ = self_.py_super()?;
super_.call_method("method", (), None)
}
fn method_super_new<'py>(self_: &Bound<'py, Self>) -> PyResult<Bound<'py, PyAny>> {
let super_ = PySuper::new(&self_.get_type(), self_)?;
super_.call_method("method", (), None)
}
}
#[test]
fn test_call_super_method() {
Python::attach(|py| {
let cls = py.get_type::<SubClass>();
pyo3::py_run!(
py,
cls,
r#"
obj = cls()
assert obj.method() == 10
assert obj.method_super_new() == 10
"#
)
});
}
+435
View File
@@ -0,0 +1,435 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};
use pyo3::{types::PyType, wrap_pymodule};
mod test_utils;
#[test]
fn class_without_docs_or_signature() {
#[pyclass]
struct MyClass {}
Python::attach(|py| {
let typeobj = py.get_type::<MyClass>();
py_assert!(py, typeobj, "typeobj.__doc__ is None");
py_assert!(py, typeobj, "typeobj.__text_signature__ is None");
});
}
#[test]
fn class_with_docs() {
/// docs line1
#[pyclass]
/// docs line2
struct MyClass {}
Python::attach(|py| {
let typeobj = py.get_type::<MyClass>();
py_assert!(py, typeobj, "typeobj.__doc__ == 'docs line1\\ndocs line2'");
py_assert!(py, typeobj, "typeobj.__text_signature__ is None");
});
}
#[test]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
fn class_with_signature_no_doc() {
#[pyclass]
struct MyClass {}
#[pymethods]
impl MyClass {
#[new]
#[pyo3(signature = (a, b=None, *, c=42), text_signature = "(a, b=None, *, c=42)")]
fn __new__(a: i32, b: Option<i32>, c: i32) -> Self {
let _ = (a, b, c);
Self {}
}
}
Python::attach(|py| {
let typeobj = py.get_type::<MyClass>();
py_assert!(py, typeobj, "typeobj.__doc__ == ''");
py_assert!(
py,
typeobj,
"typeobj.__text_signature__ == '(a, b=None, *, c=42)'"
);
});
}
#[test]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
fn class_with_docs_and_signature() {
/// docs line1
#[pyclass]
/// docs line2
struct MyClass {}
#[pymethods]
impl MyClass {
#[new]
#[pyo3(signature = (a, b=None, *, c=42), text_signature = "(a, b=None, *, c=42)")]
fn __new__(a: i32, b: Option<i32>, c: i32) -> Self {
let _ = (a, b, c);
Self {}
}
}
Python::attach(|py| {
let typeobj = py.get_type::<MyClass>();
py_assert!(py, typeobj, "typeobj.__doc__ == 'docs line1\\ndocs line2'");
py_assert!(
py,
typeobj,
"typeobj.__text_signature__ == '(a, b=None, *, c=42)'"
);
});
}
#[test]
fn test_function() {
#[pyfunction(signature = (a, b=None, *, c=42))]
#[pyo3(text_signature = "(a, b=None, *, c=42)")]
fn my_function(a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
}
Python::attach(|py| {
let f = wrap_pyfunction!(my_function)(py).unwrap();
py_assert!(py, f, "f.__text_signature__ == '(a, b=None, *, c=42)'");
});
}
#[test]
fn test_auto_test_signature_function() {
#[pyfunction]
fn my_function(a: i32, b: i32, c: i32) {
let _ = (a, b, c);
}
#[pyfunction(pass_module)]
fn my_function_2(module: &Bound<'_, PyModule>, a: i32, b: i32, c: i32) {
let _ = (module, a, b, c);
}
#[pyfunction(signature = (a, /, b = None, *, c = 5))]
fn my_function_3(a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
}
#[pyfunction(signature = (a, /, b = None, *args, c, d=5, **kwargs))]
fn my_function_4(
a: i32,
b: Option<i32>,
args: &Bound<'_, PyTuple>,
c: i32,
d: i32,
kwargs: Option<&Bound<'_, PyDict>>,
) {
let _ = (a, b, args, c, d, kwargs);
}
#[pyfunction(signature = (a = 1, /, b = None, c = 1.5, d=5, e = "pyo3", f = 'f', h = true))]
fn my_function_5(a: i32, b: Option<i32>, c: f32, d: i32, e: &str, f: char, h: bool) {
let _ = (a, b, c, d, e, f, h);
}
#[pyfunction]
#[pyo3(signature=(a, b=None, c=None))]
fn my_function_6(a: i32, b: Option<i32>, c: Option<i32>) {
let _ = (a, b, c);
}
#[pyfunction]
fn trailing_optional_required(a: i32, b: Option<i32>, c: Option<i32>) {
// Since PyO3 0.24, trailing optional arguments are treated like any other required argument
// (previously would get an implicit default of `None`)
let _ = (a, b, c);
}
macro_rules! assert_text_signature {
($py:expr, $func:ident, $expected:expr) => {
assert_eq!(
wrap_pyfunction!($func, $py)
.unwrap()
.getattr("__text_signature__")
.unwrap()
.cast_into::<pyo3::types::PyString>()
.unwrap(),
$expected
);
};
}
Python::attach(|py| {
assert_text_signature!(py, my_function, "(a, b, c)");
assert_text_signature!(py, my_function_2, "($module, a, b, c)");
assert_text_signature!(py, my_function_3, "(a, /, b=None, *, c=5)");
assert_text_signature!(py, my_function_4, "(a, /, b=None, *args, c, d=5, **kwargs)");
assert_text_signature!(
py,
my_function_5,
"(a=1, /, b=None, c=1.5, d=5, e=\"pyo3\", f='f', h=True)"
);
assert_text_signature!(py, my_function_6, "(a, b=None, c=None)");
assert_text_signature!(py, trailing_optional_required, "(a, b, c)");
});
}
#[test]
fn test_auto_test_signature_method() {
#[pyclass]
struct MyClass {}
#[pymethods]
impl MyClass {
#[new]
fn new(a: i32, b: i32, c: i32) -> Self {
let _ = (a, b, c);
Self {}
}
fn method(&self, a: i32, b: i32, c: i32) {
let _ = (a, b, c);
}
#[pyo3(signature = (a, /, b = None, *, c = 5))]
fn method_2(&self, a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
}
#[pyo3(signature = (a, /, b = None, *args, c, d=5, **kwargs))]
fn method_3(
&self,
a: i32,
b: Option<i32>,
args: &Bound<'_, PyTuple>,
c: i32,
d: i32,
kwargs: Option<&Bound<'_, PyDict>>,
) {
let _ = (a, b, args, c, d, kwargs);
}
#[staticmethod]
fn staticmethod(a: i32, b: i32, c: i32) {
let _ = (a, b, c);
}
#[classmethod]
fn classmethod(cls: &Bound<'_, PyType>, a: i32, b: i32, c: i32) {
let _ = (cls, a, b, c);
}
}
Python::attach(|py| {
let cls = py.get_type::<MyClass>();
#[cfg(any(not(Py_LIMITED_API), Py_3_10))]
py_assert!(py, cls, "cls.__text_signature__ == '(a, b, c)'");
py_assert!(
py,
cls,
"cls.method.__text_signature__ == '($self, a, b, c)'"
);
py_assert!(
py,
cls,
"cls.method_2.__text_signature__ == '($self, a, /, b=None, *, c=5)'"
);
py_assert!(
py,
cls,
"cls.method_3.__text_signature__ == '($self, a, /, b=None, *args, c, d=5, **kwargs)'"
);
py_assert!(
py,
cls,
"cls.staticmethod.__text_signature__ == '(a, b, c)'"
);
py_assert!(
py,
cls,
"cls.classmethod.__text_signature__ == '($cls, a, b, c)'"
);
});
}
#[test]
fn test_auto_test_signature_opt_out() {
#[pyfunction(text_signature = None)]
fn my_function(a: i32, b: i32, c: i32) {
let _ = (a, b, c);
}
#[pyfunction(signature = (a, /, b = None, *, c = 5), text_signature = None)]
fn my_function_2(a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
}
#[pyclass]
struct MyClass {}
#[pymethods]
impl MyClass {
#[new]
#[pyo3(text_signature = None)]
fn new(a: i32, b: i32, c: i32) -> Self {
let _ = (a, b, c);
Self {}
}
#[pyo3(text_signature = None)]
fn method(&self, a: i32, b: i32, c: i32) {
let _ = (a, b, c);
}
#[pyo3(signature = (a, /, b = None, *, c = 5), text_signature = None)]
fn method_2(&self, a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
}
#[staticmethod]
#[pyo3(text_signature = None)]
fn staticmethod(a: i32, b: i32, c: i32) {
let _ = (a, b, c);
}
#[classmethod]
#[pyo3(text_signature = None)]
fn classmethod(cls: &Bound<'_, PyType>, a: i32, b: i32, c: i32) {
let _ = (cls, a, b, c);
}
}
Python::attach(|py| {
let f = wrap_pyfunction!(my_function)(py).unwrap();
py_assert!(py, f, "f.__text_signature__ == None");
let f = wrap_pyfunction!(my_function_2)(py).unwrap();
py_assert!(py, f, "f.__text_signature__ == None");
let cls = py.get_type::<MyClass>();
py_assert!(py, cls, "cls.__text_signature__ == None");
py_assert!(py, cls, "cls.method.__text_signature__ == None");
py_assert!(py, cls, "cls.method_2.__text_signature__ == None");
py_assert!(py, cls, "cls.staticmethod.__text_signature__ == None");
py_assert!(py, cls, "cls.classmethod.__text_signature__ == None");
});
}
#[test]
#[allow(deprecated)]
fn test_pyfn() {
#[pymodule]
fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
#[pyfn(m, signature = (a, b=None, *, c=42))]
#[pyo3(text_signature = "(a, b=None, *, c=42)")]
fn my_function(a: i32, b: Option<i32>, c: i32) {
let _ = (a, b, c);
}
Ok(())
}
Python::attach(|py| {
let m = wrap_pymodule!(my_module)(py);
py_assert!(
py,
m,
"m.my_function.__text_signature__ == '(a, b=None, *, c=42)'"
);
});
}
#[test]
fn test_methods() {
#[pyclass]
struct MyClass {}
#[pymethods]
impl MyClass {
#[pyo3(text_signature = "($self, a)")]
fn method(&self, a: i32) {
let _ = a;
}
#[pyo3(text_signature = "($self, b)")]
fn pyself_method(_this: &Bound<'_, Self>, b: i32) {
let _ = b;
}
#[classmethod]
#[pyo3(text_signature = "($cls, c)")]
fn class_method(_cls: &Bound<'_, PyType>, c: i32) {
let _ = c;
}
#[staticmethod]
#[pyo3(text_signature = "(d)")]
fn static_method(d: i32) {
let _ = d;
}
}
Python::attach(|py| {
let typeobj = py.get_type::<MyClass>();
py_assert!(
py,
typeobj,
"typeobj.method.__text_signature__ == '($self, a)'"
);
py_assert!(
py,
typeobj,
"typeobj.pyself_method.__text_signature__ == '($self, b)'"
);
py_assert!(
py,
typeobj,
"typeobj.class_method.__text_signature__ == '($cls, c)'"
);
py_assert!(
py,
typeobj,
"typeobj.static_method.__text_signature__ == '(d)'"
);
});
}
#[test]
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
fn test_raw_identifiers() {
#[pyclass]
struct r#MyClass {}
#[pymethods]
impl MyClass {
#[new]
fn new() -> MyClass {
MyClass {}
}
fn r#method(&self) {}
}
Python::attach(|py| {
let typeobj = py.get_type::<MyClass>();
py_assert!(py, typeobj, "typeobj.__text_signature__ == '()'");
py_assert!(
py,
typeobj,
"typeobj.method.__text_signature__ == '($self)'"
);
});
}
+291
View File
@@ -0,0 +1,291 @@
// Common macros and helpers for tests
//
// This file is used in two different ways, which makes it a bit of a pain to build:
// - as a module `include!`-ed from `src/test_utils.rs`
// - as a module `mod test_utils` in various integration tests
//
// the inner mod enables the #![allow(dead_code)] to
// be applied - `src/test_utils.rs` uses `include!` to pull in this file
#[allow(
dead_code,
unused_macros,
reason = "many tests do not use the complete set of functionality offered here"
)]
#[allow(missing_docs, reason = "only used in tests")]
#[macro_use]
mod inner {
#[allow(
unused_imports,
reason = "pulls in `use crate as pyo3` from src/test_utils.rs, no function in integration tests"
)]
use super::*;
use pyo3::prelude::*;
#[cfg(any(not(all(Py_GIL_DISABLED, Py_3_14)), feature = "macros"))]
use pyo3::sync::MutexExt;
use pyo3::types::{IntoPyDict, PyList};
#[cfg(any(not(all(Py_GIL_DISABLED, Py_3_14)), feature = "macros"))]
use std::sync::{Mutex, PoisonError};
use uuid::Uuid;
#[macro_export]
macro_rules! py_assert {
($py:expr, $($val:ident)+, $assertion:literal) => {
pyo3::py_run!($py, $($val)+, concat!("assert ", $assertion))
};
($py:expr, *$dict:expr, $assertion:literal) => {
pyo3::py_run!($py, *$dict, concat!("assert ", $assertion))
};
}
#[macro_export]
macro_rules! assert_py_eq {
($val:expr, $expected:expr) => {
assert!($val.eq($expected).unwrap());
};
}
#[macro_export]
macro_rules! py_expect_exception {
// Case1: idents & no err_msg
($py:expr, $($val:ident)+, $code:expr, $err:ident) => {{
use pyo3::types::IntoPyDict;
use pyo3::BoundObject;
let d = [$((stringify!($val), (&$val).into_pyobject($py).unwrap().into_any().into_bound()),)+].into_py_dict($py).unwrap();
py_expect_exception!($py, *d, $code, $err)
}};
// Case2: dict & no err_msg
($py:expr, *$dict:expr, $code:expr, $err:ident) => {{
let res = $py.run(&std::ffi::CString::new($code).unwrap(), None, Some(&$dict.as_borrowed()));
let err = res.expect_err(&format!("Did not raise {}", stringify!($err)));
if !err.matches($py, $py.get_type::<pyo3::exceptions::$err>()).unwrap() {
panic!("Expected {} but got {:?}", stringify!($err), err)
}
err
}};
// Case3: idents & err_msg
($py:expr, $($val:ident)+, $code:expr, $err:ident, $err_msg:literal $(,$notes:literal)*) => {{
let err = py_expect_exception!($py, $($val)+, $code, $err);
// Suppose that the error message looks like 'TypeError: ~'
assert_eq!(format!("Py{}", err), concat!(stringify!($err), ": ", $err_msg));
if err.value($py).hasattr("add_note").unwrap() {
let notes = err.value($py).getattr("__notes__").map(|n| n.cast_into::<pyo3::types::PyList>().unwrap()).unwrap_or_else(|_| pyo3::types::PyList::empty($py));
let mut _notes_iter = notes.iter();
$(
assert_eq!(_notes_iter.next().as_ref().map(|v| v.extract::<std::borrow::Cow<'_, str>>().unwrap()).as_deref(), Some($notes));
)*
}
err
}};
// Case4: dict & err_msg
($py:expr, *$dict:expr, $code:expr, $err:ident, $err_msg:literal) => {{
let err = py_expect_exception!($py, *$dict, $code, $err);
assert_eq!(format!("Py{}", err), concat!(stringify!($err), ": ", $err_msg));
err
}};
}
#[macro_export]
macro_rules! py_expect_warning {
($py:expr, $($val:ident)+, $code:expr, [$(($warning_msg:literal, $warning_category:path)),+] $(,)?) => {{
use pyo3::types::IntoPyDict;
let d = [$((stringify!($val), ($val.as_ref() as &Bound<'_, PyAny>).into_pyobject($py).expect("Failed to create test dict element")),)+].into_py_dict($py).expect("Failed to create test dict");
py_expect_warning!($py, *d, $code, [$(($warning_msg, $warning_category)),+])
}};
($py:expr, *$dict:expr, $code:expr, [$(($warning_msg:literal, $warning_category:path)),+] $(,)?) => {{
$crate::test_utils::CatchWarnings::enter($py, |warning_record| {
$py.run(&std::ffi::CString::new($code).unwrap(), None, Some(&$dict.as_borrowed())).expect("Failed to run warning testing code");
let expected_warnings = [$(($warning_msg, <$warning_category as pyo3::PyTypeInfo>::type_object($py))),+];
assert_eq!(warning_record.len(), expected_warnings.len(), "Expecting {} warnings but got {}", expected_warnings.len(), warning_record.len());
for ((index, warning), (msg, category)) in warning_record.iter().enumerate().zip(expected_warnings.iter()) {
let actual_msg = warning.getattr("message").unwrap().str().unwrap().to_string_lossy().to_string();
let actual_category = warning.getattr("category").unwrap();
assert_eq!(actual_msg, msg.to_string(), "Warning message mismatch at index {}, expecting `{}` but got `{}`", index, msg, actual_msg);
assert!(actual_category.is(category), "Warning category mismatch at index {}, expecting {:?} but got {:?}", index, category, actual_category);
}
Ok(())
}).expect("failed to test warnings");
}};
}
#[macro_export]
macro_rules! py_expect_warning_for_fn {
($fn:ident, $($val:ident)+, [$(($warning_msg:literal, $warning_category:path)),+] $(,)?) => {
pyo3::Python::attach(|py| {
let f = wrap_pyfunction!($fn)(py).unwrap();
py_expect_warning!(
py,
f,
"f()",
[$(($warning_msg, $warning_category)),+]
);
});
};
}
#[cfg(feature = "macros")]
pub struct UnraisableCapture<'py> {
hook: Bound<'py, UnraisableCaptureHook>,
}
#[cfg(feature = "macros")]
impl<'py> UnraisableCapture<'py> {
/// Runs the closure `f` with a custom sys.unraisablehook installed.
///
/// `f`
pub fn enter<R>(py: Python<'py>, f: impl FnOnce(&Self) -> R) -> R {
// unraisablehook is a global, so only one thread can be using this struct at a time.
static UNRAISABLE_HOOK_MUTEX: Mutex<()> = Mutex::new(());
// NB this is best-effort, other tests could always modify sys.unraisablehook directly.
let mutex_guard = UNRAISABLE_HOOK_MUTEX
.lock_py_attached(py)
.unwrap_or_else(PoisonError::into_inner);
let guard = Self {
hook: UnraisableCaptureHook::install(py),
};
let result = f(&guard);
drop(guard);
drop(mutex_guard);
result
}
/// Takes the captured unraisable error, if any.
pub fn take_capture(&self) -> Option<(PyErr, Bound<'py, PyAny>)> {
let mut guard = self.hook.get().capture.lock().unwrap();
guard.take().map(|(e, o)| (e, o.into_bound(self.hook.py())))
}
}
#[cfg(feature = "macros")]
impl Drop for UnraisableCapture<'_> {
fn drop(&mut self) {
let py = self.hook.py();
self.hook.get().uninstall(py);
}
}
#[cfg(feature = "macros")]
#[pyclass(crate = "pyo3", frozen)]
struct UnraisableCaptureHook {
pub capture: Mutex<Option<(PyErr, Py<PyAny>)>>,
old_hook: Py<PyAny>,
}
#[cfg(feature = "macros")]
#[pymethods(crate = "pyo3")]
impl UnraisableCaptureHook {
pub fn hook(&self, unraisable: Bound<'_, PyAny>) {
let err = PyErr::from_value(unraisable.getattr("exc_value").unwrap());
let instance = unraisable.getattr("object").unwrap();
self.capture.lock().unwrap().replace((err, instance.into()));
}
}
#[cfg(feature = "macros")]
impl UnraisableCaptureHook {
fn install(py: Python<'_>) -> Bound<'_, Self> {
let sys = py.import("sys").unwrap();
let old_hook = sys.getattr("unraisablehook").unwrap().into();
let capture = Mutex::new(None);
let capture = Bound::new(py, UnraisableCaptureHook { capture, old_hook }).unwrap();
sys.setattr("unraisablehook", capture.getattr("hook").unwrap())
.unwrap();
capture
}
fn uninstall(&self, py: Python<'_>) {
let sys = py.import("sys").unwrap();
sys.setattr("unraisablehook", &self.old_hook).unwrap();
}
}
pub struct CatchWarnings<'py> {
catch_warnings: Bound<'py, PyAny>,
}
/// catch_warnings is not thread-safe, so only one thread can be using this struct at
/// a time.
#[cfg(not(all(Py_GIL_DISABLED, Py_3_14)))] // Python 3.14t has thread-safe catch_warnings
static CATCH_WARNINGS_MUTEX: Mutex<()> = Mutex::new(());
impl<'py> CatchWarnings<'py> {
pub fn enter<R>(
py: Python<'py>,
f: impl FnOnce(&Bound<'py, PyList>) -> PyResult<R>,
) -> PyResult<R> {
// NB this is best-effort, other tests could always call the warnings API directly.
#[cfg(not(all(Py_GIL_DISABLED, Py_3_14)))]
let _mutex_guard = CATCH_WARNINGS_MUTEX
.lock_py_attached(py)
.unwrap_or_else(PoisonError::into_inner);
let warnings = py.import("warnings")?;
let kwargs = [("record", true)].into_py_dict(py)?;
let catch_warnings = warnings
.getattr("catch_warnings")?
.call((), Some(&kwargs))?;
let list = catch_warnings.call_method0("__enter__")?.cast_into()?;
let _guard = Self { catch_warnings };
f(&list)
}
}
impl Drop for CatchWarnings<'_> {
fn drop(&mut self) {
let py = self.catch_warnings.py();
self.catch_warnings
.call_method1("__exit__", (py.None(), py.None(), py.None()))
.unwrap();
}
}
macro_rules! assert_warnings {
($py:expr, $body:expr, [$(($category:ty, $message:literal)),+] $(,)? ) => {{
$crate::test_utils::CatchWarnings::enter($py, |w| {
use $crate::types::{PyListMethods, PyStringMethods};
$body;
let expected_warnings = [$((<$category as $crate::type_object::PyTypeInfo>::type_object($py), $message)),+];
assert_eq!(w.len(), expected_warnings.len());
for (warning, (category, message)) in w.iter().zip(expected_warnings) {
assert!(warning.getattr("category").unwrap().is(&category));
assert_eq!(
warning.getattr("message").unwrap().str().unwrap().to_string_lossy(),
message
);
}
Ok(())
})
.unwrap();
}};
}
#[allow(unused_imports, reason = "not all tests use this macro")]
pub(crate) use assert_warnings;
pub fn generate_unique_module_name(base: &str) -> std::ffi::CString {
let uuid = Uuid::new_v4().simple().to_string();
std::ffi::CString::new(format!("{base}_{uuid}")).unwrap()
}
}
#[allow(unused_imports, reason = "some integration tests use just the macros")]
pub use inner::*;
+48
View File
@@ -0,0 +1,48 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};
mod test_utils;
#[pyclass]
struct MyClass {}
#[pymethods]
impl MyClass {
#[staticmethod]
#[pyo3(signature = (*args))]
fn test_args(args: Bound<'_, PyTuple>) -> Bound<'_, PyTuple> {
args
}
#[staticmethod]
#[pyo3(signature = (**kwargs))]
fn test_kwargs(kwargs: Option<Bound<'_, PyDict>>) -> Option<Bound<'_, PyDict>> {
kwargs
}
}
#[test]
fn variable_args() {
Python::attach(|py| {
let my_obj = py.get_type::<MyClass>();
py_assert!(py, my_obj, "my_obj.test_args() == ()");
py_assert!(py, my_obj, "my_obj.test_args(1) == (1,)");
py_assert!(py, my_obj, "my_obj.test_args(1, 2) == (1, 2)");
});
}
#[test]
fn variable_kwargs() {
Python::attach(|py| {
let my_obj = py.get_type::<MyClass>();
py_assert!(py, my_obj, "my_obj.test_kwargs() == None");
py_assert!(py, my_obj, "my_obj.test_kwargs(test=1) == {'test': 1}");
py_assert!(
py,
my_obj,
"my_obj.test_kwargs(test1=1, test2=2) == {'test1':1, 'test2':2}"
);
});
}
+208
View File
@@ -0,0 +1,208 @@
#![cfg(feature = "macros")]
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::PyTuple;
use std::fmt;
mod test_utils;
#[pyclass]
struct MutRefArg {
n: i32,
}
#[pymethods]
impl MutRefArg {
fn get(&self) -> i32 {
self.n
}
fn set_other(&self, mut other: PyRefMut<'_, MutRefArg>) {
other.n = 100;
}
}
#[test]
fn mut_ref_arg() {
Python::attach(|py| {
let inst1 = Py::new(py, MutRefArg { n: 0 }).unwrap();
let inst2 = Py::new(py, MutRefArg { n: 0 }).unwrap();
py_run!(py, inst1 inst2, "inst1.set_other(inst2)");
let inst2 = inst2.bind(py).borrow();
assert_eq!(inst2.n, 100);
});
}
#[pyclass]
struct PyUsize {
#[pyo3(get)]
pub value: usize,
}
#[pyfunction]
fn get_zero() -> PyUsize {
PyUsize { value: 0 }
}
#[test]
/// Checks that we can use return a custom class in arbitrary function and use those functions
/// both in rust and python
fn return_custom_class() {
Python::attach(|py| {
// Using from rust
assert_eq!(get_zero().value, 0);
// Using from python
let get_zero = wrap_pyfunction!(get_zero)(py).unwrap();
py_assert!(py, get_zero, "get_zero().value == 0");
});
}
#[test]
fn intopytuple_primitive() {
Python::attach(|py| {
let tup = (1, 2, "foo");
py_assert!(py, tup, "tup == (1, 2, 'foo')");
py_assert!(py, tup, "tup[0] == 1");
py_assert!(py, tup, "tup[1] == 2");
py_assert!(py, tup, "tup[2] == 'foo'");
});
}
#[pyclass]
struct SimplePyClass {}
#[test]
fn intopytuple_pyclass() {
Python::attach(|py| {
let tup = (
Py::new(py, SimplePyClass {}).unwrap(),
Py::new(py, SimplePyClass {}).unwrap(),
);
py_assert!(py, tup, "type(tup[0]).__name__ == 'SimplePyClass'");
py_assert!(py, tup, "type(tup[0]).__name__ == type(tup[1]).__name__");
py_assert!(py, tup, "tup[0] != tup[1]");
});
}
#[test]
fn pytuple_primitive_iter() {
Python::attach(|py| {
let tup = PyTuple::new(py, [1u32, 2, 3].iter()).unwrap();
py_assert!(py, tup, "tup == (1, 2, 3)");
});
}
#[test]
fn pytuple_pyclass_iter() {
Python::attach(|py| {
let tup = PyTuple::new(
py,
[
Py::new(py, SimplePyClass {}).unwrap(),
Py::new(py, SimplePyClass {}).unwrap(),
]
.iter(),
)
.unwrap();
py_assert!(py, tup, "type(tup[0]).__name__ == 'SimplePyClass'");
py_assert!(py, tup, "type(tup[0]).__name__ == type(tup[0]).__name__");
py_assert!(py, tup, "tup[0] != tup[1]");
});
}
#[test]
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
fn test_pickle() {
use pyo3::types::PyDict;
#[pyclass(dict, module = "test_module")]
struct PickleSupport {}
#[pymethods]
impl PickleSupport {
#[new]
fn new() -> PickleSupport {
PickleSupport {}
}
pub fn __reduce__<'py>(
slf: &Bound<'py, Self>,
py: Python<'py>,
) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyTuple>, Bound<'py, PyAny>)> {
let cls = slf.getattr("__class__")?;
let dict = slf.getattr("__dict__")?;
Ok((cls, PyTuple::empty(py), dict))
}
}
fn add_module(module: Bound<'_, PyModule>) -> PyResult<()> {
PyModule::import(module.py(), "sys")?
.dict()
.get_item("modules")
.unwrap()
.unwrap()
.cast::<PyDict>()?
.set_item(module.name()?, module)
}
Python::attach(|py| {
let module = PyModule::new(py, "test_module").unwrap();
module.add_class::<PickleSupport>().unwrap();
add_module(module).unwrap();
let inst = Py::new(py, PickleSupport {}).unwrap();
py_run!(
py,
inst,
r#"
inst.a = 1
assert inst.__dict__ == {'a': 1}
import pickle
inst2 = pickle.loads(pickle.dumps(inst))
assert inst2.__dict__ == {'a': 1}
"#
);
});
}
/// Testing https://github.com/PyO3/pyo3/issues/1106. A result type that
/// implements `From<MyError> for PyErr` should be automatically converted
/// when using `#[pyfunction]`.
///
/// This only makes sure that valid `Result` types do work. For an invalid
/// enum type, see `ui/invalid_result_conversion.py`.
#[derive(Debug)]
struct MyError {
pub descr: &'static str,
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "My error message: {}", self.descr)
}
}
/// Important for the automatic conversion to `PyErr`.
impl From<MyError> for PyErr {
fn from(err: MyError) -> pyo3::PyErr {
pyo3::exceptions::PyOSError::new_err(err.to_string())
}
}
#[pyfunction]
fn result_conversion_function() -> Result<(), MyError> {
Err(MyError {
descr: "something went wrong",
})
}
#[test]
fn test_result_conversion() {
Python::attach(|py| {
wrap_pyfunction!(result_conversion_function)(py).unwrap();
});
}