You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

lib.rs 5.1 kB

3 years ago
3 years ago
3 years ago
3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. #![allow(clippy::borrow_deref_ref)] // clippy warns about code generated by #[pymethods]
  2. use arrow::pyarrow::PyArrowConvert;
  3. use dora_node_api::{DoraNode, Event, EventStream};
  4. use dora_operator_api_python::{metadata_to_pydict, pydict_to_metadata};
  5. use eyre::Context;
  6. use pyo3::exceptions::PyLookupError;
  7. use pyo3::prelude::*;
  8. use pyo3::types::{PyBytes, PyDict};
  9. #[pyclass]
  10. pub struct Node {
  11. events: EventStream,
  12. node: DoraNode,
  13. }
  14. #[pyclass]
  15. pub struct PyEvent(Event);
  16. #[pymethods]
  17. impl PyEvent {
  18. pub fn __getitem__(&mut self, key: &str, py: Python<'_>) -> PyResult<PyObject> {
  19. let value = match key {
  20. "type" => Some(self.ty().to_object(py)),
  21. "id" => self.id().map(|v| v.to_object(py)),
  22. "data" => self.data(py),
  23. "data_arrow" => self.data_arrow(py)?,
  24. "metadata" => self.metadata(py),
  25. "error" => self.error().map(|v| v.to_object(py)),
  26. other => {
  27. return Err(PyLookupError::new_err(format!(
  28. "event has no property `{other}`"
  29. )))
  30. }
  31. };
  32. value.ok_or_else(|| PyLookupError::new_err(format!("event has no property `{key}`")))
  33. }
  34. }
  35. impl PyEvent {
  36. fn ty(&self) -> &str {
  37. match &self.0 {
  38. Event::Stop => "STOP",
  39. Event::Input { .. } => "INPUT",
  40. Event::InputClosed { .. } => "INPUT_CLOSED",
  41. Event::Error(_) => "ERROR",
  42. _other => "UNKNOWN",
  43. }
  44. }
  45. fn id(&self) -> Option<&str> {
  46. match &self.0 {
  47. Event::Input { id, .. } => Some(id),
  48. Event::InputClosed { id } => Some(id),
  49. _ => None,
  50. }
  51. }
  52. fn data(&self, py: Python<'_>) -> Option<PyObject> {
  53. match &self.0 {
  54. Event::Input {
  55. data: Some(data), ..
  56. } => Some(PyBytes::new(py, data).into()),
  57. _ => None,
  58. }
  59. }
  60. fn data_arrow(&mut self, py: Python<'_>) -> PyResult<Option<PyObject>> {
  61. if let Event::Input { data, .. } = &mut self.0 {
  62. if let Some(data) = data.take() {
  63. let array = data
  64. .into_arrow_array()
  65. .map_err(|err| arrow::pyarrow::PyArrowException::new_err(err.to_string()))?;
  66. // TODO: Does this call leak data?
  67. let array_data = array.to_pyarrow(py)?;
  68. return Ok(Some(array_data));
  69. }
  70. }
  71. Ok(None)
  72. }
  73. fn metadata(&self, py: Python<'_>) -> Option<PyObject> {
  74. match &self.0 {
  75. Event::Input { metadata, .. } => Some(metadata_to_pydict(metadata, py).to_object(py)),
  76. _ => None,
  77. }
  78. }
  79. fn error(&self) -> Option<&str> {
  80. match &self.0 {
  81. Event::Error(error) => Some(error),
  82. _other => None,
  83. }
  84. }
  85. }
  86. #[pymethods]
  87. impl Node {
  88. #[new]
  89. pub fn new() -> eyre::Result<Self> {
  90. let (node, events) = DoraNode::init_from_env()?;
  91. Ok(Node { events, node })
  92. }
  93. #[allow(clippy::should_implement_trait)]
  94. pub fn next(&mut self, py: Python) -> PyResult<Option<PyEvent>> {
  95. self.__next__(py)
  96. }
  97. pub fn __next__(&mut self, py: Python) -> PyResult<Option<PyEvent>> {
  98. let event = py.allow_threads(|| self.events.recv());
  99. Ok(event.map(PyEvent))
  100. }
  101. fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
  102. slf
  103. }
  104. pub fn send_output(
  105. &mut self,
  106. output_id: String,
  107. data: PyObject,
  108. metadata: Option<&PyDict>,
  109. py: Python,
  110. ) -> eyre::Result<()> {
  111. if let Ok(py_bytes) = data.downcast::<PyBytes>(py) {
  112. let data = py_bytes.as_bytes();
  113. self.send_output_slice(output_id, data.len(), data, metadata)
  114. } else if let Ok(arrow_array) = arrow::array::ArrayData::from_pyarrow(data.as_ref(py)) {
  115. if arrow_array.buffers().len() != 1 {
  116. eyre::bail!("output arrow array must contain a single buffer");
  117. }
  118. let len = arrow_array.len();
  119. let slice = &arrow_array.buffer(0)[..len];
  120. self.send_output_slice(output_id, len, slice, metadata)
  121. } else {
  122. eyre::bail!("invalid `data` type, must by `PyBytes` or arrow array")
  123. }
  124. }
  125. }
  126. impl Node {
  127. fn send_output_slice(
  128. &mut self,
  129. output_id: String,
  130. len: usize,
  131. data: &[u8],
  132. metadata: Option<&PyDict>,
  133. ) -> eyre::Result<()> {
  134. let metadata = pydict_to_metadata(metadata)?;
  135. self.node
  136. .send_output(output_id.into(), metadata, len, |out| {
  137. out.copy_from_slice(data);
  138. })
  139. .wrap_err("failed to send output")
  140. }
  141. pub fn id(&self) -> String {
  142. self.node.id().to_string()
  143. }
  144. }
  145. #[pyfunction]
  146. fn start_runtime() -> eyre::Result<()> {
  147. dora_runtime::main().wrap_err("Dora Runtime raised an error.")
  148. }
  149. #[pymodule]
  150. fn dora(_py: Python, m: &PyModule) -> PyResult<()> {
  151. m.add_function(wrap_pyfunction!(start_runtime, m)?)?;
  152. m.add_class::<Node>().unwrap();
  153. Ok(())
  154. }

DORA (Dataflow-Oriented Robotic Architecture) is middleware designed to streamline and simplify the creation of AI-based robotic applications. It offers low latency, composable, and distributed datafl