nautilus_common/python/
timer.rs1use std::str::FromStr;
17
18use nautilus_core::{
19 UUID4, UnixNanos,
20 python::{IntoPyObjectNautilusExt, to_pyvalue_err},
21};
22use pyo3::{
23 IntoPyObjectExt,
24 basic::CompareOp,
25 prelude::*,
26 types::{PyInt, PyString, PyTuple},
27};
28use ustr::Ustr;
29
30use crate::timer::TimeEvent;
31
32#[pymethods]
33#[pyo3_stub_gen::derive::gen_stub_pymethods]
34impl TimeEvent {
35 #[new]
40 fn py_new(name: &str, event_id: UUID4, ts_event: u64, ts_init: u64) -> Self {
41 Self::new(Ustr::from(name), event_id, ts_event.into(), ts_init.into())
42 }
43
44 fn __setstate__(&mut self, state: &Bound<'_, PyAny>) -> PyResult<()> {
45 let py_tuple: &Bound<'_, PyTuple> = state.cast::<PyTuple>()?;
46
47 let ts_event = py_tuple.get_item(2)?.cast::<PyInt>()?.extract::<u64>()?;
48 let ts_init: u64 = py_tuple.get_item(3)?.cast::<PyInt>()?.extract::<u64>()?;
49
50 self.name = Ustr::from(
51 py_tuple
52 .get_item(0)?
53 .cast::<PyString>()?
54 .extract::<&str>()?,
55 );
56 self.event_id = UUID4::from_str(
57 py_tuple
58 .get_item(1)?
59 .cast::<PyString>()?
60 .extract::<&str>()?,
61 )
62 .map_err(to_pyvalue_err)?;
63 self.ts_event = ts_event.into();
64 self.ts_init = ts_init.into();
65
66 Ok(())
67 }
68
69 fn __getstate__(&self, py: Python) -> PyResult<Py<PyAny>> {
70 (
71 self.name.to_string(),
72 self.event_id.to_string(),
73 self.ts_event.as_u64(),
74 self.ts_init.as_u64(),
75 )
76 .into_py_any(py)
77 }
78
79 fn __reduce__(&self, py: Python) -> PyResult<Py<PyAny>> {
80 let safe_constructor = py.get_type::<Self>().getattr("_safe_constructor")?;
81 let state = self.__getstate__(py)?;
82 (safe_constructor, PyTuple::empty(py), state).into_py_any(py)
83 }
84
85 #[staticmethod]
86 fn _safe_constructor() -> Self {
87 Self::new(
88 Ustr::from("NULL"),
89 UUID4::new(),
90 UnixNanos::default(),
91 UnixNanos::default(),
92 )
93 }
94
95 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
96 match op {
97 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
98 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
99 _ => py.NotImplemented(),
100 }
101 }
102
103 fn __repr__(&self) -> String {
104 self.to_string()
105 }
106
107 #[getter]
108 #[pyo3(name = "name")]
109 fn py_name(&self) -> String {
110 self.name.to_string()
111 }
112
113 #[getter]
114 #[pyo3(name = "event_id")]
115 const fn py_event_id(&self) -> UUID4 {
116 self.event_id
117 }
118
119 #[getter]
120 #[pyo3(name = "ts_event")]
121 const fn py_ts_event(&self) -> u64 {
122 self.ts_event.as_u64()
123 }
124
125 #[getter]
126 #[pyo3(name = "ts_init")]
127 const fn py_ts_init(&self) -> u64 {
128 self.ts_init.as_u64()
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use std::{num::NonZeroU64, sync::Arc, time::Duration};
135
136 use nautilus_core::{
137 UnixNanos, datetime::NANOSECONDS_IN_MILLISECOND, python::IntoPyObjectNautilusExt,
138 time::get_atomic_clock_realtime,
139 };
140 use pyo3::prelude::*;
141
142 use crate::{
143 live::timer::LiveTimer,
144 runner::{TimeEventMessage, TimeEventSender, set_time_event_sender},
145 testing::wait_until,
146 timer::{TimeEvent, TimeEventCallback},
147 };
148
149 #[pyfunction]
150 const fn receive_event(_py: Python, _event: TimeEvent) {
151 }
153
154 #[derive(Debug)]
155 struct TestTimeEventSender;
156
157 impl TimeEventSender for TestTimeEventSender {
158 fn send(&self, _message: TimeEventMessage) {
159 }
161 }
162
163 #[tokio::test]
164 async fn test_live_timer_starts_and_stops() {
165 set_time_event_sender(Arc::new(TestTimeEventSender));
166
167 Python::initialize();
168 let callback = Python::attach(|py| {
169 let callable = wrap_pyfunction!(receive_event, py).unwrap();
170 let callable = callable.into_py_any_unwrap(py);
171 TimeEventCallback::from(callable)
172 });
173
174 let clock = get_atomic_clock_realtime();
176 let start_time = clock.get_time_ns();
177 let interval_ns = NonZeroU64::new(100 * NANOSECONDS_IN_MILLISECOND).unwrap();
178
179 let test_sender = Arc::new(TestTimeEventSender);
180 let mut timer = LiveTimer::new(
181 "TEST_TIMER".into(),
182 interval_ns,
183 start_time,
184 None,
185 callback,
186 false,
187 Some(test_sender),
188 );
189
190 let next_time_ns = timer.next_time_ns();
191 timer.start();
192
193 tokio::time::sleep(Duration::from_millis(300)).await;
195
196 timer.cancel();
197 assert!(timer.is_expired(), "Timer should be expired after cancel");
198 assert!(timer.next_time_ns() > next_time_ns);
199 }
200
201 #[tokio::test]
202 async fn test_live_timer_with_stop_time() {
203 set_time_event_sender(Arc::new(TestTimeEventSender));
204
205 Python::initialize();
206 let callback = Python::attach(|py| {
207 let callable = wrap_pyfunction!(receive_event, py).unwrap();
208 let callable = callable.into_py_any_unwrap(py);
209 TimeEventCallback::from(callable)
210 });
211
212 let clock = get_atomic_clock_realtime();
214 let start_time = clock.get_time_ns();
215 let interval_ns = NonZeroU64::new(100 * NANOSECONDS_IN_MILLISECOND).unwrap();
216 let stop_time = start_time + 500 * NANOSECONDS_IN_MILLISECOND;
217
218 let test_sender = Arc::new(TestTimeEventSender);
219 let mut timer = LiveTimer::new(
220 "TEST_TIMER".into(),
221 interval_ns,
222 start_time,
223 Some(stop_time),
224 callback,
225 false,
226 Some(test_sender),
227 );
228
229 let next_time_ns = timer.next_time_ns();
230 timer.start();
231
232 tokio::time::sleep(Duration::from_secs(1)).await;
234
235 wait_until(|| timer.is_expired(), Duration::from_secs(2));
236 assert!(timer.next_time_ns() > next_time_ns);
237 }
238
239 #[tokio::test]
240 async fn test_live_timer_with_zero_interval_and_immediate_stop_time() {
241 set_time_event_sender(Arc::new(TestTimeEventSender));
242
243 Python::initialize();
244 let callback = Python::attach(|py| {
245 let callable = wrap_pyfunction!(receive_event, py).unwrap();
246 let callable = callable.into_py_any_unwrap(py);
247 TimeEventCallback::from(callable)
248 });
249
250 let clock = get_atomic_clock_realtime();
252 let start_time = UnixNanos::default();
253 let interval_ns = NonZeroU64::new(1).unwrap();
254 let stop_time = clock.get_time_ns();
255
256 let test_sender = Arc::new(TestTimeEventSender);
257 let mut timer = LiveTimer::new(
258 "TEST_TIMER".into(),
259 interval_ns,
260 start_time,
261 Some(stop_time),
262 callback,
263 false,
264 Some(test_sender),
265 );
266
267 timer.start();
268
269 wait_until(|| timer.is_expired(), Duration::from_secs(2));
270 }
271}