use pyo3::{IntoPyObjectExt, exceptions::PyTypeError, prelude::*, types::PyType};
#[pyclass(name = "Local")]
pub struct PyLocal {
#[pyo3(get, name = "value_type")]
pub(crate) ty: Py<PyType>,
#[pyo3(get)]
pub(crate) value: Py<PyAny>,
}
#[pymethods]
impl PyLocal {
#[classmethod]
#[pyo3(signature = (key, /))]
pub fn __class_getitem__(
cls: &Bound<'_, PyType>,
key: &Bound<'_, PyAny>,
) -> PyResult<Py<PyAny>> {
let ty = key.cast::<PyType>().map_err(|_| {
PyTypeError::new_err(format!("Local[...] expects a type, got {}", key.get_type()))
})?;
let value = key.call0().map_err(|e| {
PyTypeError::new_err(format!(
"Local[{}]: type must be callable with no arguments to create a default value ({})",
ty, e
))
})?;
Self {
ty: ty.clone().unbind(),
value: value.unbind(),
}
.into_py_any(cls.py())
}
#[new]
pub fn new(value: Bound<'_, PyAny>) -> Self {
Self {
ty: value.get_type().into(),
value: value.unbind(),
}
}
#[setter]
pub fn set_value(&mut self, py: Python, value: Bound<'_, PyAny>) -> PyResult<()> {
if !value.get_type().is(self.ty.bind(py)) {
return Err(PyTypeError::new_err(format!(
"Expected type {}, but got {}",
self.ty,
value.get_type()
)));
}
self.value = value.unbind();
Ok(())
}
pub fn __getattr__(&self, py: Python, name: &str) -> PyResult<Py<PyAny>> {
self.value.bind(py).getattr(name).map(|v| v.unbind())
}
pub fn __setattr__(&mut self, py: Python, name: &str, value: Bound<'_, PyAny>) -> PyResult<()> {
self.value.bind(py).setattr(name, value)
}
}