-
Notifications
You must be signed in to change notification settings - Fork 918
Closed
Labels
Description
I am creating a Python API for a Rust library. Some of the methods return nested Rust structs.
For example:
#[pymethods]
impl Client {
fn list(&self) -> PyResult<Vec<XpStatus>> {
// ...
}
}
#[pyclass]
pub struct XpStatus {
pub xp: Xp,
pub container_status: HashMap<ContainerId, ContainerStatus>,
pub containers_by_lifecycle: HashMap<ContainerStatusKind, Vec<ContainerId>>,
pub max_containers: u64,
}
#[pyclass]
pub struct Xp {
pub def: XpDef,
pub uid: XpId,
pub lifecycle: XpLifecycle,
pub creation_time: DateTime<Utc>,
pub priority: i64,
pub queue_pos: u64,
}
pub enum ContainerStatus {
Running,
Creating,
Completed {
exit_code: u64,
error: String,
finished_at: DateTime<Utc>,
},
None,
}
// ...
I would like the Python code to be able to access all members of the returned structs.
The simplest option might be to define getters on all members. However, unless I'm mistaken this would seem to require copying the entire substructure on each access which would make it very expensive to iterate over collections contained in the struct from Python code.
To avoid this performance hit, we need to fully convert the struct into a Python compatible object. I can think of two different ways this could be achieved.
- Create a second version for each struct in Rust which is compatible in Python and then manually convert these from Rust code.
This would look something like this:
#[pyclass(name="XpStatus")]
pub struct PyXpStatus {
#[getter]
pub xp: PyCell<PyXp>,
#[getter]
pub container_status: PyDict,
// ...
}
impl From<XpStatus> for PyXpStatus {
// ...
}
Probably this could even be generated automatically by a macro.
- Create Python versions of all structs in Python, and instantiate those directly.
If we're going to create a new version of all structs anyway, we might as well do so in Python. This has the added benefits of allowing for a slightly more idiomatic API and also making "jump to source" work so Python users can look at the Python definitions of all classes rather than an opaque stub or Rust source.
I think this might be the preferred solution. I'm still slightly unsure how to best convert the Rust structs into Python classes on the Rust side. When building a mixed Rust/Python project with maturin, can you just usePyModule::importto import the Python portion of the module on the Rust side? Or would you usePyModule::from_codeandinclude_str?
Does this seem like a reasonable approach?