monarch_hyperactor/
namespace.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
9//! Python bindings for read-only namespace operations.
10//!
11//! This module exposes only the read portion of the Namespace API to Python,
12//! allowing lookups but not registration or unregistration.
13
14use std::sync::Arc;
15
16use hyperactor_mesh::ActorMeshRef;
17use hyperactor_mesh::HostMeshRef;
18use hyperactor_mesh::ProcMeshRef;
19use hyperactor_mesh::namespace::InMemoryNamespace;
20use hyperactor_mesh::namespace::MeshKind;
21use hyperactor_mesh::namespace::Namespace;
22use hyperactor_mesh::namespace::NamespaceError;
23use pyo3::exceptions::PyKeyError;
24use pyo3::exceptions::PyRuntimeError;
25use pyo3::exceptions::PyValueError;
26use pyo3::prelude::*;
27
28use crate::actor::PythonActor;
29use crate::actor_mesh::PythonActorMeshImpl;
30use crate::host_mesh::PyHostMesh;
31use crate::proc_mesh::PyProcMesh;
32use crate::pytokio::PyPythonTask;
33
34/// Convert NamespaceError to PyErr.
35fn namespace_error_to_pyerr(e: NamespaceError) -> PyErr {
36    match e {
37        NamespaceError::NotFound(key) => PyKeyError::new_err(key),
38        NamespaceError::DeserializationError(msg) => PyValueError::new_err(msg),
39        _ => PyRuntimeError::new_err(e.to_string()),
40    }
41}
42
43/// The kind of mesh (host, proc, or actor).
44#[pyclass(
45    name = "MeshKind",
46    module = "monarch._rust_bindings.monarch_hyperactor.namespace",
47    eq
48)]
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub enum PyMeshKind {
51    Host,
52    Proc,
53    Actor,
54}
55
56#[pymethods]
57impl PyMeshKind {
58    /// MeshKind.Host
59    #[classattr]
60    const HOST: Self = PyMeshKind::Host;
61
62    /// MeshKind.Proc
63    #[classattr]
64    const PROC: Self = PyMeshKind::Proc;
65
66    /// MeshKind.Actor
67    #[classattr]
68    const ACTOR: Self = PyMeshKind::Actor;
69
70    fn __repr__(&self) -> &'static str {
71        match self {
72            PyMeshKind::Host => "MeshKind.Host",
73            PyMeshKind::Proc => "MeshKind.Proc",
74            PyMeshKind::Actor => "MeshKind.Actor",
75        }
76    }
77
78    fn __str__(&self) -> &'static str {
79        match self {
80            PyMeshKind::Host => "host",
81            PyMeshKind::Proc => "proc",
82            PyMeshKind::Actor => "actor",
83        }
84    }
85}
86
87impl From<PyMeshKind> for MeshKind {
88    fn from(kind: PyMeshKind) -> Self {
89        match kind {
90            PyMeshKind::Host => MeshKind::Host,
91            PyMeshKind::Proc => MeshKind::Proc,
92            PyMeshKind::Actor => MeshKind::Actor,
93        }
94    }
95}
96
97impl From<MeshKind> for PyMeshKind {
98    fn from(kind: MeshKind) -> Self {
99        match kind {
100            MeshKind::Host => PyMeshKind::Host,
101            MeshKind::Proc => PyMeshKind::Proc,
102            MeshKind::Actor => PyMeshKind::Actor,
103        }
104    }
105}
106
107/// A read-only namespace for looking up meshes.
108///
109/// This class only exposes read operations (get, contains) and does not
110/// allow registration or unregistration of meshes.
111#[pyclass(
112    name = "Namespace",
113    module = "monarch._rust_bindings.monarch_hyperactor.namespace"
114)]
115pub struct PyNamespace {
116    inner: Arc<InMemoryNamespace>,
117}
118
119impl PyNamespace {
120    /// Create a new PyNamespace from an InMemoryNamespace.
121    pub fn new(namespace: Arc<InMemoryNamespace>) -> Self {
122        Self { inner: namespace }
123    }
124}
125
126#[pymethods]
127impl PyNamespace {
128    /// Get the namespace name.
129    #[getter]
130    fn name(&self) -> &str {
131        self.inner.name()
132    }
133
134    /// Check if a mesh exists in the namespace.
135    ///
136    /// Args:
137    ///     kind: The mesh kind (MeshKind.Host, MeshKind.Proc, or MeshKind.Actor)
138    ///     name: The mesh name
139    ///
140    /// Returns:
141    ///     True if the mesh exists, False otherwise
142    fn contains(&self, kind: PyMeshKind, name: String) -> PyResult<PyPythonTask> {
143        let ns = self.inner.clone();
144        match kind {
145            PyMeshKind::Host => PyPythonTask::new(async move {
146                ns.contains::<HostMeshRef>(&name)
147                    .await
148                    .map_err(namespace_error_to_pyerr)
149            }),
150            PyMeshKind::Proc => PyPythonTask::new(async move {
151                ns.contains::<ProcMeshRef>(&name)
152                    .await
153                    .map_err(namespace_error_to_pyerr)
154            }),
155            PyMeshKind::Actor => PyPythonTask::new(async move {
156                ns.contains::<ActorMeshRef<PythonActor>>(&name)
157                    .await
158                    .map_err(namespace_error_to_pyerr)
159            }),
160        }
161    }
162
163    /// Get a mesh from the namespace.
164    ///
165    /// Args:
166    ///     kind: The mesh kind (MeshKind.Host, MeshKind.Proc, or MeshKind.Actor)
167    ///     name: The mesh name
168    ///
169    /// Returns:
170    ///     HostMesh, ProcMesh, or ActorMesh depending on kind
171    ///
172    /// Raises:
173    ///     KeyError: If the mesh is not found
174    fn get(&self, kind: PyMeshKind, name: String) -> PyResult<PyPythonTask> {
175        let ns = self.inner.clone();
176
177        match kind {
178            PyMeshKind::Host => PyPythonTask::new(async move {
179                let mesh: HostMeshRef = ns.get(&name).await.map_err(namespace_error_to_pyerr)?;
180                Ok(PyHostMesh::new_ref(mesh))
181            }),
182            PyMeshKind::Proc => PyPythonTask::new(async move {
183                let mesh: ProcMeshRef = ns.get(&name).await.map_err(namespace_error_to_pyerr)?;
184                Ok(PyProcMesh::new_ref(mesh))
185            }),
186            PyMeshKind::Actor => PyPythonTask::new(async move {
187                let mesh: ActorMeshRef<PythonActor> =
188                    ns.get(&name).await.map_err(namespace_error_to_pyerr)?;
189                Ok(PythonActorMeshImpl::new_ref(mesh))
190            }),
191        }
192    }
193
194    fn __repr__(&self) -> String {
195        format!("Namespace(name='{}')", self.inner.name())
196    }
197}
198
199/// Create an in-memory namespace for testing.
200///
201/// Args:
202///     name: The namespace name (e.g., "my.namespace")
203///
204/// Returns:
205///     A Namespace instance backed by in-memory storage
206#[pyfunction]
207fn create_in_memory_namespace(name: String) -> PyNamespace {
208    PyNamespace::new(Arc::new(InMemoryNamespace::new(name)))
209}
210
211pub fn register_python_bindings(module: &Bound<'_, PyModule>) -> PyResult<()> {
212    module.add_class::<PyMeshKind>()?;
213    module.add_class::<PyNamespace>()?;
214    module.add_function(wrap_pyfunction!(create_in_memory_namespace, module)?)?;
215    Ok(())
216}