Reputation: 769
Context of my question:
In pydantic's, you can define a CustomType
, which is any python class with the following class method named __get_validators__(cls)
defined. The objective is to have pydantic validate the input to the initialization of the CustomType
instance.
class CustomType:
@classmethod
def __get_validators__(cls):
yield cls.validate_func_one
I am attempting to create a struct in Rust, mapping it to Python class using the familiar attribute macro #[pyclass]
. My objective is to create from Rust, a Python class that is compatible with pydantic validation scheme, e.g. having the classmethod(__get_validators__)
defined.
Here is my attempt:
#[pyclass(name = "MyPythonClass")]
#[derive(Debug, Clone)]
pub struct PyClass {
pub rust_object: MyRustStruct,
}
The implementation of PyClass
is as follows:
#[pymethods]
impl PyClass {
#[new]
fn new(string: String) -> PyResult<PyClass > {
Ok(PyClass {
rust_object: MyRustStruct::new(string).unwrap(),
})
}
// intended to be the `cls.validate` function which is yielded
fn __call__(_cls: &PyType, v: &PyString, _values: &PyDict) -> PyResult<PyClass> {
let v: &str = v.extract()?;
Ok(PyClass{
// PyClass::new does all the validations required in rust
rust_object: PyClass::new(v).unwrap(),
})
}
#[classmethod] // not sure how to write this...
fn __get_validator__(_cls: &PyType) -> PyResult<PyClassContainer> {
Ok(Self::get_container())
}
PyClass
is a wrapper over the underlying Rust struct MyRustStruct
, such that MyRustStruct
can be used for other purposes, e.g. to build a binary etc. This is standard practice.
To be able to yield
, I would need two things: first, a struct that is an Iterator
in rust and last, that struct
has to implement
First:
#[pyclass(name = "PyClassContainer")]
#[derive(Debug)]
struct PyClassContainer{
iter: Vec<PyClass>,
}
To trivially 'implement' the rust Iterator
trait, I dump the PyClass
object into a Vector
.
Then lastly, for the implementations of __iter__
and __next__
of PyClassContainer
:
#[pymethods]
impl PyClassContainer{
fn __iter__(slf: PyRef<'_, Self>) -> PyResult<Py<PyClassContainer>> {
let iter = PyClassContainer{
iter: slf.iter.clone().into_iter(),
};
Py::new(slf.py(), iter)
}
// to use `yield`, the `__next__` method has to return an `IterNextOutput<T, U>` enum
// https://docs.rs/pyo3/latest/pyo3/pyclass/enum.IterNextOutput.html
fn __next__(slf: PyRef<'_, Self>) -> IterNextOutput<Result<PyClass, &'static str>, PyErr> {
match self.iter.into_iter() {
Some(Ok(item)) => IterNextOutput::Yield(Ok(item)),
Some(Err(err)) => IterNextOutput::Return(PyStopIteration::new_err(err)),
}
}
}
My intent is to have __next__
to 'yield' Ok(item)
from the .iter
field of PyClassContainer
which will return me an instance of the python object of type PyClass
when PyClass.__get_validator__
is called.
However, the signature of attribute macro #[classmethod]
does not allow the passing in of a reference of self
, which makes it impossible for me to 'clone' the fields that are already available in self
.
Upvotes: 1
Views: 520