diff --git a/doc/reference.rst b/doc/reference.rst index 1191288..498f3d3 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -49,6 +49,7 @@ Values ------ .. autoclass:: OpaquePythonObject +.. autoclass:: ToRecord Decimal ^^^^^^^ diff --git a/src/lib.rs b/src/lib.rs index f6b7762..2bdd0c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -240,6 +240,47 @@ fn pyobject_to_value<'v>(obj: Bound, heap: &'v Heap) -> PyResult() { + let to_rec = to_rec_bound.borrow(); + let fields_func = obj.py().import("dataclasses")?.getattr("fields")?; + let fields = fields_func.call1((&to_rec.object,))?; + + let record_constr = convert_anyhow_err( + to_rec + .module + .borrow(obj.py()) + .0 + .get(to_rec.type_name.as_str()), + )?; + + let module = starlark::environment::Module::new(); + let mut eval = starlark::eval::Evaluator::new(&module); + let sl_kwargs = fields + .downcast::()? + .iter() + .map(|fld| { + let name = fld.getattr("name")?.extract::()?; + Ok(( + name.clone(), + pyobject_to_value(to_rec.object.bind(obj.py()).getattr(&name)?, eval.heap())?, + )) + }) + .collect::>>()?; + + let record = convert_starlark_err( + eval.eval_function( + record_constr.value(), + &[], + &sl_kwargs + .iter() + .map(|(k, v)| (k.as_str(), v.dupe())) + .collect::)>>(), + ), + )?; + + return Ok(record); + } + let json = obj.py().import("json")?; let json_str: String = json.getattr("dumps")?.call1((obj,))?.extract()?; convert_anyhow_err(serde_to_starlark( @@ -882,6 +923,36 @@ impl Globals { // }}} +// {{{ ToRecord + +/// An object that, given a Python :func:`dataclasses.dataclass`__, +/// upon conversion to Starlark, will return a Starlark +/// `record +/// `__. +/// +/// .. versionadded:: 2025.2.6 +#[pyclass] +struct ToRecord { + module: Py, + type_name: String, + object: Py, +} + +#[pymethods] +impl ToRecord { + #[new] + #[pyo3(signature = (module, type_name, obj))] + fn new(module: Py, type_name: &str, obj: Py) -> Self { + ToRecord { + module: module, + type_name: type_name.to_string(), + object: obj, + } + } +} + +// }}} + // {{{ OpaquePythonObject #[derive(Debug, ProvidesStaticType, NoSerialize, Allocative)] diff --git a/starlark.pyi b/starlark.pyi index 922c755..67c8238 100644 --- a/starlark.pyi +++ b/starlark.pyi @@ -156,6 +156,10 @@ class LibraryExtension: CallStack: LibraryExtension RustDecimal: LibraryExtension +@final +class ToRecord: + def __new__(cls, module: FrozenModule, type_name: str, obj: object) -> ToRecord: ... + @final class OpaquePythonObject: def __new__(cls, obj: object) -> OpaquePythonObject: ...