use std::cell::RefCell;
use ahash::AHashMap;
use nautilus_model::identifiers::ComponentId;
use pyo3::prelude::*;
thread_local! {
static PYTHON_WRAPPERS: RefCell<AHashMap<ComponentId, Py<PyAny>>> =
RefCell::new(AHashMap::new());
}
pub fn retain_python_wrapper(component_id: ComponentId, wrapper: Py<PyAny>) {
let displaced =
PYTHON_WRAPPERS.with_borrow_mut(|wrappers| wrappers.insert(component_id, wrapper));
if displaced.is_some() {
log::warn!("Replaced the retained Python wrapper for {component_id}");
}
drop(displaced);
}
pub fn release_python_wrapper(component_id: ComponentId) {
let released = PYTHON_WRAPPERS.with_borrow_mut(|wrappers| wrappers.remove(&component_id));
drop(released);
}
#[must_use]
pub fn get_python_wrapper(component_id: ComponentId) -> Option<Py<PyAny>> {
Python::attach(|py| {
PYTHON_WRAPPERS.with_borrow(|wrappers| {
wrappers
.get(&component_id)
.map(|wrapper| wrapper.clone_ref(py))
})
})
}
#[cfg(test)]
mod tests {
use pyo3::{ffi::c_str, types::PyModule, wrap_pyfunction};
use rstest::rstest;
use super::*;
#[pyfunction]
fn wrapper_is_retained(component_id: &str) -> bool {
get_python_wrapper(ComponentId::from(component_id)).is_some()
}
#[rstest]
fn test_wrapper_finalization_re_enters_an_unborrowed_registry() {
Python::initialize();
Python::attach(|py| {
let module = PyModule::new(py, "test_wrapper_finalization").unwrap();
module
.add_function(wrap_pyfunction!(wrapper_is_retained, &module).unwrap())
.unwrap();
let code = c_str!(
r#"
OBSERVED = []
class Finalizing:
def __del__(self):
OBSERVED.append(wrapper_is_retained("Finalizing-Component"))
"#
);
py.run(code, Some(&module.dict()), None).unwrap();
let finalizing = module.getattr("Finalizing").unwrap();
let component_id = ComponentId::from("Finalizing-Component");
retain_python_wrapper(component_id, finalizing.call0().unwrap().unbind());
retain_python_wrapper(component_id, finalizing.call0().unwrap().unbind());
release_python_wrapper(component_id);
let observed = module
.getattr("OBSERVED")
.unwrap()
.extract::<Vec<bool>>()
.unwrap();
assert_eq!(observed, vec![true, false]);
});
}
}