diff --git a/Cargo.lock b/Cargo.lock index f8c9d338..f6c68717 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2144,7 +2144,6 @@ name = "derive" version = "0.1.0" dependencies = [ "proc-macro2", - "pyo3", "quote", "quote_into", "syn 1.0.109", @@ -2457,6 +2456,7 @@ name = "dora-node-api-python" version = "0.3.4" dependencies = [ "arrow", + "derive", "dora-node-api", "dora-operator-api-python", "dora-ros2-bridge-python", diff --git a/Cargo.toml b/Cargo.toml index 5449bea7..8dfe2520 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ dora-coordinator = { version = "0.3.4", path = "binaries/coordinator" } dora-ros2-bridge = { path = "libraries/extensions/ros2-bridge" } dora-ros2-bridge-msg-gen = { path = "libraries/extensions/ros2-bridge/msg-gen" } dora-ros2-bridge-python = { path = "libraries/extensions/ros2-bridge/python" } +derive = { path = "derive" } arrow = { version = "52" } arrow-schema = { version = "52" } arrow-data = { version = "52" } diff --git a/apis/python/node/Cargo.toml b/apis/python/node/Cargo.toml index 6e05782a..2156a8e4 100644 --- a/apis/python/node/Cargo.toml +++ b/apis/python/node/Cargo.toml @@ -25,6 +25,7 @@ arrow = { workspace = true, features = ["pyarrow"] } pythonize = { workspace = true } futures = "0.3.28" dora-ros2-bridge-python = { workspace = true } +derive = { workspace = true } [lib] name = "dora" diff --git a/apis/python/node/src/lib.rs b/apis/python/node/src/lib.rs index ba9c6c46..a44c3a76 100644 --- a/apis/python/node/src/lib.rs +++ b/apis/python/node/src/lib.rs @@ -24,6 +24,7 @@ use pyo3::types::{PyBytes, PyDict}; /// ``` /// #[pyclass] +#[derive(derive::DirHelper)] pub struct Node { events: Events, node: DoraNode, @@ -197,6 +198,13 @@ impl Node { Ok(()) } + + // TODO: We should only list fields which are at least readableby Python users, right? + + /// Get a list of the fields of this Node. + pub fn __dir__(&self) -> Vec { + self.fields() + } } enum Events { diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 4157d0cb..9c2a4e11 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -8,7 +8,6 @@ syn = "1.0" quote = "1.0" proc-macro2 = "1.0" quote_into = "0.2.0" -pyo3 = { workspace = true, features = ["eyre", "abi3-py37"] } [lib] proc-macro = true \ No newline at end of file diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 50c5d1cd..4398a59f 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,9 +1,15 @@ +//! Derive macros for dora + extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput, Fields}; -/// Add a Python `__dir__`` method to the struct. +/// Add a `fields` method to the struct. +/// +/// Because we cannot have multiple `#[pymethods]` impls, this macro +/// produces a function called `fields` which should be called from the +/// PyO3 impl. #[proc_macro_derive(DirHelper)] pub fn dir_helper_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -31,9 +37,8 @@ pub fn dir_helper_derive(input: TokenStream) -> TokenStream { } }];); quote! { - #[pyo3::prelude::pymethods] impl #name { - pub fn __dir__(&self) -> Vec { + pub fn fields(&self) -> Vec { let mut names = Vec::new(); #assigner names @@ -44,9 +49,8 @@ pub fn dir_helper_derive(input: TokenStream) -> TokenStream { Fields::Unit => { // If the struct has no fields quote! { - #[pyo3::prelude::pymethods] impl #name { - pub fn __dir__(&self) -> Vec { + pub fn fields(&self) -> Vec { Vec::new() } } diff --git a/derive/tests/functional.rs b/derive/tests/functional.rs index 04d91035..7b8452cc 100644 --- a/derive/tests/functional.rs +++ b/derive/tests/functional.rs @@ -1,8 +1,6 @@ use derive::DirHelper; -use pyo3::pyclass; #[derive(DirHelper)] -#[pyclass] #[allow(dead_code)] struct WithFields { hello: (), @@ -19,7 +17,7 @@ fn test_with_fields() { my: "".to_string(), name: 0.0, } - .__dir__(); + .fields(); assert_eq!( vec![ "hello".to_string(), @@ -32,12 +30,11 @@ fn test_with_fields() { } #[derive(DirHelper)] -#[pyclass] #[allow(dead_code)] struct UnitNoFields; #[test] fn test_no_fields() { - let fields: Vec = UnitNoFields.__dir__(); + let fields: Vec = UnitNoFields.fields(); assert_eq!(Vec::::new(), fields); }