66 * LICENSE file in the root directory of this source tree.
77 */
88
9- //! Configuration for Monarch Hyperactor.
9+ //! Configuration bridge for Monarch Hyperactor.
1010//!
11- //! This module provides monarch-specific configuration attributes that extend
12- //! the base hyperactor configuration system.
11+ //! This module defines Monarch-specific configuration keys and their
12+ //! Python bindings on top of the core `hyperactor::config::global`
13+ //! system. It wires those keys into the layered config and exposes
14+ //! Python-facing helpers such as `configure(...)`,
15+ //! `get_global_config()`, `get_runtime_config()`, and
16+ //! `clear_runtime_config()`, which together implement the "Runtime"
17+ //! configuration layer used by the Monarch Python API.
1318
1419use std:: collections:: HashMap ;
1520use std:: fmt:: Debug ;
@@ -79,10 +84,14 @@ static TYPEHASH_TO_INFO: std::sync::LazyLock<HashMap<u64, &'static PythonConfigT
7984 . collect ( )
8085 } ) ;
8186
82- /// Given a key, get the associated `T`-typed value from the global config, then
83- /// convert it to a `P`-typed object that can be converted to PyObject, and
84- /// return that PyObject.
85- fn get_global_config < ' py , P , T > (
87+ /// Fetch a config value from the layered global config and convert it
88+ /// to Python.
89+ ///
90+ /// Looks up `key` in the full configuration
91+ /// (Defaults/File/Env/Runtime/ TestOverride), clones the `T`-typed
92+ /// value if present, converts it to `P`, then into a `PyObject`. If
93+ /// the key is unset in all layers, returns `Ok(None)`.
94+ fn get_global_config_py < ' py , P , T > (
8695 py : Python < ' py > ,
8796 key : & ' static dyn ErasedKey ,
8897) -> PyResult < Option < PyObject > >
@@ -91,8 +100,15 @@ where
91100 P : IntoPyObjectExt < ' py > ,
92101 PyErr : From < <T as TryInto < P > >:: Error > ,
93102{
94- // Well, it can't fail unless there's a bug in the code in this file.
95- let key = key. downcast_ref :: < T > ( ) . expect ( "cannot fail" ) ;
103+ // The error case should never happen. If somehow it ever does
104+ // we'll represent "our typing assumptions are wrong" by returning
105+ // a PyTypeError rather than a panic.
106+ let key = key. downcast_ref :: < T > ( ) . ok_or_else ( || {
107+ PyTypeError :: new_err ( format ! (
108+ "internal config type mismatch for key `{}`" ,
109+ key. name( ) ,
110+ ) )
111+ } ) ?;
96112 let val: Option < P > = hyperactor:: config:: global:: try_get_cloned ( key. clone ( ) )
97113 . map ( |v| v. try_into ( ) )
98114 . transpose ( ) ?;
@@ -102,11 +118,12 @@ where
102118/// Fetch a config value from the **Runtime** layer only and convert
103119/// it to Python.
104120///
105- /// This mirrors [`get_global_config`] but restricts the lookup to the
106- /// `Source::Runtime` layer (ignoring TestOverride/Env/File/defaults).
107- /// If the key has a runtime override, it is cloned as `T`, converted
108- /// to `P`, then to a `PyObject`; otherwise `Ok(None)` is returned.
109- fn get_runtime_config < ' py , P , T > (
121+ /// This mirrors [`get_global_config_py`] but restricts the lookup to
122+ /// the `Source::Runtime` layer (ignoring
123+ /// TestOverride/Env/File/defaults). If the key has a runtime
124+ /// override, it is cloned as `T`, converted to `P`, then to a
125+ /// `PyObject`; otherwise `Ok(None)` is returned.
126+ fn get_runtime_config_py < ' py , P , T > (
110127 py : Python < ' py > ,
111128 key : & ' static dyn ErasedKey ,
112129) -> PyResult < Option < PyObject > >
@@ -125,8 +142,16 @@ where
125142 val. map ( |v| v. into_py_any ( py) ) . transpose ( )
126143}
127144
128- /// Note that this function writes strictly into the `Runtime` layer.
129- fn set_runtime_config < T : AttrValue + Debug > ( key : & ' static dyn ErasedKey , value : T ) -> PyResult < ( ) > {
145+ /// Store a Python-provided config value into the **Runtime** layer.
146+ ///
147+ /// This is the write-path for the "Python configuration layer": it
148+ /// takes a typed key/value and merges it into `Source::Runtime` via
149+ /// `create_or_merge`. No other layers
150+ /// (Env/File/TestOverride/Defaults) are affected.
151+ fn set_runtime_config_py < T : AttrValue + Debug > (
152+ key : & ' static dyn ErasedKey ,
153+ value : T ,
154+ ) -> PyResult < ( ) > {
130155 // Again, can't fail unless there's a bug in the code in this file.
131156 let key = key. downcast_ref ( ) . expect ( "cannot fail" ) ;
132157 let mut attrs = Attrs :: new ( ) ;
@@ -135,8 +160,22 @@ fn set_runtime_config<T: AttrValue + Debug>(key: &'static dyn ErasedKey, value:
135160 Ok ( ( ) )
136161}
137162
138- fn set_runtime_config_from_py_obj ( py : Python < ' _ > , name : & str , val : PyObject ) -> PyResult < ( ) > {
139- // Get the `ErasedKey` from the kwarg `name` passed to `monarch.configure(...)`.
163+ /// Bridge a single Python kwarg into a typed Runtime config update.
164+ ///
165+ /// This is the write-path behind `configure(**kwargs)`:
166+ /// - `configure(...)` calls this for each `(name, value)` pair,
167+ /// - we resolve `name` to an erased config key via `KEY_BY_NAME`,
168+ /// - we use the key's `typehash` to find the registered
169+ /// `PythonConfigTypeInfo`,
170+ /// - and finally call its `set_runtime_config` closure, which
171+ /// downcasts the value and forwards to `set_runtime_config_py` to
172+ /// write into the `Source::Runtime` layer.
173+ ///
174+ /// Unknown keys or keys without a Python conversion registered result
175+ /// in a `ValueError` / `TypeError` back to Python.
176+ fn configure_kwarg ( py : Python < ' _ > , name : & str , val : PyObject ) -> PyResult < ( ) > {
177+ // Get the `ErasedKey` from the kwarg `name` passed to
178+ // `monarch.configure(...)`.
140179 let key = match KEY_BY_NAME . get ( name) {
141180 None => {
142181 return Err ( PyValueError :: new_err ( format ! (
@@ -147,8 +186,9 @@ fn set_runtime_config_from_py_obj(py: Python<'_>, name: &str, val: PyObject) ->
147186 Some ( key) => * key,
148187 } ;
149188
150- // Using the typehash from the erased key, get/call the function that can downcast
151- // the key and set the value on the global config.
189+ // Using the typehash from the erased key, get/call the function
190+ // that can downcast the key and set the value on the global
191+ // config.
152192 match TYPEHASH_TO_INFO . get ( & key. typehash ( ) ) {
153193 None => Err ( PyTypeError :: new_err ( format ! (
154194 "configuration key `{}` has type `{}`, but configuring with values of this type from Python is not supported." ,
@@ -159,22 +199,47 @@ fn set_runtime_config_from_py_obj(py: Python<'_>, name: &str, val: PyObject) ->
159199 }
160200}
161201
162- /// Struct to associate a typehash with functions for getting/setting
163- /// values in the global config with keys of type `Key<T>`, where
164- /// `T::typehash() == PythonConfigTypeInfo::typehash()`.
202+ /// Per-type adapter for the Python config bridge.
203+ ///
204+ /// Each `PythonConfigTypeInfo` provides type-specific get/set logic
205+ /// for a particular `Key<T>` via the type-erased `ErasedKey`
206+ /// interface.
207+ ///
208+ /// Since we only have `&'static dyn ErasedKey` at runtime (we don't
209+ /// know `T`), we use **type erasure with recovery via function
210+ /// pointers**: the `declare_py_config_type!` macro bakes the concrete
211+ /// type `T` into each function pointer at compile time, allowing
212+ /// runtime dispatch to recover the type.
213+ ///
214+ /// Fields:
215+ /// - `typehash`: Identifies the underlying `T` for runtime lookup
216+ /// - `set_global_config`: Knows how to extract `PyObject` as `T` and
217+ /// write to Runtime layer
218+ /// - `get_global_config`: Reads `T` from merged config (all layers)
219+ /// and converts to `PyObject`
220+ /// - `get_runtime_config`: Reads `T` from Runtime layer only and
221+ /// converts to `PyObject`
222+ ///
223+ /// Instances are registered via `inventory` and collected into
224+ /// `TYPEHASH_TO_INFO`, enabling dynamic dispatch by typehash in
225+ /// `configure()` and `get_*_config()`.
165226struct PythonConfigTypeInfo {
227+ /// Identifies the underlying `T` (matches `T::typehash()`).
166228 typehash : fn ( ) -> u64 ,
167-
229+ /// Read this key from the merged layered config into a PyObject.
168230 get_global_config :
169231 fn ( py : Python < ' _ > , key : & ' static dyn ErasedKey ) -> PyResult < Option < PyObject > > ,
170-
232+ /// Write a Python value into the Runtime layer for this key.
171233 set_runtime_config :
172234 fn ( py : Python < ' _ > , key : & ' static dyn ErasedKey , val : PyObject ) -> PyResult < ( ) > ,
173-
235+ /// Read this key from the Runtime layer into a PyObject.
174236 get_runtime_config :
175237 fn ( py : Python < ' _ > , key : & ' static dyn ErasedKey ) -> PyResult < Option < PyObject > > ,
176238}
177239
240+ // Collect all `PythonConfigTypeInfo` instances registered by
241+ // `declare_py_config_type!`. These are later gathered into
242+ // `TYPEHASH_TO_INFO` via `inventory::iter()`.
178243inventory:: collect!( PythonConfigTypeInfo ) ;
179244
180245/// Macro to declare that keys of this type can be configured
@@ -197,13 +262,13 @@ macro_rules! declare_py_config_type {
197262 "invalid value `{}` for configuration key `{}` ({})" ,
198263 val, key. name( ) , err
199264 ) ) ) ?;
200- set_runtime_config ( key, val)
265+ set_runtime_config_py ( key, val)
201266 } ,
202267 get_global_config: |py, key| {
203- get_global_config :: <$ty, $ty>( py, key)
268+ get_global_config_py :: <$ty, $ty>( py, key)
204269 } ,
205270 get_runtime_config: |py, key| {
206- get_runtime_config :: <$ty, $ty>( py, key)
271+ get_runtime_config_py :: <$ty, $ty>( py, key)
207272 }
208273 }
209274 }
@@ -220,13 +285,13 @@ macro_rules! declare_py_config_type {
220285 "invalid value `{}` for configuration key `{}` ({})" ,
221286 val, key. name( ) , err
222287 ) ) ) ?. into( ) ;
223- set_runtime_config ( key, val)
288+ set_runtime_config_py ( key, val)
224289 } ,
225290 get_global_config: |py, key| {
226- get_global_config :: <$py_ty, $ty>( py, key)
291+ get_global_config_py :: <$py_ty, $ty>( py, key)
227292 } ,
228293 get_runtime_config: |py, key| {
229- get_runtime_config :: <$py_ty, $ty>( py, key)
294+ get_runtime_config_py :: <$py_ty, $ty>( py, key)
230295 }
231296 }
232297 }
@@ -239,29 +304,48 @@ declare_py_config_type!(
239304 i8 , i16 , i32 , i64 , u8 , u16 , u32 , u64 , usize , f32 , f64 , bool , String
240305) ;
241306
242- /// Iterate over each key-value pair. Attempt to retrieve the `Key<T>`
243- /// associated with the key and convert the value to `T`, then set
244- /// them on the global config. The association between kwarg and
245- /// `Key<T>` is specified using the `CONFIG` meta-attribute.
307+ /// Python entrypoint for `monarch_hyperactor.config.configure(...)`.
308+ ///
309+ /// This takes the keyword arguments passed from Python, resolves each
310+ /// kwarg name to a typed config key via `KEY_BY_NAME` (populated from
311+ /// `@meta(CONFIG = ConfigAttr { py_name: Some(...), .. })`), and then
312+ /// uses `configure_kwarg` to downcast the value and write it into the
313+ /// **Runtime** configuration layer.
314+ ///
315+ /// In other words, this is the write-path from `configure(**kwargs)`
316+ /// into `Source::Runtime`; other layers
317+ /// (Env/File/TestOverride/Defaults) are untouched.
318+ ///
319+ /// The name `configure(...)` is historical – conceptually this is
320+ /// `set_runtime_config(...)` for the Python-owned Runtime layer, but
321+ /// we keep the shorter name for API stability.
246322#[ pyfunction]
247323#[ pyo3( signature = ( * * kwargs) ) ]
248324fn configure ( py : Python < ' _ > , kwargs : Option < HashMap < String , PyObject > > ) -> PyResult < ( ) > {
249325 kwargs
250326 . map ( |kwargs| {
251327 kwargs
252328 . into_iter ( )
253- . try_for_each ( |( key, val) | set_runtime_config_from_py_obj ( py, & key, val) )
329+ . try_for_each ( |( key, val) | configure_kwarg ( py, & key, val) )
254330 } )
255331 . transpose ( ) ?;
256332 Ok ( ( ) )
257333}
258334
259- /// For all attribute keys whose `@meta(CONFIG = ConfigAttr { py_name:
260- /// Some(...), .. })` specifies a kwarg name, return the current
261- /// associated value in the global config. Keys with no value in the
262- /// global config are omitted from the result.
335+ /// Return a snapshot of the current Hyperactor configuration for
336+ /// Python-exposed keys.
337+ ///
338+ /// Iterates over all attribute keys whose `@meta(CONFIG = ConfigAttr
339+ /// { py_name: Some(...), .. })` declares a Python kwarg name, looks
340+ /// up each key in the **layered** global config
341+ /// (Defaults/File/Env/Runtime/TestOverride), and, if set, converts
342+ /// the value to a `PyObject`.
343+ ///
344+ /// The result is a plain `HashMap` from kwarg name to value for all
345+ /// such keys that currently have a value in the global config; keys
346+ /// with no value in any layer are omitted.
263347#[ pyfunction]
264- fn get_configuration ( py : Python < ' _ > ) -> PyResult < HashMap < String , PyObject > > {
348+ fn get_global_config ( py : Python < ' _ > ) -> PyResult < HashMap < String , PyObject > > {
265349 KEY_BY_NAME
266350 . iter ( )
267351 . filter_map ( |( name, key) | match TYPEHASH_TO_INFO . get ( & key. typehash ( ) ) {
@@ -282,25 +366,25 @@ fn get_configuration(py: Python<'_>) -> PyResult<HashMap<String, PyObject>> {
282366/// `@meta(CONFIG = ConfigAttr { py_name: Some(...), .. })`) that are
283367/// currently set in the Runtime layer.
284368///
285- /// This is used by Python's `configured()` context manager to
369+ /// This can be used to implement a `configured()` context manager to
286370/// snapshot and restore the Runtime layer for composable, nested
287371/// configuration overrides:
288372///
289373/// ```python
290- /// prev = get_runtime_configuration ()
374+ /// prev = get_runtime_config ()
291375/// try:
292376/// configure(**overrides)
293- /// yield get_configuration ()
377+ /// yield get_global_config ()
294378/// finally:
295- /// clear_runtime_configuration ()
379+ /// clear_runtime_config ()
296380/// configure(**prev)
297381/// ```
298382///
299- /// Unlike `get_configuration ()`, which returns the merged view across
300- /// all layers (File, Env, Runtime, TestOverride), this returns only
301- /// what's explicitly set in the Runtime layer.
383+ /// Unlike `get_global_config ()`, which returns the merged view across
384+ /// all layers (File, Env, Runtime, TestOverride, defaults ), this
385+ /// returns only what's explicitly set in the Runtime layer.
302386#[ pyfunction]
303- fn get_runtime_configuration ( py : Python < ' _ > ) -> PyResult < HashMap < String , PyObject > > {
387+ fn get_runtime_config ( py : Python < ' _ > ) -> PyResult < HashMap < String , PyObject > > {
304388 KEY_BY_NAME
305389 . iter ( )
306390 . filter_map ( |( name, key) | match TYPEHASH_TO_INFO . get ( & key. typehash ( ) ) {
@@ -325,7 +409,7 @@ fn get_runtime_configuration(py: Python<'_>) -> PyResult<HashMap<String, PyObjec
325409/// to restore configuration state after applying temporary overrides.
326410/// Other layers (Env, File, TestOverride, defaults) are unaffected.
327411#[ pyfunction]
328- fn clear_runtime_configuration ( _py : Python < ' _ > ) -> PyResult < ( ) > {
412+ fn clear_runtime_config ( _py : Python < ' _ > ) -> PyResult < ( ) > {
329413 hyperactor:: config:: global:: clear ( Source :: Runtime ) ;
330414 Ok ( ( ) )
331415}
@@ -353,26 +437,26 @@ pub fn register_python_bindings(module: &Bound<'_, PyModule>) -> PyResult<()> {
353437 ) ?;
354438 module. add_function ( configure) ?;
355439
356- let get_configuration = wrap_pyfunction ! ( get_configuration , module) ?;
357- get_configuration . setattr (
440+ let get_global_config = wrap_pyfunction ! ( get_global_config , module) ?;
441+ get_global_config . setattr (
358442 "__module__" ,
359443 "monarch._rust_bindings.monarch_hyperactor.config" ,
360444 ) ?;
361- module. add_function ( get_configuration ) ?;
445+ module. add_function ( get_global_config ) ?;
362446
363- let get_runtime_configuration = wrap_pyfunction ! ( get_runtime_configuration , module) ?;
364- get_runtime_configuration . setattr (
447+ let get_runtime_config = wrap_pyfunction ! ( get_runtime_config , module) ?;
448+ get_runtime_config . setattr (
365449 "__module__" ,
366450 "monarch._rust_bindings.monarch_hyperactor.config" ,
367451 ) ?;
368- module. add_function ( get_runtime_configuration ) ?;
452+ module. add_function ( get_runtime_config ) ?;
369453
370- let clear_runtime_configuration = wrap_pyfunction ! ( clear_runtime_configuration , module) ?;
371- clear_runtime_configuration . setattr (
454+ let clear_runtime_config = wrap_pyfunction ! ( clear_runtime_config , module) ?;
455+ clear_runtime_config . setattr (
372456 "__module__" ,
373457 "monarch._rust_bindings.monarch_hyperactor.config" ,
374458 ) ?;
375- module. add_function ( clear_runtime_configuration ) ?;
459+ module. add_function ( clear_runtime_config ) ?;
376460
377461 Ok ( ( ) )
378462}
0 commit comments