1use std::hash::DefaultHasher;
10use std::hash::Hash;
11use std::hash::Hasher;
12use std::ops::Deref;
13use std::sync::Arc;
14
15use hyperactor::Endpoint as _;
16use hyperactor::Mailbox;
17use hyperactor::OncePortHandle;
18use hyperactor::PortHandle;
19use hyperactor::RemoteEndpoint as _;
20use hyperactor::accum::Accumulator;
21use hyperactor::accum::CommReducer;
22use hyperactor::accum::ReducerFactory;
23use hyperactor::accum::ReducerSpec;
24use hyperactor::mailbox::MailboxSender;
25use hyperactor::mailbox::MessageEnvelope;
26use hyperactor::mailbox::OncePortReceiver;
27use hyperactor::mailbox::PortReceiver;
28use hyperactor::mailbox::Undeliverable;
29use hyperactor::mailbox::monitored_return_handle;
30use hyperactor_config::Flattrs;
31use monarch_types::PickledPyObject;
32use monarch_types::py_global;
33use pyo3::IntoPyObjectExt;
34use pyo3::exceptions::PyEOFError;
35use pyo3::exceptions::PyRuntimeError;
36use pyo3::exceptions::PyValueError;
37use pyo3::prelude::*;
38use pyo3::types::PyTuple;
39use pyo3::types::PyType;
40use serde::Deserialize;
41use serde::Serialize;
42use typeuri::Named;
43
44use crate::actor::PythonMessage;
45use crate::actor::PythonMessageKind;
46use crate::context::PyInstance;
47use crate::proc::PyActorAddr;
48use crate::pytokio::PyPythonTask;
49use crate::pytokio::PythonTask;
50use crate::runtime::GilSite;
51use crate::runtime::monarch_with_gil;
52use crate::runtime::monarch_with_gil_blocking;
53
54#[derive(Clone, Debug)]
55#[pyclass(
56 name = "Mailbox",
57 module = "monarch._rust_bindings.monarch_hyperactor.mailbox"
58)]
59pub struct PyMailbox {
60 pub(super) inner: Mailbox,
61}
62
63impl PyMailbox {
64 pub fn get_inner(&self) -> &Mailbox {
65 &self.inner
66 }
67}
68
69#[pymethods]
70impl PyMailbox {
71 fn open_port<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
72 let (handle, receiver) = self.inner.open_port();
73 let handle = Py::new(py, PythonPortHandle { inner: handle })?;
74 let receiver = Py::new(
75 py,
76 PythonPortReceiver {
77 inner: Arc::new(tokio::sync::Mutex::new(receiver)),
78 },
79 )?;
80 PyTuple::new(py, vec![handle.into_any(), receiver.into_any()])
81 }
82
83 fn open_once_port<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
84 let (handle, receiver) = self.inner.open_once_port();
85 let handle = Py::new(
86 py,
87 PythonOncePortHandle {
88 inner: Some(handle),
89 },
90 )?;
91 let receiver = Py::new(
92 py,
93 PythonOncePortReceiver {
94 inner: Arc::new(std::sync::Mutex::new(Some(receiver))),
95 },
96 )?;
97 PyTuple::new(py, vec![handle.into_any(), receiver.into_any()])
98 }
99
100 fn open_accum_port<'py>(
101 &self,
102 py: Python<'py>,
103 accumulator: Py<PyAny>,
104 ) -> PyResult<Bound<'py, PyTuple>> {
105 let py_accumulator = PythonAccumulator::new(py, accumulator)?;
106 let (handle, receiver) = self.inner.open_accum_port(py_accumulator);
107 let handle = Py::new(py, PythonPortHandle { inner: handle })?;
108 let receiver = Py::new(
109 py,
110 PythonPortReceiver {
111 inner: Arc::new(tokio::sync::Mutex::new(receiver)),
112 },
113 )?;
114 PyTuple::new(py, vec![handle.into_any(), receiver.into_any()])
115 }
116
117 pub(super) fn post(&self, dest: &PyActorAddr, message: &PythonMessage) -> PyResult<()> {
118 let port_id = dest
119 .inner
120 .port_addr(hyperactor::Port::handler::<PythonMessage>());
121 let message = wirevalue::Any::serialize(message).map_err(|err| {
122 PyRuntimeError::new_err(format!(
123 "failed to serialize message ({:?}) to Any: {}",
124 message, err
125 ))
126 })?;
127 let envelope = MessageEnvelope::new(
128 self.inner.actor_addr().clone(),
129 port_id,
130 message,
131 Flattrs::new(),
132 );
133 let return_handle = self
134 .inner
135 .bound_return_handle()
136 .unwrap_or(monitored_return_handle());
137 self.inner.post(envelope, return_handle);
138 Ok(())
139 }
140
141 #[getter]
142 pub(super) fn actor_id(&self) -> PyActorAddr {
143 PyActorAddr {
144 inner: self.inner.actor_addr().clone(),
145 }
146 }
147
148 fn __repr__(&self) -> String {
149 format!("{:?}", self.inner)
150 }
151}
152
153#[pyclass(
154 frozen,
155 name = "PortId",
156 module = "monarch._rust_bindings.monarch_hyperactor.mailbox"
157)]
158#[derive(Clone)]
159pub struct PyPortId {
160 inner: hyperactor::PortAddr,
161}
162
163impl From<hyperactor::PortAddr> for PyPortId {
164 fn from(port_id: hyperactor::PortAddr) -> Self {
165 Self { inner: port_id }
166 }
167}
168
169impl From<PyPortId> for hyperactor::PortAddr {
170 fn from(port_id: PyPortId) -> Self {
171 port_id.inner
172 }
173}
174
175impl From<Mailbox> for PyMailbox {
176 fn from(inner: Mailbox) -> Self {
177 PyMailbox { inner }
178 }
179}
180
181#[pymethods]
182impl PyPortId {
183 #[new]
184 #[pyo3(signature = (*, actor_id, port))]
185 fn new(actor_id: &PyActorAddr, port: u64) -> Self {
186 Self {
187 inner: actor_id.inner.port_addr(port.into()),
188 }
189 }
190
191 #[staticmethod]
192 fn from_string(port_id: &str) -> PyResult<Self> {
193 Ok(Self {
194 inner: port_id.parse().map_err(|e| {
195 PyValueError::new_err(format!("Failed to parse port id '{}': {}", port_id, e))
196 })?,
197 })
198 }
199
200 #[getter]
201 fn actor_id(&self) -> PyActorAddr {
202 PyActorAddr {
203 inner: self.inner.actor_addr(),
204 }
205 }
206
207 #[getter]
208 fn index(&self) -> u64 {
209 self.inner.index()
210 }
211
212 fn __repr__(&self) -> String {
213 self.inner.to_string()
214 }
215
216 fn __hash__(&self) -> u64 {
217 let mut hasher = DefaultHasher::new();
218 self.inner.to_string().hash(&mut hasher);
219 hasher.finish()
220 }
221
222 fn __eq__(&self, other: &Bound<'_, PyAny>) -> PyResult<bool> {
223 if let Ok(other) = other.extract::<PyPortId>() {
224 Ok(self.inner == other.inner)
225 } else {
226 Ok(false)
227 }
228 }
229
230 fn __reduce__<'py>(slf: &Bound<'py, Self>) -> PyResult<(Bound<'py, PyAny>, (String,))> {
231 Ok((slf.getattr("from_string")?, (slf.borrow().__repr__(),)))
232 }
233}
234
235impl std::fmt::Debug for PyPortId {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 self.inner.fmt(f)
238 }
239}
240
241#[derive(Clone, Debug)]
242#[pyclass(
243 name = "PortHandle",
244 module = "monarch._rust_bindings.monarch_hyperactor.mailbox"
245)]
246pub(crate) struct PythonPortHandle {
247 inner: PortHandle<PythonMessage>,
248}
249
250#[pymethods]
251impl PythonPortHandle {
252 fn send(&self, instance: &PyInstance, message: PythonMessage) -> PyResult<()> {
253 self.inner.post(instance.deref(), message);
254 Ok(())
255 }
256
257 fn bind(&self) -> PythonPortRef {
258 PythonPortRef {
259 inner: self.inner.bind(),
260 }
261 }
262}
263
264#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
265#[pyclass(
266 name = "PortRef",
267 module = "monarch._rust_bindings.monarch_hyperactor.mailbox"
268)]
269pub struct PythonPortRef {
270 pub(crate) inner: hyperactor::PortRef<PythonMessage>,
271}
272
273#[pymethods]
274impl PythonPortRef {
275 #[new]
276 fn new(port: PyPortId) -> Self {
277 Self {
278 inner: hyperactor::PortRef::attest(port.inner),
279 }
280 }
281 fn __reduce__(slf: Bound<'_, PythonPortRef>) -> PyResult<(Bound<'_, PyType>, (PyPortId,))> {
282 let id: PyPortId = (*slf.borrow()).inner.port_addr().clone().into();
283 Ok((slf.get_type(), (id,)))
284 }
285
286 fn send(&self, instance: &PyInstance, message: PythonMessage) -> PyResult<()> {
287 self.inner.post(instance.deref(), message);
288 Ok(())
289 }
290
291 fn __repr__(&self) -> String {
292 self.inner.to_string()
293 }
294
295 #[getter]
296 fn port_id(&self) -> PyResult<PyPortId> {
297 Ok(self.inner.port_addr().clone().into())
298 }
299
300 #[getter]
301 fn get_return_undeliverable(&self) -> bool {
302 self.inner.get_return_undeliverable()
303 }
304
305 #[setter]
306 fn set_return_undeliverable(&mut self, return_undeliverable: bool) {
307 self.inner.return_undeliverable(return_undeliverable);
308 }
309}
310
311impl From<hyperactor::PortRef<PythonMessage>> for PythonPortRef {
312 fn from(port_ref: hyperactor::PortRef<PythonMessage>) -> Self {
313 Self { inner: port_ref }
314 }
315}
316
317#[derive(Debug)]
318#[pyclass(
319 name = "PortReceiver",
320 module = "monarch._rust_bindings.monarch_hyperactor.mailbox"
321)]
322pub(super) struct PythonPortReceiver {
323 inner: Arc<tokio::sync::Mutex<PortReceiver<PythonMessage>>>,
324}
325
326async fn recv_async(
327 receiver: Arc<tokio::sync::Mutex<PortReceiver<PythonMessage>>>,
328) -> PyResult<Py<PyAny>> {
329 let message = receiver
330 .lock()
331 .await
332 .recv()
333 .await
334 .map_err(|err| PyErr::new::<PyEOFError, _>(format!("Port closed: {}", err)))?;
335
336 monarch_with_gil(GilSite::ReplyConvert, |py| message.into_py_any(py)).await
337}
338
339#[pymethods]
340impl PythonPortReceiver {
341 fn recv_task(&mut self) -> PyResult<PyPythonTask> {
342 let receiver = self.inner.clone();
343 Ok(PythonTask::new(recv_async(receiver))?.into())
344 }
345}
346
347impl PythonPortReceiver {
348 #[allow(dead_code)]
349 pub(super) fn inner(&self) -> Arc<tokio::sync::Mutex<PortReceiver<PythonMessage>>> {
350 Arc::clone(&self.inner)
351 }
352}
353
354#[derive(Debug)]
355#[pyclass(
356 name = "UndeliverableMessageEnvelope",
357 module = "monarch._rust_bindings.monarch_hyperactor.mailbox"
358)]
359pub(crate) struct PythonUndeliverableMessageEnvelope {
360 pub(crate) inner: Option<Undeliverable<MessageEnvelope>>,
361}
362
363impl PythonUndeliverableMessageEnvelope {
364 fn inner(&self) -> PyResult<&Undeliverable<MessageEnvelope>> {
365 self.inner.as_ref().ok_or_else(|| {
366 PyErr::new::<PyRuntimeError, _>(
367 "PythonUndeliverableMessageEnvelope was already consumed",
368 )
369 })
370 }
371
372 pub(crate) fn take(&mut self) -> anyhow::Result<Undeliverable<MessageEnvelope>> {
373 self.inner.take().ok_or_else(|| {
374 anyhow::anyhow!("PythonUndeliverableMessageEnvelope was already consumed")
375 })
376 }
377}
378
379#[pymethods]
380impl PythonUndeliverableMessageEnvelope {
381 fn __repr__(&self) -> PyResult<String> {
382 let inner = self.inner()?;
383 let Some(envelope) = inner.as_message() else {
384 return Ok("UndeliverableMessageEnvelope(lost)".to_string());
385 };
386 Ok(format!(
387 "UndeliverableMessageEnvelope(sender={}, dest={}, error={})",
388 envelope.sender(),
389 envelope.dest(),
390 self.error_msg()?
391 ))
392 }
393
394 fn sender(&self) -> PyResult<PyActorAddr> {
395 let envelope = self.inner()?.as_message().ok_or_else(|| {
396 PyErr::new::<PyRuntimeError, _>("undeliverable message reports do not have an envelope")
397 })?;
398 Ok(PyActorAddr {
399 inner: envelope.sender().clone(),
400 })
401 }
402
403 fn dest(&self) -> PyResult<PyPortId> {
404 let envelope = self.inner()?.as_message().ok_or_else(|| {
405 PyErr::new::<PyRuntimeError, _>("undeliverable message reports do not have an envelope")
406 })?;
407 let port_id: hyperactor::PortAddr = envelope.dest().clone();
408 Ok(port_id.into())
409 }
410
411 fn error_msg(&self) -> PyResult<String> {
412 match self.inner()? {
413 Undeliverable::Returned(envelope) => {
414 Ok(envelope.error_msg().unwrap_or_else(|| "None".to_string()))
415 }
416 Undeliverable::Report(report) => Ok(report.error_msg().unwrap_or_default()),
417 }
418 }
419}
420
421#[derive(Debug)]
422#[pyclass(
423 name = "OncePortHandle",
424 module = "monarch._rust_bindings.monarch_hyperactor.mailbox"
425)]
426pub(super) struct PythonOncePortHandle {
427 inner: Option<OncePortHandle<PythonMessage>>,
428}
429
430#[pymethods]
431impl PythonOncePortHandle {
432 fn send(&mut self, instance: &PyInstance, message: PythonMessage) -> PyResult<()> {
433 let Some(port) = self.inner.take() else {
434 return Err(PyErr::new::<PyValueError, _>("OncePort is already used"));
435 };
436 port.post(instance.deref(), message);
437 Ok(())
438 }
439
440 fn bind(&mut self) -> PyResult<PythonOncePortRef> {
441 let Some(port) = self.inner.take() else {
442 return Err(PyErr::new::<PyValueError, _>("OncePort is already used"));
443 };
444 Ok(PythonOncePortRef {
445 inner: Some(port.bind()),
446 })
447 }
448}
449
450#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
451#[pyclass(
452 name = "OncePortRef",
453 module = "monarch._rust_bindings.monarch_hyperactor.mailbox"
454)]
455pub struct PythonOncePortRef {
456 pub(crate) inner: Option<hyperactor::OncePortRef<PythonMessage>>,
457}
458
459#[pymethods]
460impl PythonOncePortRef {
461 #[new]
462 fn new(port: Option<PyPortId>) -> Self {
463 Self {
464 inner: port.map(|port| hyperactor::PortRef::attest(port.inner).into_once()),
465 }
466 }
467 fn __reduce__(
468 slf: Bound<'_, PythonOncePortRef>,
469 ) -> PyResult<(Bound<'_, PyType>, (Option<PyPortId>,))> {
470 let id: Option<PyPortId> = (*slf.borrow())
471 .inner
472 .as_ref()
473 .map(|x: &hyperactor::OncePortRef<PythonMessage>| x.port_addr().clone().into());
474 Ok((slf.get_type(), (id,)))
475 }
476
477 fn send(&mut self, instance: &PyInstance, message: PythonMessage) -> PyResult<()> {
478 let Some(port_ref) = self.inner.take() else {
479 return Err(PyErr::new::<PyValueError, _>("OncePortRef is already used"));
480 };
481 let port_ref: hyperactor::OncePortRef<PythonMessage> = port_ref;
482 port_ref.post(instance.deref(), message);
483 Ok(())
484 }
485
486 fn __repr__(&self) -> String {
487 self.inner.as_ref().map_or(
488 "OncePortRef is already used".to_string(),
489 |r: &hyperactor::OncePortRef<PythonMessage>| r.to_string(),
490 )
491 }
492
493 #[getter]
494 fn port_id(&self) -> PyResult<PyPortId> {
495 Ok(self.inner.as_ref().unwrap().port_addr().clone().into())
496 }
497
498 #[getter]
499 fn get_return_undeliverable(&self) -> bool {
500 self.inner.as_ref().unwrap().get_return_undeliverable()
501 }
502
503 #[setter]
504 fn set_return_undeliverable(&mut self, return_undeliverable: bool) {
505 if let Some(ref mut inner) = self.inner {
506 inner.return_undeliverable(return_undeliverable);
507 }
508 }
509}
510
511impl From<hyperactor::OncePortRef<PythonMessage>> for PythonOncePortRef {
512 fn from(port_ref: hyperactor::OncePortRef<PythonMessage>) -> Self {
513 Self {
514 inner: Some(port_ref),
515 }
516 }
517}
518
519#[pyclass(
520 name = "OncePortReceiver",
521 module = "monarch._rust_bindings.monarch_hyperactor.mailbox"
522)]
523pub(super) struct PythonOncePortReceiver {
524 inner: Arc<std::sync::Mutex<Option<OncePortReceiver<PythonMessage>>>>,
525}
526
527#[pymethods]
528impl PythonOncePortReceiver {
529 fn recv_task(&mut self) -> PyResult<PyPythonTask> {
530 let Some(receiver) = self.inner.lock().unwrap().take() else {
531 return Err(PyErr::new::<PyValueError, _>("OncePort is already used"));
532 };
533 let fut = async move {
534 let message = receiver
535 .recv()
536 .await
537 .map_err(|err| PyErr::new::<PyEOFError, _>(format!("Port closed: {}", err)))?;
538
539 monarch_with_gil(GilSite::ReplyConvert, |py| message.into_py_any(py)).await
540 };
541 Ok(PythonTask::new(fut)?.into())
542 }
543}
544
545impl PythonOncePortReceiver {
546 #[allow(dead_code)]
547 pub(super) fn inner(&self) -> Arc<std::sync::Mutex<Option<OncePortReceiver<PythonMessage>>>> {
548 Arc::clone(&self.inner)
549 }
550}
551
552#[derive(
553 Clone,
554 Serialize,
555 Deserialize,
556 Named,
557 PartialEq,
558 FromPyObject,
559 IntoPyObject,
560 Debug
561)]
562pub enum EitherPortRef {
563 Unbounded(PythonPortRef),
564 Once(PythonOncePortRef),
565}
566
567impl EitherPortRef {
568 pub fn get_return_undeliverable(&self) -> bool {
569 match self {
570 EitherPortRef::Unbounded(port_ref) => port_ref.inner.get_return_undeliverable(),
571 EitherPortRef::Once(once_port_ref) => once_port_ref.inner.as_ref().is_some_and(
572 |r: &hyperactor::OncePortRef<PythonMessage>| r.get_return_undeliverable(),
573 ),
574 }
575 }
576
577 pub fn set_return_undeliverable(&mut self, return_undeliverable: bool) {
578 match self {
579 EitherPortRef::Unbounded(port_ref) => {
580 port_ref.inner.return_undeliverable(return_undeliverable);
581 }
582 EitherPortRef::Once(once_port_ref) => {
583 if let Some(ref mut inner) = once_port_ref.inner {
584 inner.return_undeliverable(return_undeliverable);
585 }
586 }
587 }
588 }
589
590 pub fn post(
593 &mut self,
594 cx: &impl hyperactor::context::Actor,
595 message: crate::actor::PythonMessage,
596 ) -> anyhow::Result<()> {
597 match self {
598 EitherPortRef::Unbounded(port_ref) => port_ref.inner.post(cx, message),
599 EitherPortRef::Once(once_port_ref) => {
600 let port = once_port_ref
601 .inner
602 .take()
603 .ok_or_else(|| anyhow::anyhow!("OncePortRef already used"))?;
604 port.post(cx, message);
605 }
606 }
607 Ok(())
608 }
609
610 pub fn post_with_headers(
615 &mut self,
616 cx: &impl hyperactor::context::Actor,
617 headers: hyperactor_config::Flattrs,
618 message: crate::actor::PythonMessage,
619 ) -> anyhow::Result<()> {
620 match self {
621 EitherPortRef::Unbounded(port_ref) => {
622 port_ref.inner.post_with_headers(cx, headers, message)
623 }
624 EitherPortRef::Once(once_port_ref) => {
625 let port = once_port_ref
626 .inner
627 .take()
628 .ok_or_else(|| anyhow::anyhow!("OncePortRef already used"))?;
629 port.post_with_headers(cx, headers, message);
630 }
631 }
632 Ok(())
633 }
634}
635
636#[derive(Debug, Named)]
637struct PythonReducer(Py<PyAny>);
638
639impl PythonReducer {
640 fn new(params: Option<wirevalue::Any>) -> anyhow::Result<Self> {
641 let p = params.ok_or_else(|| anyhow::anyhow!("params cannot be None"))?;
642 let obj: PickledPyObject = p.deserialized()?;
643 Ok(monarch_with_gil_blocking(
644 GilSite::Reducer,
645 |py: Python<'_>| -> PyResult<Self> {
646 let unpickled = obj.unpickle(py)?;
647 Ok(Self(unpickled.unbind()))
648 },
649 )?)
650 }
651}
652
653impl CommReducer for PythonReducer {
654 type Update = PythonMessage;
655
656 fn reduce(&self, left: Self::Update, right: Self::Update) -> anyhow::Result<Self::Update> {
657 monarch_with_gil_blocking(
658 GilSite::Reducer,
659 |py: Python<'_>| -> PyResult<PythonMessage> {
660 let result = self.0.call(py, (left, right), None)?;
661 result.extract::<PythonMessage>(py)
662 },
663 )
664 .map_err(Into::into)
665 }
666}
667
668struct PythonAccumulator {
669 accumulator: Py<PyAny>,
670 reducer: Option<wirevalue::Any>,
671}
672
673impl PythonAccumulator {
674 fn new(py: Python<'_>, accumulator: Py<PyAny>) -> PyResult<Self> {
675 let py_reducer = accumulator.getattr(py, "reducer")?;
676 let reducer: Option<wirevalue::Any> = if py_reducer.is_none(py) {
677 None
678 } else {
679 let pickled = PickledPyObject::cloudpickle(py_reducer.bind(py))?;
680 Some(
681 wirevalue::Any::serialize(&pickled)
682 .map_err(|e| PyRuntimeError::new_err(e.to_string()))?,
683 )
684 };
685
686 Ok(Self {
687 accumulator,
688 reducer,
689 })
690 }
691}
692
693impl Accumulator for PythonAccumulator {
694 type State = PythonMessage;
695 type Update = PythonMessage;
696
697 fn accumulate(&self, state: &mut Self::State, update: Self::Update) -> anyhow::Result<()> {
698 monarch_with_gil_blocking(GilSite::Accumulate, |py: Python<'_>| -> PyResult<()> {
699 if matches!(state.kind, PythonMessageKind::Uninit {}) {
701 *state = self
702 .accumulator
703 .getattr(py, "initial_state")?
704 .extract::<PythonMessage>(py)?;
705 }
706
707 let old_state = state.clone();
710 let result = self.accumulator.call(py, (old_state, update), None)?;
711 *state = result.extract::<PythonMessage>(py)?;
712 Ok(())
713 })
714 .map_err(Into::into)
715 }
716
717 fn reducer_spec(&self) -> Option<ReducerSpec> {
718 self.reducer.as_ref().map(|r| ReducerSpec {
719 typehash: <PythonReducer as Named>::typehash(),
720 builder_params: Some(r.clone()),
721 })
722 }
723}
724
725inventory::submit! {
726 ReducerFactory {
727 typehash_f: <PythonReducer as Named>::typehash,
728 builder_f: |params| Ok(Box::new(PythonReducer::new(params)?)),
729 }
730}
731
732py_global!(point, "monarch._src.actor.actor_mesh", "Point");
733
734pub fn register_python_bindings(hyperactor_mod: &Bound<'_, PyModule>) -> PyResult<()> {
735 hyperactor_mod.add_class::<PyMailbox>()?;
736 hyperactor_mod.add_class::<PyPortId>()?;
737 hyperactor_mod.add_class::<PythonPortHandle>()?;
738 hyperactor_mod.add_class::<PythonPortRef>()?;
739 hyperactor_mod.add_class::<PythonPortReceiver>()?;
740 hyperactor_mod.add_class::<PythonOncePortHandle>()?;
741 hyperactor_mod.add_class::<PythonOncePortRef>()?;
742 hyperactor_mod.add_class::<PythonOncePortReceiver>()?;
743 hyperactor_mod.add_class::<PythonUndeliverableMessageEnvelope>()?;
744 Ok(())
745}