Crate pyderive

Source
Expand description

This library provides derive macros of Python spacial methods and a class attributes for PyO3.

The field attribute #[pyderive(..)] helps to customize implementations, like dataclasses.field() of Python.

It requires to enable multiple-pymethods feature of PyO3 because the derive macros that this library provides may implement multiple #[pymethods].

§Example

// Enable `multiple-pymethods` feature of PyO3
use pyo3::prelude::*;
use pyderive::*;

// Place #[derive(PyNew, ...)] before #[pyclass]
#[derive(PyNew, PyMatchArgs, PyRepr, PyEq)]
#[pyclass(get_all)]
#[derive(PartialEq, Hash)]
struct MyClass {
    string: String,
    integer: i64,
    option: Option<i64>
}
# Python script
from rust_module import MyClass


# Derives __new__()
m = MyClass("a", 1, None)

# Derives __match_args__ (supports Pattern Matching by positional arguments)
match m:
    case MyClass(a, b, c):
        assert a == "a"
        assert b == 1
        assert c is None
    case _:
        raise AssertionError

# Derives __repr__(), calls Python repr() recursively
assert str(m) == "MyClass(string='a', integer=1, option=None)"
assert repr(m) == "MyClass(string='a', integer=1, option=None)"

# Derives __eq__() that depends on PartialEq trait
assert m == MyClass("a", 1, None)

§Detail

Some macros change implementations depend on #[pyclass(..)] and #[pyo3(..)] arguments, hence it should place #[derive(PyNew)] etc. before #[pyclass(..)] and #[pyo3(..)].

We list the default implementations that the macros generate.

Derive MacroDerives
PyNew__new__() with all fields
PyMatchArgs__match_args__ class attr. with get fields
PyRepr__repr__() returns get and set fields
PyStr__str__() returns get and set fields
PyIter__iter__() returns an iterator of get fields
PyReversed__reversed__() returns an iterator of get fields
PyLen__len__() returns number of get fields
PyDataclassFields__dataclass_fields__ class attr. with all fields

Notes, methods implemented by PyRepr and PyStr are recursively calls repr() or str() like a Python dataclass.

We call the field is get (or set) field if the field has a #[pyclass/pyo3(get)] (or #[pyclass/pyo3(set)]) attribute or its struct has a #[pyclass/pyo3(get_all)] (or #[pyclass/pyo3(set_all)]) attribute.

The following derive macros depend on traits.

Derive MacroDerives
PyEq__eq__() and __ne__(), depends on PartialEq
PyOrd__lt__(), __le__(), __gt__() and __ge__(), depend on PartialOrd
PyRichCmp==, !=, >, >=, < and <= by __richcmp__(), depend on PartialEq and PartialOrd
PyNumericNumeric op traits (__add__() etc.)
PyBitwiseBitwise op traits (__and__() etc.)

Notes, implementation of PyEq and PyOrd does not use __richcmp__().

Module pyderive::ops and pyderive::convert provides derive macros that implement individual method that enumerating numeric type (__add__() etc.) and called by builtin functions (__int__() etc.).

§Customize Implementation

The field attributes #[pyderive(..)] is used to customize implementations produced by pyderive’s derive.

use pyderive::*;

#[derive(PyNew, PyRepr)]
#[pyclass]
struct MyClass {
    string: String,
    #[pyderive(repr=false)]
    #[pyo3(get)]
    integer: i64,
    #[pyderive(default=10)]
    option: Option<i64>
}

It allows to omit the right-hand side, and it evaluates to the right-hand as true except default , for example, #[pyderive(repr)] is equivalent to #[pyderive(repr=true)].

  • #[pyderive(repr=<bool>)]

    If repr=true, the field is included in the string that the __repr__() method returns; if repr=false, it isn’t.

    The derive macro PyDataclassFields reads this attribute also, see PyDataclassFields for detail.

  • #[pyderive(str=<bool>)]

    If str=true, the field is included in the string that the __str__() method returns; if str=false, it isn’t.

  • #[pyderive(new=<bool>)]

    If new=false, the field is excluded from the arguments of the __new__() method. Notes, new=true has no effect.

    The derive macro PyDataclassFields reads this attribute also, see PyDataclassFields for detail.

  • #[pyderive(default=<expr>)]

    This is used to customize default value for the __new__() method. It supports any rust expression which PyO3 supports, e.g.,

    #[derive(PyNew)]
    #[pyclass]
    struct PyClass {
        #[pyderive(default = Some("str".to_string()))]
        field: Option<String>,
    }

    We note that this internally produces #[pyo3(signature = ..)] attribute.

    1. No #[pyderive(..)] (for example, just field: i64)

      Pseudocode:

      def __new__(cls, field):
           self = super().__new__(cls)
           self.field = field
           return self
    2. #[pyderive(new=false)]

      The field is excluded from the arguments, and initialized by Default::default() in the __new__() method. We note that it is evaluated on every __new__() call.

      Pseudocode:

      def __new__(cls):
           self = super().__new__(cls)
           self.field = field::default()  # call rust fn
           return self
    3. #[pyderive(default=<expr>)]

      The field is included to the arguments with default value <expr>. We note that <expr> (rust code) is evaluated on every __new__() call (PyO3 feature).

      Pseudocode:

      def __new__(cls, field=<expr>):
           self = super().__new__(cls)
           self.field = field
           return self
    4. #[pyderive(new=false, default=<expr>)]

      The field is excluded from the arguments, and initialized with <expr> in the __new__() method. We note that <expr> (rust code) is evaluated on every __new__() call.

      Pseudocode:

      def __new__(cls):
           self = super().__new__(cls)
           self.field = <expr>
           return self
  • #[pyderive(default_factory=true)]

    If default_factory=true, let the default_factory attribute of Fieldobj be lambda: <expr>, and let the default attribute be dataclasses.MISSING, where <expr> is given by #[pyderive(default=<expr>)]. Notes, default_factory=false has no effect, If the field is not marked by #[pyderive(default=<expr>)], this ignores.

    See PyDataclassFields for detail.

  • #[pyderive(kw_only=true)]

    If kw_only=true, the following fields are keyword only arguments in the __new__() method, like * and dataclasses.KW_ONLY. Note, kw_only=false has no effect.

    The derive macro PyDataclassFields reads this attribute also, see PyDataclassFields for detail.

  • #[pyderive(match_args=<bool>)]

    If match_args=true, the field is included in the __match_args__ class attribute; if match_args=false, it isn’t.

    We note that, as far as I know, the field must be accessible on the pattern matching. For example, pattern matching does not work with not get field without a getter (even if match_args=true), but it does work if the field has a getter.

  • #[pyderive(iter=<bool>)]

    If iter=true, the field is included in the iterator that __iter__() and __reversed__() return; if iter=false, it isn’t.

  • #[pyderive(len=<bool>)]

    If len=true, the field is counted by the __len__(); if len=false, it isn’t.

  • #[pyderive(dataclass_field=false)]

    If dataclass_field=false, the field is excluded from the __dataclass_fields__ dict. Notes, dataclass_field=true has no effect.

    See PyDataclassFields for detail.

  • #[pyderive(annotation=<str>)]

    The derive macro PyDataclassFields reads this attribute, see PyDataclassFields for detail.

Modules§

convert
Provides derive macros that implements conversion by built-in functions.
ops
Provides derive macros that implements enumeration of numeric type.

Derive Macros§

PyBitwise
Derive macro generating an impl of bitwise op methods/fns base on std::ops traits.
PyDataclassFields
Derive macro generating a __dataclass_fields__ fn/Python class attribute.
PyEq
Derive macro generating a __eq__() and __ne__() fn/Python methods.
PyHash
Derive macro generating a __hash__() fn/Python method.
PyIter
Derive macro generating a __iter__() fn/Python method.
PyLen
Derive macro generating a __len__() fn/Python method.
PyMatchArgs
Derive macro generating a __match_args__ const/Python class attribute.
PyNew
Derive macro generating a __new__() Python method.
PyNumeric
Derive macro generating an impl of numeric op methods/fns base on std::ops traits.
PyOrd
Derive macro generating __lt__(), __le__(), __gt__() and __ge__() fn/Python methods.
PyRepr
Derive macro generating a __repr__() fn/Python method.
PyReversed
Derive macro generating a __reversed__() fn/Python method.
PyRichCmp
Derive macro generating __richcmp__ fn that provides Python comparison operations (==, !=, <, <=, >, and >=).
PyStr
Derive macro generating a __str__() fn/Python method.
ToPyObject
Derive macro generating an impl of the trait ToPyObject by trait IntoPy<PyObject>.