Skip to main content

monarch_hyperactor/
mailbox.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9use 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    /// Post a message through this port reference.
591    /// The message is first resolved for any pending pickle state before sending.
592    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    /// Post a message through this port reference with
611    /// caller-supplied envelope headers. Delegates to the underlying
612    /// `PortRef::post_with_headers` /
613    /// `OncePortRef::post_with_headers`.
614    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            // Initialize state if it is empty.
700            if matches!(state.kind, PythonMessageKind::Uninit {}) {
701                *state = self
702                    .accumulator
703                    .getattr(py, "initial_state")?
704                    .extract::<PythonMessage>(py)?;
705            }
706
707            // TODO(pzhang) Make accumulate consumes state and update, and returns
708            // a new state. That will avoid this clone.
709            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}