9999 val. map ( |v| v. into_py_any ( py) ) . transpose ( )
100100}
101101
102- fn set_global_config < T : AttrValue + Debug > ( key : & ' static dyn ErasedKey , value : T ) -> PyResult < ( ) > {
102+ /// Fetch a config value from the **Runtime** layer only and convert
103+ /// it to Python.
104+ ///
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 > (
110+ py : Python < ' py > ,
111+ key : & ' static dyn ErasedKey ,
112+ ) -> PyResult < Option < PyObject > >
113+ where
114+ T : AttrValue + TryInto < P > ,
115+ P : IntoPyObjectExt < ' py > ,
116+ PyErr : From < <T as TryInto < P > >:: Error > ,
117+ {
118+ let key = key. downcast_ref :: < T > ( ) . expect ( "cannot fail" ) ;
119+ let runtime = hyperactor:: config:: global:: runtime_attrs ( ) ;
120+ let val: Option < P > = runtime
121+ . get ( key. clone ( ) )
122+ . cloned ( )
123+ . map ( |v| v. try_into ( ) )
124+ . transpose ( ) ?;
125+ val. map ( |v| v. into_py_any ( py) ) . transpose ( )
126+ }
127+
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 < ( ) > {
103130 // Again, can't fail unless there's a bug in the code in this file.
104131 let key = key. downcast_ref ( ) . expect ( "cannot fail" ) ;
105132 let mut attrs = Attrs :: new ( ) ;
@@ -108,7 +135,7 @@ fn set_global_config<T: AttrValue + Debug>(key: &'static dyn ErasedKey, value: T
108135 Ok ( ( ) )
109136}
110137
111- fn set_global_config_from_py_obj ( py : Python < ' _ > , name : & str , val : PyObject ) -> PyResult < ( ) > {
138+ fn set_runtime_config_from_py_obj ( py : Python < ' _ > , name : & str , val : PyObject ) -> PyResult < ( ) > {
112139 // Get the `ErasedKey` from the kwarg `name` passed to `monarch.configure(...)`.
113140 let key = match KEY_BY_NAME . get ( name) {
114141 None => {
@@ -128,7 +155,7 @@ fn set_global_config_from_py_obj(py: Python<'_>, name: &str, val: PyObject) -> P
128155 name,
129156 key. typename( )
130157 ) ) ) ,
131- Some ( info) => ( info. set_global_config ) ( py, key, val) ,
158+ Some ( info) => ( info. set_runtime_config ) ( py, key, val) ,
132159 }
133160}
134161
@@ -137,10 +164,15 @@ fn set_global_config_from_py_obj(py: Python<'_>, name: &str, val: PyObject) -> P
137164/// `T::typehash() == PythonConfigTypeInfo::typehash()`.
138165struct PythonConfigTypeInfo {
139166 typehash : fn ( ) -> u64 ,
140- set_global_config :
141- fn ( py : Python < ' _ > , key : & ' static dyn ErasedKey , val : PyObject ) -> PyResult < ( ) > ,
167+
142168 get_global_config :
143169 fn ( py : Python < ' _ > , key : & ' static dyn ErasedKey ) -> PyResult < Option < PyObject > > ,
170+
171+ set_runtime_config :
172+ fn ( py : Python < ' _ > , key : & ' static dyn ErasedKey , val : PyObject ) -> PyResult < ( ) > ,
173+
174+ get_runtime_config :
175+ fn ( py : Python < ' _ > , key : & ' static dyn ErasedKey ) -> PyResult < Option < PyObject > > ,
144176}
145177
146178inventory:: collect!( PythonConfigTypeInfo ) ;
@@ -160,15 +192,18 @@ macro_rules! declare_py_config_type {
160192 hyperactor:: submit! {
161193 PythonConfigTypeInfo {
162194 typehash: $ty:: typehash,
163- set_global_config : |py, key, val| {
195+ set_runtime_config : |py, key, val| {
164196 let val: $ty = val. extract:: <$ty>( py) . map_err( |err| PyTypeError :: new_err( format!(
165197 "invalid value `{}` for configuration key `{}` ({})" ,
166198 val, key. name( ) , err
167199 ) ) ) ?;
168- set_global_config ( key, val)
200+ set_runtime_config ( key, val)
169201 } ,
170202 get_global_config: |py, key| {
171203 get_global_config:: <$ty, $ty>( py, key)
204+ } ,
205+ get_runtime_config: |py, key| {
206+ get_runtime_config:: <$ty, $ty>( py, key)
172207 }
173208 }
174209 }
@@ -180,15 +215,18 @@ macro_rules! declare_py_config_type {
180215 hyperactor:: submit! {
181216 PythonConfigTypeInfo {
182217 typehash: $ty:: typehash,
183- set_global_config : |py, key, val| {
218+ set_runtime_config : |py, key, val| {
184219 let val: $ty = val. extract:: <$py_ty>( py) . map_err( |err| PyTypeError :: new_err( format!(
185220 "invalid value `{}` for configuration key `{}` ({})" ,
186221 val, key. name( ) , err
187222 ) ) ) ?. into( ) ;
188- set_global_config ( key, val)
223+ set_runtime_config ( key, val)
189224 } ,
190225 get_global_config: |py, key| {
191226 get_global_config:: <$py_ty, $ty>( py, key)
227+ } ,
228+ get_runtime_config: |py, key| {
229+ get_runtime_config:: <$py_ty, $ty>( py, key)
192230 }
193231 }
194232 }
@@ -212,7 +250,7 @@ fn configure(py: Python<'_>, kwargs: Option<HashMap<String, PyObject>>) -> PyRes
212250 . map ( |kwargs| {
213251 kwargs
214252 . into_iter ( )
215- . try_for_each ( |( key, val) | set_global_config_from_py_obj ( py, & key, val) )
253+ . try_for_each ( |( key, val) | set_runtime_config_from_py_obj ( py, & key, val) )
216254 } )
217255 . transpose ( ) ?;
218256 Ok ( ( ) )
@@ -236,6 +274,62 @@ fn get_configuration(py: Python<'_>) -> PyResult<HashMap<String, PyObject>> {
236274 . collect ( )
237275}
238276
277+ /// Get only the Runtime layer configuration (Python-exposed keys).
278+ ///
279+ /// The Runtime layer is effectively the "Python configuration layer",
280+ /// populated exclusively via `configure(**kwargs)` from Python. This
281+ /// function returns only the Python-exposed keys (those with
282+ /// `@meta(CONFIG = ConfigAttr { py_name: Some(...), .. })`) that are
283+ /// currently set in the Runtime layer.
284+ ///
285+ /// This is used by Python's `configured()` context manager to
286+ /// snapshot and restore the Runtime layer for composable, nested
287+ /// configuration overrides:
288+ ///
289+ /// ```python
290+ /// prev = get_runtime_configuration()
291+ /// try:
292+ /// configure(**overrides)
293+ /// yield get_configuration()
294+ /// finally:
295+ /// clear_runtime_configuration()
296+ /// configure(**prev)
297+ /// ```
298+ ///
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.
302+ #[ pyfunction]
303+ fn get_runtime_configuration ( py : Python < ' _ > ) -> PyResult < HashMap < String , PyObject > > {
304+ KEY_BY_NAME
305+ . iter ( )
306+ . filter_map ( |( name, key) | match TYPEHASH_TO_INFO . get ( & key. typehash ( ) ) {
307+ None => None ,
308+ Some ( info) => match ( info. get_runtime_config ) ( py, * key) {
309+ Err ( err) => Some ( Err ( err) ) ,
310+ Ok ( val) => val. map ( |val| Ok ( ( ( * name) . into ( ) , val) ) ) ,
311+ } ,
312+ } )
313+ . collect ( )
314+ }
315+
316+ /// Clear runtime configuration overrides.
317+ ///
318+ /// This removes all entries from the Runtime config layer for this
319+ /// process. The Runtime layer is exclusively populated via Python's
320+ /// `configure(**kwargs)`, so clearing it is SAFE — it will not
321+ /// destroy configuration from other sources (environment variables,
322+ /// config files, or built-in defaults).
323+ ///
324+ /// This is primarily used by Python's `configured()` context manager
325+ /// to restore configuration state after applying temporary overrides.
326+ /// Other layers (Env, File, TestOverride, defaults) are unaffected.
327+ #[ pyfunction]
328+ fn clear_runtime_configuration ( _py : Python < ' _ > ) -> PyResult < ( ) > {
329+ hyperactor:: config:: global:: clear ( Source :: Runtime ) ;
330+ Ok ( ( ) )
331+ }
332+
239333/// Register Python bindings for the config module
240334pub fn register_python_bindings ( module : & Bound < ' _ , PyModule > ) -> PyResult < ( ) > {
241335 let reload = wrap_pyfunction ! ( reload_config_from_env, module) ?;
@@ -266,5 +360,19 @@ pub fn register_python_bindings(module: &Bound<'_, PyModule>) -> PyResult<()> {
266360 ) ?;
267361 module. add_function ( get_configuration) ?;
268362
363+ let get_runtime_configuration = wrap_pyfunction ! ( get_runtime_configuration, module) ?;
364+ get_runtime_configuration. setattr (
365+ "__module__" ,
366+ "monarch._rust_bindings.monarch_hyperactor.config" ,
367+ ) ?;
368+ module. add_function ( get_runtime_configuration) ?;
369+
370+ let clear_runtime_configuration = wrap_pyfunction ! ( clear_runtime_configuration, module) ?;
371+ clear_runtime_configuration. setattr (
372+ "__module__" ,
373+ "monarch._rust_bindings.monarch_hyperactor.config" ,
374+ ) ?;
375+ module. add_function ( clear_runtime_configuration) ?;
376+
269377 Ok ( ( ) )
270378}
0 commit comments