pyderive/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! This library provides derive macros of Python spacial methods and a class attributes for [PyO3].
4//!
5//! The field attribute `#[pyderive(..)]` helps to customize implementations,
6//! like [`dataclasses.field()`][dataclasses-field] of Python.
7//!
8//! It requires to enable `multiple-pymethods` feature of PyO3
9//! because the derive macros that this library provides may implement multiple `#[pymethods]`.
10//!
11//! [dataclasses-field]: https://docs.python.org/3/library/dataclasses.html#dataclasses.field
12//! [PyO3]: https://github.com/PyO3/pyo3
13//!
14//! # Example
15//!
16//! ```
17//! // Enable `multiple-pymethods` feature of PyO3
18//! use pyo3::prelude::*;
19//! use pyderive::*;
20//!
21//! // Place #[derive(PyNew, ...)] before #[pyclass]
22//! #[derive(PyNew, PyMatchArgs, PyRepr, PyEq)]
23//! #[pyclass(get_all)]
24//! #[derive(PartialEq, Hash)]
25//! struct MyClass {
26//! string: String,
27//! integer: i64,
28//! option: Option<i64>
29//! }
30//! ```
31//! ```python
32//! # Python script
33//! from rust_module import MyClass
34//!
35//!
36//! # Derives __new__()
37//! m = MyClass("a", 1, None)
38//!
39//! # Derives __match_args__ (supports Pattern Matching by positional arguments)
40//! match m:
41//! case MyClass(a, b, c):
42//! assert a == "a"
43//! assert b == 1
44//! assert c is None
45//! case _:
46//! raise AssertionError
47//!
48//! # Derives __repr__(), calls Python repr() recursively
49//! assert str(m) == "MyClass(string='a', integer=1, option=None)"
50//! assert repr(m) == "MyClass(string='a', integer=1, option=None)"
51//!
52//! # Derives __eq__() that depends on PartialEq trait
53//! assert m == MyClass("a", 1, None)
54//! ```
55//!
56//! # Detail
57//!
58//! Some macros change implementations depend on `#[pyclass(..)]` and `#[pyo3(..)]` arguments,
59//! hence it should place `#[derive(PyNew)]` etc. before `#[pyclass(..)]` and `#[pyo3(..)]`.
60//!
61//! We list the default implementations that the macros generate.
62//!
63//! | Derive Macro | Derives |
64//! | --------------------- | ---------------------------------------------------- |
65//! | [`PyNew`] | `__new__()` with all fields |
66//! | [`PyMatchArgs`] | `__match_args__` class attr. with `get` fields |
67//! | [`PyRepr`] | `__repr__()` returns `get` and `set` fields |
68//! | [`PyStr`] | `__str__()` returns `get` and `set` fields |
69//! | [`PyIter`] | `__iter__()` returns an iterator of `get` fields |
70//! | [`PyReversed`] | `__reversed__()` returns an iterator of `get` fields |
71//! | [`PyLen`] | `__len__()` returns number of `get` fields |
72//! | [`PyDataclassFields`] | `__dataclass_fields__` class attr. with all fields |
73//!
74//! Notes, methods implemented by [`PyRepr`] and [`PyStr`] are recursively calls `repr()` or `str()` like a Python `dataclass`.
75//!
76//! We call the field is *`get` (or `set`) field*
77//! if the field has a `#[pyclass/pyo3(get)]` (or `#[pyclass/pyo3(set)]`) attribute or
78//! its struct has a `#[pyclass/pyo3(get_all)]` (or `#[pyclass/pyo3(set_all)]`) attribute.
79//!
80//! The following derive macros depend on traits.
81//!
82//! | Derive Macro | Derives |
83//! | --------------- | -------------------------------------------------------------------------------------------------- |
84//! | [`PyEq`] | `__eq__()` and `__ne__()`, depends on [`PartialEq`] |
85//! | [`PyOrd`] | `__lt__()`, `__le__()`, `__gt__()` and `__ge__()`, depend on [`PartialOrd`] |
86//! | [`PyRichCmp`] | `==`, `!=`, `>`, `>=`, `<` and `<=` by `__richcmp__()`, depend on [`PartialEq`] and [`PartialOrd`] |
87//! | [`PyNumeric`] | Numeric op traits (`__add__()` etc.) |
88//! | [`PyBitwise`] | Bitwise op traits (`__and__()` etc.) |
89//!
90//! Notes, implementation of [`PyEq`] and [`PyOrd`] does not use `__richcmp__()`.
91//!
92//! Module [`pyderive::ops`](mod@ops) and [`pyderive::convert`](mod@convert) provides
93//! derive macros that implement individual method that enumerating numeric type (`__add__()` etc.) and
94//! called by builtin functions (`__int__()` etc.).
95//!
96//! [pyo3_IntoPy]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPy.html
97//! [pyo3_pyclass]: https://docs.rs/pyo3/latest/pyo3/attr.pyclass.html
98//!
99//! # Customize Implementation
100//!
101//! The field attributes `#[pyderive(..)]` is used to customize implementations
102//! produced by [pyderive](crate)'s derive.
103//!
104//! ```
105//! # use pyo3::prelude::*;
106//! use pyderive::*;
107//!
108//! #[derive(PyNew, PyRepr)]
109//! #[pyclass]
110//! struct MyClass {
111//! string: String,
112//! #[pyderive(repr=false)]
113//! #[pyo3(get)]
114//! integer: i64,
115//! #[pyderive(default=10)]
116//! option: Option<i64>
117//! }
118//! ```
119//!
120//! It allows to omit the right-hand side,
121//! and it evaluates to the right-hand as `true`
122//! except `default` , for example,
123//! `#[pyderive(repr)]` is equivalent to `#[pyderive(repr=true)]`.
124//!
125//! - `#[pyderive(repr=<bool>)]`
126//!
127//! If `repr=true`,
128//! the field is included in the string that the `__repr__()` method returns;
129//! if `repr=false`, it isn't.
130//!
131//! The derive macro [`PyDataclassFields`] reads this attribute also,
132//! see [`PyDataclassFields`] for detail.
133//!
134//! - `#[pyderive(str=<bool>)]`
135//!
136//! If `str=true`,
137//! the field is included in the string that the `__str__()` method returns;
138//! if `str=false`, it isn't.
139//!
140//! - `#[pyderive(new=<bool>)]`
141//!
142//! If `new=false`,
143//! the field is excluded from the arguments of the `__new__()` method.
144//! Notes, `new=true` has no effect.
145//!
146//! The derive macro [`PyDataclassFields`] reads this attribute also,
147//! see [`PyDataclassFields`] for detail.
148//!
149//! - `#[pyderive(default=<expr>)]`
150//!
151//! This is used to customize default value for the `__new__()` method.
152//! It supports any rust expression which PyO3 supports, e.g.,
153//!
154//! ```
155//! # use pyderive::*;
156//! # use pyo3::prelude::*;
157//! #
158//! #[derive(PyNew)]
159//! #[pyclass]
160//! struct PyClass {
161//! #[pyderive(default = Some("str".to_string()))]
162//! field: Option<String>,
163//! }
164//! ```
165//!
166//! We note that this internally produces `#[pyo3(signature = ..)]` attribute.
167//!
168//! 1. No `#[pyderive(..)]` (for example, just `field: i64`)
169//!
170//! Pseudocode:
171//!
172//! ```python
173//! def __new__(cls, field):
174//! self = super().__new__(cls)
175//! self.field = field
176//! return self
177//! ```
178//!
179//! 2. `#[pyderive(new=false)]`
180//!
181//! The field is excluded from the arguments,
182//! and initialized by [`Default::default()`] in the `__new__()` method.
183//! We note that it is evaluated on every `__new__()` call.
184//!
185//! Pseudocode:
186//!
187//! ```python
188//! def __new__(cls):
189//! self = super().__new__(cls)
190//! self.field = field::default() # call rust fn
191//! return self
192//! ```
193//!
194//! 3. `#[pyderive(default=<expr>)]`
195//!
196//! The field is included to the arguments with default value `<expr>`.
197//! We note that `<expr>` (rust code) is evaluated on every `__new__()` call (PyO3 feature).
198//!
199//! Pseudocode:
200//!
201//! ```python
202//! def __new__(cls, field=<expr>):
203//! self = super().__new__(cls)
204//! self.field = field
205//! return self
206//! ```
207//!
208//! 4. `#[pyderive(new=false, default=<expr>)]`
209//!
210//! The field is excluded from the arguments,
211//! and initialized with `<expr>` in the `__new__()` method.
212//! We note that `<expr>` (rust code) is evaluated on every `__new__()` call.
213//!
214//! Pseudocode:
215//!
216//! ```python
217//! def __new__(cls):
218//! self = super().__new__(cls)
219//! self.field = <expr>
220//! return self
221//! ```
222//!
223//! - `#[pyderive(default_factory=true)]`
224//!
225//! If `default_factory=true`,
226//! let the `default_factory` attribute of `Field`obj be `lambda: <expr>`,
227//! and let the `default` attribute be [`dataclasses.MISSING`][MISSING],
228//! where `<expr>` is given by `#[pyderive(default=<expr>)]`.
229//! Notes, `default_factory=false` has no effect,
230//! If the field is not marked by `#[pyderive(default=<expr>)]`, this ignores.
231//!
232//! See [`PyDataclassFields`] for detail.
233//!
234//! - `#[pyderive(kw_only=true)]`
235//!
236//! If `kw_only=true`,
237//! the following fields are keyword only arguments in the `__new__()` method,
238//! like [`*`][keyword-only-arguments] and [`dataclasses.KW_ONLY`][KW_ONLY].
239//! Note, `kw_only=false` has no effect.
240//!
241//! The derive macro [`PyDataclassFields`] reads this attribute also,
242//! see [`PyDataclassFields`] for detail.
243//!
244//! - `#[pyderive(match_args=<bool>)]`
245//!
246//! If `match_args=true`,
247//! the field is included in the `__match_args__` class attribute;
248//! if `match_args=false`, it isn't.
249//!
250//! We note that, as far as I know,
251//! the field must be accessible on the pattern matching.
252//! For example,
253//! pattern matching does *not* work with *not `get` field without a getter*
254//! (even if `match_args=true`), but it does work if the field has a getter.
255//!
256//! - `#[pyderive(iter=<bool>)]`
257//!
258//! If `iter=true`,
259//! the field is included in the iterator that `__iter__()` and `__reversed__()` return;
260//! if `iter=false`, it isn't.
261//!
262//! - `#[pyderive(len=<bool>)]`
263//!
264//! If `len=true`,
265//! the field is counted by the `__len__()`;
266//! if `len=false`, it isn't.
267//!
268//! - `#[pyderive(dataclass_field=false)]`
269//!
270//! If `dataclass_field=false`,
271//! the field is excluded from the `__dataclass_fields__` dict.
272//! Notes, `dataclass_field=true` has no effect.
273//!
274//! See [`PyDataclassFields`] for detail.
275//!
276//! - `#[pyderive(annotation=<str>)]`
277//!
278//! The derive macro [`PyDataclassFields`] reads this attribute,
279//! see [`PyDataclassFields`] for detail.
280//!
281//! [keyword-only-arguments]: https://docs.python.org/3/tutorial/controlflow.html#keyword-only-arguments
282//! [KW_ONLY]: https://docs.python.org/3/library/dataclasses.html#dataclasses.KW_ONLY
283//! [MISSING]: https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING
284
285pub mod convert;
286pub mod ops;
287
288/// Derive macro generating a `__dataclass_fields__` fn/Python class attribute.
289///
290/// It returns a [`dataclasses.Field`][Field] dict that helper functions of the [dataclasses] module read.
291/// It supports [`is_dataclass()`][is_dataclass], [`fields()`][fields],
292/// [`asdict()`][asdict] (include nest), [`astuple()`][astuple] (include nest)
293/// and [`replace()`][replace] of the dataclasses module.
294///
295/// The resulting dict contains all fields as default.
296///
297/// If the filed is marked by `#[pyderive(dataclass_field=false)]` attribute,
298/// the field is excluded from the dict that `__dataclass_fields__` returns.
299/// Notes, `dataclass_field=true` has no effect.
300///
301/// - It should place `#[derive(PyDataclassField)]` before `#[pyclass]`.
302/// - All fields in the arguments of the `__new__()` method should be `get` field, like `dataclass` does.
303/// - It requires [`IntoPyObject`][pyo3_IntoPyObject] trait for fields.
304///
305/// This does not generate other fn/method,
306/// use [`PyNew`] etc. to implement `__new__()` etc.
307///
308/// [pyo3_IntoPyObject]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPyObject.html
309/// [pyo3_pyclass]: https://docs.rs/pyo3/latest/pyo3/attr.pyclass.html
310///
311/// # Example
312///
313/// ```
314/// use pyo3::{prelude::*, py_run};
315/// use pyderive::*;
316///
317/// // Place before `#[pyclass]`
318/// #[derive(PyNew, PyDataclassFields)]
319/// #[pyclass(get_all)]
320/// struct PyClass {
321/// string: String,
322/// integer: i64,
323/// float: f64,
324/// tuple: (String, i64, f64),
325/// option: Option<String>,
326/// #[pyderive(dataclass_field=false)]
327/// excluded: String,
328/// }
329///
330/// Python::attach(|py| -> PyResult<()> {
331/// let a = Py::new(py, PyClass {
332/// string: "s".to_string(),
333/// integer: 1,
334/// float: 1.0,
335/// tuple: ("s".to_string(), 1, 1.0),
336/// option: None,
337/// excluded: "s".to_string(),
338/// })?;
339///
340/// let test = "
341/// from dataclasses import is_dataclass, asdict, astuple
342///
343/// assert is_dataclass(a) is True
344/// assert asdict(a) == {'string': 's', 'integer': 1, 'float': 1.0, 'tuple': ('s', 1, 1.0), 'option': None}
345/// assert astuple(a) == ('s', 1, 1.0, ('s', 1, 1.0), None)
346/// ";
347/// py_run!(py, a, test);
348///
349/// Ok(())
350/// });
351/// ```
352///
353/// # Implementation Notes
354///
355/// | `dataclasses.Field` Attribute | Compatibility |
356/// | ----------------------------- | ---------------------------------- |
357/// | `name` | ✅ |
358/// | `type` | ❌ (✅ if `annotation` given) |
359/// | `default` | ✅ (`<expr>` or `MISSING`) |
360/// | `default_factory` | ✅ (`lambda: <expr>` or `MISSING`) |
361/// | `new` | ✅ |
362/// | `repr` | ✅ |
363/// | `hash` | ❌ (`None` for pyderive) |
364/// | `compare` | ❌ (`None` for pyderive) |
365/// | `metadata` | ✅ (empty for pyderive) |
366/// | `kw_only` | ✅ |
367///
368/// 1. The `type` attribute of `Field` is `None` as default.
369/// If the field is marked by `#[pyderive(annotation=<type>)]`,
370/// this uses the given `<type>` as `type` attribute.
371/// 2. If the field is marked by `#[pyderive(default_factory=true)]`,
372/// the `default` attribute of the resulting `Field` obj is [`MISSING`][MISSING]
373/// and the `default_factory` is `lambda: <expr>`.
374/// Notes, it evaluates `<expr>` on every `Field.default_factory` call.
375///
376/// | Rust Field Attribute | Python `default` Attribute | Python `default_factory` Attribute |
377/// | ----------------------------------- | -------------------------- | ---------------------------------- |
378/// | `#[pyderive(default_factory=true)]` | `MISSING` | `lambda: <expr>` |
379/// | Other | `<expr>` | `MISSING` |
380/// 3. Attributes `hash` and `compare` are `None`.
381/// 4. This marks `new=false` field as a [`ClassVar` field][dataclass_ClassVar].
382///
383/// | Field Attribute | Result |
384/// | ---------------------- | -------------------------------------- |
385/// |`new=true` (default) | Dataclass field |
386/// |`new=false` | [`ClassVar` field][dataclass_ClassVar] |
387/// |`dataclass_field=false` | Exclude from `__dataclass_fields__` |
388/// 5. The [PEP 487][PEP487] ([`__set_name__()`][set_name] hook) is not supported
389/// (The default value of `__dataclass_fields__` is a different object
390/// from `__new__()`'s one, that is, they have different object IDs.
391/// This calls `__set_name__()` of `__dataclass_fields__` only,
392/// but not `__new__()`'s one).
393///
394/// [dataclasses]: https://docs.python.org/3/library/dataclasses.html
395/// [dataclass]: https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass
396/// [Field]: https://docs.python.org/3/library/dataclasses.html#dataclasses.Field
397/// [fields]: https://docs.python.org/3/library/dataclasses.html#dataclasses.fields
398/// [asdict]: https://docs.python.org/3/library/dataclasses.html#dataclasses.asdict
399/// [astuple]: https://docs.python.org/3/library/dataclasses.html#dataclasses.astuple
400/// [replace]: https://docs.python.org/3/library/dataclasses.html#dataclasses.replace
401/// [is_dataclass]: https://docs.python.org/3/library/dataclasses.html#dataclasses.is_dataclass
402/// [ClassVar]: https://docs.python.org/3/library/typing.html#typing.ClassVar
403/// [dataclass_ClassVar]: https://docs.python.org/3/library/dataclasses.html#class-variables
404/// [MISSING]: https://docs.python.org/3/library/dataclasses.html#dataclasses.MISSING
405/// [PEP487]: https://peps.python.org/pep-0487/
406/// [set_name]: https://docs.python.org/3/reference/datamodel.html#object.__set_name__
407pub use pyderive_macros::PyDataclassFields;
408/// Derive macro generating a [`__eq__()`][__eq__] and [`__ne__()`][__ne__] fn/Python methods.
409///
410/// The implementation requires [`PartialEq`] impl.
411///
412/// *Note that implementing `__eq__()` and `__ne__()` methods will cause
413/// Python not to generate a default `__hash__()` implementation,
414/// so consider also implementing `__hash__()`.*
415///
416/// # Expansion
417///
418/// This implements, for example;
419///
420/// ```
421/// # use pyo3::prelude::*;
422/// # #[pyclass]
423/// # #[derive(PartialEq)]
424/// # struct PyClass {}
425/// #[pymethods]
426/// impl PyClass {
427/// pub fn __eq__(&self, other: &Self) -> bool {
428/// self.eq(other)
429/// }
430/// pub fn __ne__(&self, other: &Self) -> bool {
431/// self.ne(other)
432/// }
433/// }
434/// ```
435///
436/// [__eq__]: https://docs.python.org/reference/datamodel.html#object.__eq__
437/// [__ne__]: https://docs.python.org/reference/datamodel.html#object.__ne__
438///
439/// # Example
440///
441/// ```
442/// use pyo3::{prelude::*, py_run};
443/// use pyderive::*;
444///
445/// #[derive(PyEq)]
446/// #[pyclass]
447/// #[derive(PartialEq)]
448/// struct PyClass {
449/// field: f64,
450/// }
451///
452/// Python::attach(|py| -> PyResult<()> {
453/// let a = Py::new(py, PyClass { field: 0.0 })?;
454/// let b = Py::new(py, PyClass { field: 1.0 })?;
455/// let c = Py::new(py, PyClass { field: f64::NAN })?;
456///
457/// py_run!(py, a b, "assert a == a");
458/// py_run!(py, a b, "assert a != b");
459/// py_run!(py, c, "assert c != c");
460/// py_run!(py, a, "assert a != 1");
461///
462/// Ok(())
463/// });
464/// ```
465pub use pyderive_macros::PyEq;
466
467/// Derive macro generating a [`__iter__()`][__iter__] fn/Python method.
468///
469/// It returns an iterator of `get` fields as default,
470/// in the order of declaration.
471///
472/// If the filed is marked by `#[pyderive(iter=true)]` attribute,
473/// the field is included to the iterator that `__iter__()` returns;
474/// if `#[pyderive(iter=false)]`, it isn't.
475///
476/// - It should place `#[derive(PyIter)]` before `#[pyclass]`.
477/// - It requires [`IntoPyObject`][pyo3_IntoPyObject] trait for fields.
478/// - Calling `__next__()` is thread-safe, it raises `PyRuntimeError` when it fails to take a lock.
479///
480/// [pyo3_IntoPyObject]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPyObject.html
481/// [pyo3_pyclass]: https://docs.rs/pyo3/latest/pyo3/attr.pyclass.html
482/// [__iter__]: https://docs.python.org/reference/datamodel.html#object.__iter__
483///
484/// # Example
485///
486/// ```
487/// use pyo3::{prelude::*, py_run};
488/// use pyderive::*;
489///
490/// // Place before `#[pyclass]`
491/// #[derive(PyIter)]
492/// #[pyclass(get_all)]
493/// struct PyClass {
494/// string: String,
495/// integer: i64,
496/// float: f64,
497/// tuple: (String, i64, f64),
498/// option: Option<String>,
499/// #[pyderive(iter=false)]
500/// excluded: String,
501/// }
502///
503/// Python::attach(|py| -> PyResult<()> {
504/// let a = Py::new(py, PyClass {
505/// string: "s".to_string(),
506/// integer: 1,
507/// float: 1.0,
508/// tuple: ("s".to_string(), 1, 1.0),
509/// option: None,
510/// excluded: "excluded".to_string(),
511/// })?;
512///
513/// py_run!(py, a, "assert tuple(a) == ('s', 1, 1.0, ('s', 1, 1.0), None)");
514///
515/// Ok(())
516/// });
517/// ```
518pub use pyderive_macros::PyIter;
519/// Derive macro generating a [`__len__()`][__len__] fn/Python method.
520///
521/// That returns number of `get` fields as default.
522///
523/// If the filed is marked by `#[pyderive(len=true)]` attribute,
524/// the field is counted by the `__len__()`; if `#[pyderive(len=false)]`, it isn't.
525///
526/// - It should place `#[derive(PyLen)]` before `#[pyclass]`.
527///
528/// [__len__]: https://docs.python.org/reference/datamodel.html#object.__len__
529///
530/// # Example
531///
532/// ```
533/// use pyo3::{prelude::*, py_run};
534/// use pyderive::*;
535///
536/// // Place before `#[pyclass]`
537/// #[derive(PyLen)]
538/// #[pyclass(get_all)]
539/// struct PyClass {
540/// string: String,
541/// integer: i64,
542/// float: f64,
543/// tuple: (String, i64, f64),
544/// option: Option<String>,
545/// #[pyderive(len=false)]
546/// excluded: String,
547/// }
548///
549/// Python::attach(|py| -> PyResult<()> {
550/// let a = Py::new(py, PyClass {
551/// string: "s".to_string(),
552/// integer: 1,
553/// float: 1.0,
554/// tuple: ("s".to_string(), 1, 1.0),
555/// option: None,
556/// excluded: "excluded".to_string(),
557/// })?;
558///
559/// py_run!(py, a, "assert len(a) == 5");
560///
561/// Ok(())
562/// });
563/// ```
564pub use pyderive_macros::PyLen;
565/// Derive macro generating a [`__match_args__`][__match_args__] const/Python class attribute.
566///
567/// It contains `get` fields as default,
568/// in the order of declaration.
569///
570/// If the filed is marked by `#[pyderive(match_args=true)]` attribute,
571/// the field is included to the `__match_args__`;
572/// if `#[pyderive(match_args=false)]`, it isn't.
573///
574/// - It should place `#[derive(PyMatchArgs)]` before `#[pyclass]`.
575///
576/// [__match_args__]: https://docs.python.org/reference/datamodel.html#object.__match_args__
577///
578/// # Example
579///
580/// ```
581/// use pyo3::{prelude::*, py_run};
582/// use pyderive::*;
583///
584/// // Place before `#[pyclass]`
585/// #[derive(PyNew, PyMatchArgs)]
586/// #[pyclass(get_all)]
587/// struct PyClass {
588/// string: String,
589/// integer: i64,
590/// float: f64,
591/// tuple: (String, i64, f64),
592/// option: Option<String>,
593/// #[pyderive(match_args=false)]
594/// excluded: String,
595/// }
596///
597/// let test = "
598/// match PyClass('s', 1, 1.0, ('s', 1, 1.0), None, 's'):
599/// case PyClass(a, b, c, d, e):
600/// assert a == 's'
601/// assert b == 1
602/// assert c == 1.0
603/// assert d == ('s', 1, 1.0)
604/// assert e is None
605/// case _:
606/// raise AssertionError
607/// ";
608///
609/// Python::attach(|py| {
610/// if py.version_info() >= (3, 10) {
611/// let PyClass = py.get_type::<PyClass>();
612///
613/// py_run!(py, PyClass, test)
614/// }
615/// });
616/// ```
617pub use pyderive_macros::PyMatchArgs;
618/// Derive macro generating a [`__new__()`][__new__] Python method.
619///
620/// It has all fields as the arguments as default,
621/// in the order of declaration.
622///
623/// If the filed is marked by `#[pyderive(new=false)]` attribute,
624/// the field is excluded from the arguments of the `__new__()` method.
625/// Notes, `new=true` has no effect.
626///
627/// - It should place `#[derive(PyNew)]` before `#[pyclass]`.
628///
629/// See the [Customize Implementation](crate) section of the crate doc for detail.
630///
631/// [__new__]: https://docs.python.org/reference/datamodel.html#object.__new__
632///
633/// # Example
634///
635/// ```
636/// use pyo3::{prelude::*, py_run};
637/// use pyderive::*;
638///
639/// // Place before `#[pyclass]`
640/// #[derive(PyNew)]
641/// #[pyclass(get_all)]
642/// struct PyClass {
643/// string: String,
644/// integer: i64,
645/// float: f64,
646/// tuple: (String, i64, f64),
647/// option: Option<String>,
648/// #[pyderive(new=false)]
649/// excluded: String,
650/// }
651///
652/// let test = "
653/// a = PyClass('s', 1, 1.0, ('s', 1, 1.0), None)
654/// assert a.string == 's'
655/// assert a.integer == 1
656/// assert a.float == 1.0
657/// assert a.tuple == ('s', 1, 1.0)
658/// assert a.option is None
659/// assert a.excluded == ''
660/// ";
661///
662/// Python::attach(|py| {
663/// let PyClass = py.get_type::<PyClass>();
664///
665/// py_run!(py, PyClass, test)
666/// });
667/// ```
668pub use pyderive_macros::PyNew;
669/// Derive macro generating [`__lt__()`][__lt__], [`__le__()`][__le__], [`__gt__()`][__gt__] and [`__ge__()`][__ge__] fn/Python methods.
670///
671/// The implementation requires [`PartialOrd`] impl.
672///
673/// <section class="warning">
674/// PyO3 supports <code>#[pyclass(ord)]</code> since 0.22.
675/// </section>
676///
677/// The generated methods return `False` when [`PartialOrd::partial_cmp`] returns [`None`].
678///
679/// *Note that implementing `__lt__()`, `__le__()`, `__gt__()` and `__ge__()` methods
680/// will cause Python not to generate a default `__hash__()` implementation,
681/// so consider also implementing `__hash__()`.*
682///
683/// # Expansion
684///
685/// This implements, for example;
686///
687/// ```
688/// # use std::cmp::Ordering;
689/// # use pyo3::prelude::*;
690/// # #[pyclass]
691/// # #[derive(PartialOrd, PartialEq)]
692/// # struct PyClass {}
693/// #[pymethods]
694/// impl PyClass {
695/// pub fn __lt__(&self, other: &Self) -> bool {
696/// matches!(self.partial_cmp(other), Some(Ordering::Less))
697/// }
698/// // and __le__, __gt__ and __ge__
699/// }
700/// ```
701///
702/// [__lt__]: https://docs.python.org/reference/datamodel.html#object.__lt__
703/// [__le__]: https://docs.python.org/reference/datamodel.html#object.__le__
704/// [__gt__]: https://docs.python.org/reference/datamodel.html#object.__gt__
705/// [__ge__]: https://docs.python.org/reference/datamodel.html#object.__ge__
706///
707/// # Example
708///
709/// ```
710/// use pyo3::{prelude::*, py_run};
711/// use pyderive::*;
712///
713/// #[derive(PyOrd)]
714/// #[pyclass]
715/// #[derive(PartialOrd, PartialEq)]
716/// struct PyClass {
717/// field: f64,
718/// }
719///
720/// Python::attach(|py| -> PyResult<()> {
721/// let a = Py::new(py, PyClass { field: 0.0 })?;
722/// let b = Py::new(py, PyClass { field: 1.0 })?;
723/// let c = Py::new(py, PyClass { field: f64::NAN })?;
724///
725/// py_run!(py, a b, "assert a < b");
726/// py_run!(py, a b, "assert a <= b");
727/// py_run!(py, a b, "assert not a > b");
728/// py_run!(py, a b, "assert not a >= b");
729/// py_run!(py, c, "assert not c < c");
730///
731/// let test = "
732/// try:
733/// a < 1
734/// except TypeError:
735/// pass
736/// else:
737/// raise AssertionError
738/// ";
739/// py_run!(py, a, test);
740///
741/// Ok(())
742/// });
743/// ```
744pub use pyderive_macros::PyOrd;
745/// Derive macro generating a [`__repr__()`][__repr__] fn/Python method.
746///
747/// It returns the string that contains `get` and `set` fields as default,
748/// in the order of declaration.
749///
750/// If the filed is marked by `#[pyderive(repr=true)]` attribute,
751/// the field is included in the string that `__str__()` returns;
752/// if `#[pyderive(repr=false)]`, it isn't.
753///
754/// - It should place `#[derive(PyRepr)]` before `#[pyclass]`.
755/// - It requires [`IntoPyObject`][pyo3_IntoPyObject] trait for fields.
756/// - This recursively calls `repr()` like a dataclass.
757///
758/// [pyo3_IntoPyObject]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPyObject.html
759/// [pyo3_pyclass]: https://docs.rs/pyo3/latest/pyo3/attr.pyclass.html
760/// [__repr__]: https://docs.python.org/reference/datamodel.html#object.__repr__
761/// [repr]: https://docs.python.org/library/functions.html#repr
762///
763/// # Example
764///
765/// ```
766/// use pyo3::{prelude::*, py_run};
767/// use pyderive::*;
768///
769/// // Place before `#[pyclass]`
770/// #[derive(PyRepr)]
771/// #[pyclass(get_all)]
772/// struct PyClass {
773/// string: String,
774/// integer: i64,
775/// float: f64,
776/// tuple: (String, i64, f64),
777/// option: Option<String>,
778/// #[pyderive(repr=false)]
779/// excluded: String,
780/// }
781///
782/// Python::attach(|py| -> PyResult<()> {
783/// let a = Py::new(py, PyClass {
784/// string: "s".to_string(),
785/// integer: 1,
786/// float: 1.0,
787/// tuple: ("s".to_string(), 1, 1.0),
788/// option: None,
789/// excluded: "excluded".to_string(),
790/// })?;
791///
792/// py_run!(py, a, r#"assert repr(a) == "PyClass(string='s', integer=1, float=1.0, tuple=('s', 1, 1.0), option=None)""#);
793///
794/// Ok(())
795/// });
796/// ```
797pub use pyderive_macros::PyRepr;
798/// Derive macro generating a [`__reversed__()`][__reversed__] fn/Python method.
799///
800/// It returns an iterator of `get` fields as default,
801/// in the reverse order of declaration.
802///
803/// This is a reversed one of a derive macro, [`PyIter`].
804///
805/// If the filed is marked by `#[pyderive(iter=true)]` attribute,
806/// the field is included to the iterator that `__reversed__()` returns;
807/// if `#[pyderive(iter=false)]`, it isn't.
808///
809/// - It should place `#[derive(PyReversed)]` before `#[pyclass]`.
810/// - It requires [`IntoPyObject`][pyo3_IntoPyObject] trait for fields.
811/// - Calling `__next__()` is thread-safe, it raises `PyRuntimeError` when it fails to take a lock.
812///
813/// [pyo3_IntoPyObject]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPyObject.html
814/// [pyo3_pyclass]: https://docs.rs/pyo3/latest/pyo3/attr.pyclass.html
815/// [__reversed__]: https://docs.python.org/reference/datamodel.html#object.__reversed__
816///
817/// # Example
818///
819/// ```
820/// use pyo3::{prelude::*, py_run};
821/// use pyderive::*;
822///
823/// // Place before `#[pyclass]`
824/// #[derive(PyReversed)]
825/// #[pyclass(get_all)]
826/// struct PyClass {
827/// string: String,
828/// integer: i64,
829/// float: f64,
830/// tuple: (String, i64, f64),
831/// option: Option<String>,
832/// #[pyderive(iter=false)]
833/// excluded: String,
834/// }
835///
836/// Python::attach(|py| -> PyResult<()> {
837/// let a = Py::new(py, PyClass {
838/// string: "s".to_string(),
839/// integer: 1,
840/// float: 1.0,
841/// tuple: ("s".to_string(), 1, 1.0),
842/// option: None,
843/// excluded: "excluded".to_string(),
844/// })?;
845///
846/// py_run!(py, a, "assert tuple(reversed(a)) == (None, ('s', 1, 1.0), 1.0, 1, 's')");
847///
848/// Ok(())
849/// });
850/// ```
851pub use pyderive_macros::PyReversed;
852/// Derive macro generating `__richcmp__` fn that provides Python comparison operations (`==`, `!=`, `<`, `<=`, `>`, and `>=`).
853///
854/// The implementation requires [`PartialEq`] and [`PartialOrd`] impl.
855///
856/// <section class="warning">
857/// PyO3 supports <code>#[pyclass(ord)]</code> since 0.22, it is recommended to use it.
858/// </section>
859///
860/// The generated methods return `False` when [`PartialOrd::partial_cmp`] returns [`None`].
861///
862/// *Note that implementing `__richcmp__` will cause Python not to generate
863/// a default `__hash__` implementation, so consider implementing `__hash__`
864/// when implementing `__richcmp__`.*
865///
866/// # Expansion
867///
868/// This implements, for example;
869///
870/// ```
871/// # use std::cmp::Ordering;
872/// # use pyo3::prelude::*;
873/// # use pyo3::pyclass::CompareOp;
874/// # #[pyclass]
875/// # #[derive(PartialOrd, PartialEq)]
876/// # struct PyClass {}
877/// #[pymethods]
878/// impl PyClass {
879/// pub fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
880/// match op {
881/// CompareOp::Eq => self.eq(other),
882/// CompareOp::Ne => self.ne(other),
883/// CompareOp::Lt => matches!(self.partial_cmp(other), Some(Ordering::Less)),
884/// CompareOp::Le => matches!(self.partial_cmp(other), Some(Ordering::Less | Ordering::Equal)),
885/// CompareOp::Gt => matches!(self.partial_cmp(other), Some(Ordering::Greater)),
886/// CompareOp::Ge => matches!(self.partial_cmp(other), Some(Ordering::Greater | Ordering::Equal))
887/// }
888/// }
889/// }
890/// ```
891///
892/// # Example
893///
894/// ```
895/// use pyo3::{prelude::*, py_run};
896/// use pyderive::*;
897///
898/// #[derive(PyRichCmp)]
899/// #[pyclass]
900/// #[derive(PartialOrd, PartialEq)]
901/// struct PyClass {
902/// field: f64,
903/// }
904///
905/// Python::attach(|py| -> PyResult<()> {
906/// let a = Py::new(py, PyClass { field: 0.0 })?;
907/// let b = Py::new(py, PyClass { field: 1.0 })?;
908/// let c = Py::new(py, PyClass { field: f64::NAN })?;
909///
910/// py_run!(py, a b, "assert a == a");
911/// py_run!(py, a b, "assert a != b");
912/// py_run!(py, a b, "assert a < b");
913/// py_run!(py, a b, "assert a <= b");
914/// py_run!(py, a b, "assert not a > b");
915/// py_run!(py, a b, "assert not a >= b");
916/// py_run!(py, c, "assert not c < c");
917///
918/// let test = "
919/// try:
920/// a < 1
921/// except TypeError:
922/// pass
923/// else:
924/// raise AssertionError
925/// ";
926/// py_run!(py, a, test);
927///
928/// Ok(())
929/// });
930/// ```
931pub use pyderive_macros::PyRichCmp;
932/// Derive macro generating a [`__str__()`][__str__] fn/Python method.
933///
934/// It returns the string that contains `get` and `set` fields as default,
935/// in the order of declaration.
936///
937/// If the filed is marked by `#[pyderive(str=true)]` attribute,
938/// the field is included in the string that `__str__()` returns;
939/// if `#[pyderive(str=false)]`, it isn't.
940///
941/// - It should place `#[derive(PyStr)]` before `#[pyclass]`.
942/// - It requires [`IntoPyObject`][pyo3_IntoPyObject] trait for fields.
943/// - recursively calls `str()` like a dataclass.
944///
945/// [pyo3_IntoPyObject]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPyObject.html
946/// [pyo3_pyclass]: https://docs.rs/pyo3/latest/pyo3/attr.pyclass.html
947/// [__str__]: https://docs.python.org/reference/datamodel.html#object.__str__
948/// [str]: https://docs.python.org/library/functions.html#str
949///
950/// # Example
951///
952/// ```
953/// use pyo3::{prelude::*, py_run};
954/// use pyderive::*;
955///
956/// // Place before `#[pyclass]`
957/// #[derive(PyStr)]
958/// #[pyclass(get_all)]
959/// struct PyClass {
960/// string: String,
961/// integer: i64,
962/// float: f64,
963/// tuple: (String, i64, f64),
964/// option: Option<String>,
965/// #[pyderive(str=false)]
966/// excluded: String,
967/// }
968///
969/// Python::attach(|py| -> PyResult<()> {
970/// let a = Py::new(py, PyClass {
971/// string: "s".to_string(),
972/// integer: 1,
973/// float: 1.0,
974/// tuple: ("s".to_string(), 1, 1.0),
975/// option: None,
976/// excluded: "excluded".to_string(),
977/// })?;
978///
979/// py_run!(py, a, r#"assert str(a) == "PyClass(string='s', integer=1, float=1.0, tuple=('s', 1, 1.0), option=None)""#);
980///
981/// Ok(())
982/// });
983/// ```
984pub use pyderive_macros::PyStr;
985
986/// Derive macro generating an impl of bitwise op methods/fns base on [std::ops] traits.
987///
988/// This derives;
989///
990/// | Python method | Required Trait |
991/// |------------------------------|-----------------------------------|
992/// | [`__invert__()`][__invert__] | `Not for &Class` |
993/// | [`__and__()`][__and__] | `BitAnd<&Class> for &Class` |
994/// | [`__or__()`][__or__] | `BitOr<&Class> for &Class` |
995/// | [`__xor__()`][__xor__] | `BitXor<&Class> for &Class` |
996/// | [`__iand__()`][__iand__] | `BitAndAssign<&Class> for &Class` |
997/// | [`__ior__()`][__ior__] | `BitOrAssign<&Class> for &Class` |
998/// | [`__ixor__()`][__ixor__] | `BitXorAssign<&Class> for &Class` |
999///
1000/// [__invert__]: https://docs.python.org/3/reference/datamodel.html#object.__invert__
1001/// [__and__]: https://docs.python.org/3/reference/datamodel.html#object.__and__
1002/// [__or__]: https://docs.python.org/3/reference/datamodel.html#object.__or__
1003/// [__xor__]: https://docs.python.org/3/reference/datamodel.html#object.__xor__
1004/// [__iand__]: https://docs.python.org/3/reference/datamodel.html#object.__iand__
1005/// [__ior__]: https://docs.python.org/3/reference/datamodel.html#object.__ior__
1006/// [__ixor__]: https://docs.python.org/3/reference/datamodel.html#object.__ixor__
1007pub use pyderive_macros::PyBitwise;
1008/// Derive macro generating an impl of numeric op methods/fns base on [std::ops] traits.
1009///
1010/// This derives;
1011///
1012/// | Python method | Required Trait |
1013/// |----------------------------------|-----------------------------------------|
1014/// | [`__pos__()`][__pos__] | -- |
1015/// | [`__neg__()`][__neg__] | `Neg<&Class> for &Class` |
1016/// | [`__add__()`][__add__] | `Add<&Class> for &Class` |
1017/// | [`__sub__()`][__sub__] | `Sub<&Class> for &Class` |
1018/// | [`__mul__()`][__mul__] | `Mul<&Class> for &Class` |
1019/// | [`__truediv__()`][__truediv__] | `Div<&Class> for &Class` |
1020/// | [`__mod__()`][__mod__] | `Rem<&Class> for &Class` |
1021/// | [`__iadd__()`][__iadd__] | `AddAssign<&Class> for &Class` |
1022/// | [`__isub__()`][__isub__] | `SubAssign<&Class> for &Class` |
1023/// | [`__imul__()`][__imul__] | `MulAssign<&Class> for &Class` |
1024/// | [`__itruediv__()`][__itruediv__] | `DivAssign<&Class> for &Class` |
1025/// | [`__imod__()`][__imod__] | `RemAssign<&Class> for &Class` |
1026/// | [`__divmod__()`][__divmod__] | Same as `__truediv__()` and `__mod__()` |
1027///
1028/// [__pos__]: https://docs.python.org/3/reference/datamodel.html#object.__pos__
1029/// [__neg__]: https://docs.python.org/3/reference/datamodel.html#object.__neg__
1030/// [__add__]: https://docs.python.org/3/reference/datamodel.html#object.__add__
1031/// [__sub__]: https://docs.python.org/3/reference/datamodel.html#object.__sub__
1032/// [__mul__]: https://docs.python.org/3/reference/datamodel.html#object.__mul__
1033/// [__truediv__]: https://docs.python.org/3/reference/datamodel.html#object.__truediv__
1034/// [__mod__]: https://docs.python.org/3/reference/datamodel.html#object.__mod__
1035/// [__iadd__]: https://docs.python.org/3/reference/datamodel.html#object.__iadd__
1036/// [__isub__]: https://docs.python.org/3/reference/datamodel.html#object.__isub__
1037/// [__imul__]: https://docs.python.org/3/reference/datamodel.html#object.__imul__
1038/// [__itruediv__]: https://docs.python.org/3/reference/datamodel.html#object.__itruediv__
1039/// [__imod__]: https://docs.python.org/3/reference/datamodel.html#object.__imod__
1040/// [__divmod__]: https://docs.python.org/3/reference/datamodel.html#object.__divmod__
1041pub use pyderive_macros::PyNumeric;