1//! `PyClass` and related traits.
2use crate::{
3 callback::IntoPyCallbackOutput, ffi, impl_::pyclass::PyClassImpl, IntoPy, PyCell, PyObject,
4 PyResult, PyTypeInfo, Python,
5};
6use std::{cmp::Ordering, os::raw::c_int};
7
8mod create_type_object;
9mod gc;
10
11pub(crate) use self::create_type_object::{create_type_object, PyClassTypeObject};
12pub use self::gc::{PyTraverseError, PyVisit};
13
14/// Types that can be used as Python classes.
15///
16/// The `#[pyclass]` attribute implements this trait for your Rust struct -
17/// you shouldn't implement this trait directly.
18pub trait PyClass: PyTypeInfo<AsRefTarget = PyCell<Self>> + PyClassImpl {
19 /// Whether the pyclass is frozen.
20 ///
21 /// This can be enabled via `#[pyclass(frozen)]`.
22 type Frozen: Frozen;
23}
24
25/// Operators for the `__richcmp__` method
26#[derive(Debug, Clone, Copy)]
27pub enum CompareOp {
28 /// The *less than* operator.
29 Lt = ffi::Py_LT as isize,
30 /// The *less than or equal to* operator.
31 Le = ffi::Py_LE as isize,
32 /// The equality operator.
33 Eq = ffi::Py_EQ as isize,
34 /// The *not equal to* operator.
35 Ne = ffi::Py_NE as isize,
36 /// The *greater than* operator.
37 Gt = ffi::Py_GT as isize,
38 /// The *greater than or equal to* operator.
39 Ge = ffi::Py_GE as isize,
40}
41
42impl CompareOp {
43 /// Conversion from the C enum.
44 pub fn from_raw(op: c_int) -> Option<Self> {
45 match op {
46 ffi::Py_LT => Some(CompareOp::Lt),
47 ffi::Py_LE => Some(CompareOp::Le),
48 ffi::Py_EQ => Some(CompareOp::Eq),
49 ffi::Py_NE => Some(CompareOp::Ne),
50 ffi::Py_GT => Some(CompareOp::Gt),
51 ffi::Py_GE => Some(CompareOp::Ge),
52 _ => None,
53 }
54 }
55
56 /// Returns if a Rust [`std::cmp::Ordering`] matches this ordering query.
57 ///
58 /// Usage example:
59 ///
60 /// ```rust
61 /// # use pyo3::prelude::*;
62 /// # use pyo3::class::basic::CompareOp;
63 ///
64 /// #[pyclass]
65 /// struct Size {
66 /// size: usize,
67 /// }
68 ///
69 /// #[pymethods]
70 /// impl Size {
71 /// fn __richcmp__(&self, other: &Size, op: CompareOp) -> bool {
72 /// op.matches(self.size.cmp(&other.size))
73 /// }
74 /// }
75 /// ```
76 pub fn matches(&self, result: Ordering) -> bool {
77 match self {
78 CompareOp::Eq => result == Ordering::Equal,
79 CompareOp::Ne => result != Ordering::Equal,
80 CompareOp::Lt => result == Ordering::Less,
81 CompareOp::Le => result != Ordering::Greater,
82 CompareOp::Gt => result == Ordering::Greater,
83 CompareOp::Ge => result != Ordering::Less,
84 }
85 }
86}
87
88/// Output of `__next__` which can either `yield` the next value in the iteration, or
89/// `return` a value to raise `StopIteration` in Python.
90///
91/// Usage example:
92///
93/// ```rust
94/// use pyo3::prelude::*;
95/// use pyo3::iter::IterNextOutput;
96///
97/// #[pyclass]
98/// struct PyClassIter {
99/// count: usize,
100/// }
101///
102/// #[pymethods]
103/// impl PyClassIter {
104/// #[new]
105/// pub fn new() -> Self {
106/// PyClassIter { count: 0 }
107/// }
108///
109/// fn __next__(&mut self) -> IterNextOutput<usize, &'static str> {
110/// if self.count < 5 {
111/// self.count += 1;
112/// // Given an instance `counter`, First five `next(counter)` calls yield 1, 2, 3, 4, 5.
113/// IterNextOutput::Yield(self.count)
114/// } else {
115/// // At the sixth time, we get a `StopIteration` with `'Ended'`.
116/// // try:
117/// // next(counter)
118/// // except StopIteration as e:
119/// // assert e.value == 'Ended'
120/// IterNextOutput::Return("Ended")
121/// }
122/// }
123/// }
124/// ```
125pub enum IterNextOutput<T, U> {
126 /// The value yielded by the iterator.
127 Yield(T),
128 /// The `StopIteration` object.
129 Return(U),
130}
131
132/// Alias of `IterNextOutput` with `PyObject` yield & return values.
133pub type PyIterNextOutput = IterNextOutput<PyObject, PyObject>;
134
135impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterNextOutput {
136 fn convert(self, _py: Python<'_>) -> PyResult<*mut ffi::PyObject> {
137 match self {
138 IterNextOutput::Yield(o: Py) => Ok(o.into_ptr()),
139 IterNextOutput::Return(opt: Py) => Err(crate::exceptions::PyStopIteration::new_err((opt,))),
140 }
141 }
142}
143
144impl<T, U> IntoPyCallbackOutput<PyIterNextOutput> for IterNextOutput<T, U>
145where
146 T: IntoPy<PyObject>,
147 U: IntoPy<PyObject>,
148{
149 fn convert(self, py: Python<'_>) -> PyResult<PyIterNextOutput> {
150 match self {
151 IterNextOutput::Yield(o: T) => Ok(IterNextOutput::Yield(o.into_py(py))),
152 IterNextOutput::Return(o: U) => Ok(IterNextOutput::Return(o.into_py(py))),
153 }
154 }
155}
156
157impl<T> IntoPyCallbackOutput<PyIterNextOutput> for Option<T>
158where
159 T: IntoPy<PyObject>,
160{
161 fn convert(self, py: Python<'_>) -> PyResult<PyIterNextOutput> {
162 match self {
163 Some(o: T) => Ok(PyIterNextOutput::Yield(o.into_py(py))),
164 None => Ok(PyIterNextOutput::Return(py.None())),
165 }
166 }
167}
168
169/// Output of `__anext__`.
170///
171/// <https://docs.python.org/3/reference/expressions.html#agen.__anext__>
172pub enum IterANextOutput<T, U> {
173 /// An expression which the generator yielded.
174 Yield(T),
175 /// A `StopAsyncIteration` object.
176 Return(U),
177}
178
179/// An [IterANextOutput] of Python objects.
180pub type PyIterANextOutput = IterANextOutput<PyObject, PyObject>;
181
182impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterANextOutput {
183 fn convert(self, _py: Python<'_>) -> PyResult<*mut ffi::PyObject> {
184 match self {
185 IterANextOutput::Yield(o: Py) => Ok(o.into_ptr()),
186 IterANextOutput::Return(opt: Py) => {
187 Err(crate::exceptions::PyStopAsyncIteration::new_err((opt,)))
188 }
189 }
190 }
191}
192
193impl<T, U> IntoPyCallbackOutput<PyIterANextOutput> for IterANextOutput<T, U>
194where
195 T: IntoPy<PyObject>,
196 U: IntoPy<PyObject>,
197{
198 fn convert(self, py: Python<'_>) -> PyResult<PyIterANextOutput> {
199 match self {
200 IterANextOutput::Yield(o: T) => Ok(IterANextOutput::Yield(o.into_py(py))),
201 IterANextOutput::Return(o: U) => Ok(IterANextOutput::Return(o.into_py(py))),
202 }
203 }
204}
205
206impl<T> IntoPyCallbackOutput<PyIterANextOutput> for Option<T>
207where
208 T: IntoPy<PyObject>,
209{
210 fn convert(self, py: Python<'_>) -> PyResult<PyIterANextOutput> {
211 match self {
212 Some(o: T) => Ok(PyIterANextOutput::Yield(o.into_py(py))),
213 None => Ok(PyIterANextOutput::Return(py.None())),
214 }
215 }
216}
217
218/// A workaround for [associated const equality](https://github.com/rust-lang/rust/issues/92827).
219///
220/// This serves to have True / False values in the [`PyClass`] trait's `Frozen` type.
221#[doc(hidden)]
222pub mod boolean_struct {
223 pub(crate) mod private {
224 use super::*;
225
226 /// A way to "seal" the boolean traits.
227 pub trait Boolean {}
228
229 impl Boolean for True {}
230 impl Boolean for False {}
231 }
232
233 pub struct True(());
234 pub struct False(());
235}
236
237/// A trait which is used to describe whether a `#[pyclass]` is frozen.
238#[doc(hidden)]
239pub trait Frozen: boolean_struct::private::Boolean {}
240
241impl Frozen for boolean_struct::True {}
242impl Frozen for boolean_struct::False {}
243
244mod tests {
245 #[test]
246 fn test_compare_op_matches() {
247 use super::CompareOp;
248 use std::cmp::Ordering;
249
250 assert!(CompareOp::Eq.matches(Ordering::Equal));
251 assert!(CompareOp::Ne.matches(Ordering::Less));
252 assert!(CompareOp::Ge.matches(Ordering::Greater));
253 assert!(CompareOp::Gt.matches(Ordering::Greater));
254 assert!(CompareOp::Le.matches(Ordering::Equal));
255 assert!(CompareOp::Lt.matches(Ordering::Less));
256 }
257}
258