nautilus_persistence/python/backend/
session.rs1use std::collections::HashMap;
17
18use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
19use nautilus_model::{
20 data::{
21 Bar, FundingRateUpdate, IndexPriceUpdate, InstrumentClose, InstrumentStatus,
22 MarkPriceUpdate, NautilusDataType, OptionGreeks, OrderBookDelta, OrderBookDepth, QuoteTick,
23 TradeTick,
24 },
25 python::data::data_to_pyobject,
26};
27use nautilus_serialization::arrow::{ArrowSchemaProvider, custom::CustomDataDecoder};
28use pyo3::{IntoPyObjectExt, prelude::*};
29
30use super::conversion::nautilus_data_type_from_py;
31use crate::backend::session::{DataBackendSession, DataQueryResult, QueryError};
32
33struct SendPtr<T>(*mut T);
35
36unsafe impl<T> Send for SendPtr<T> {}
38
39#[pymethods]
40#[pyo3_stub_gen::derive::gen_stub_pymethods]
41impl DataBackendSession {
42 #[new]
44 #[pyo3(signature=(chunk_size=10_000))]
45 fn py_new(chunk_size: usize) -> PyResult<Self> {
46 if chunk_size == 0 {
47 return Err(to_pyvalue_err("chunk_size must be positive"));
48 }
49
50 Ok(Self::new(chunk_size))
51 }
52
53 #[pyo3(name = "add_file")]
71 #[pyo3(signature = (data_type, table_name, file_path, sql_query=None))]
72 fn py_add_file(
73 mut slf: PyRefMut<'_, Self>,
74 data_type: &Bound<'_, PyAny>,
75 table_name: &str,
76 file_path: &str,
77 sql_query: Option<&str>,
78 ) -> PyResult<()> {
79 let _guard = slf.runtime.enter();
80 let data_type = nautilus_data_type_from_py(data_type)?;
81
82 slf.add_file_for_data_type(&data_type, table_name, file_path, sql_query)
83 }
84
85 #[pyo3(name = "add_custom_file")]
90 #[pyo3(signature = (type_name, table_name, file_path, sql_query=None))]
91 fn py_add_custom_file(
92 mut slf: PyRefMut<'_, Self>,
93 type_name: &str,
94 table_name: &str,
95 file_path: &str,
96 sql_query: Option<&str>,
97 ) -> PyResult<()> {
98 let _guard = slf.runtime.enter();
99
100 let data_type = NautilusDataType::Custom {
101 type_name: type_name.to_string(),
102 };
103
104 slf.add_file_for_data_type(&data_type, table_name, file_path, sql_query)
105 }
106
107 fn to_query_result(mut slf: PyRefMut<'_, Self>) -> DataQueryResult {
108 let py = slf.py();
109 let chunk_size = slf.chunk_size;
110 let ptr = SendPtr(&raw mut *slf);
111
112 let query_result = unsafe {
117 py.detach(move || {
118 let p = ptr;
119 (*p.0).get_query_result()
120 })
121 };
122
123 DataQueryResult::new(query_result, chunk_size)
124 }
125
126 #[pyo3(name = "register_object_store_from_uri")]
133 #[pyo3(signature = (uri, storage_options=None))]
134 fn py_register_object_store_from_uri(
135 mut slf: PyRefMut<'_, Self>,
136 uri: &str,
137 storage_options: Option<HashMap<String, String>>,
138 ) -> PyResult<()> {
139 let storage_options = storage_options.map(|m| m.into_iter().collect());
141 slf.register_object_store_from_uri(uri, storage_options)
142 .map_err(to_pyruntime_err)
143 }
144}
145
146impl DataBackendSession {
147 fn add_file_for_data_type(
148 &mut self,
149 data_type: &NautilusDataType,
150 table_name: &str,
151 file_path: &str,
152 sql_query: Option<&str>,
153 ) -> PyResult<()> {
154 match data_type {
155 NautilusDataType::OrderBookDelta => self
156 .add_file::<OrderBookDelta>(table_name, file_path, sql_query, None)
157 .map_err(to_pyruntime_err),
158 NautilusDataType::OrderBookDepth => self
159 .add_file::<OrderBookDepth>(table_name, file_path, sql_query, None)
160 .map_err(to_pyruntime_err),
161 NautilusDataType::QuoteTick => self
162 .add_file::<QuoteTick>(table_name, file_path, sql_query, None)
163 .map_err(to_pyruntime_err),
164 NautilusDataType::TradeTick => self
165 .add_file::<TradeTick>(table_name, file_path, sql_query, None)
166 .map_err(to_pyruntime_err),
167 NautilusDataType::Bar => self
168 .add_file::<Bar>(table_name, file_path, sql_query, None)
169 .map_err(to_pyruntime_err),
170 NautilusDataType::MarkPriceUpdate => self
171 .add_file::<MarkPriceUpdate>(table_name, file_path, sql_query, None)
172 .map_err(to_pyruntime_err),
173 NautilusDataType::IndexPriceUpdate => self
174 .add_file::<IndexPriceUpdate>(table_name, file_path, sql_query, None)
175 .map_err(to_pyruntime_err),
176 NautilusDataType::FundingRateUpdate => self
177 .add_file::<FundingRateUpdate>(table_name, file_path, sql_query, None)
178 .map_err(to_pyruntime_err),
179 NautilusDataType::OptionGreeks => self
180 .add_file::<OptionGreeks>(table_name, file_path, sql_query, None)
181 .map_err(to_pyruntime_err),
182 NautilusDataType::InstrumentStatus => self
183 .add_file::<InstrumentStatus>(table_name, file_path, sql_query, None)
184 .map_err(to_pyruntime_err),
185 NautilusDataType::InstrumentClose => self
186 .add_file::<InstrumentClose>(table_name, file_path, sql_query, None)
187 .map_err(to_pyruntime_err),
188 NautilusDataType::Custom { type_name } => {
189 let mut metadata = HashMap::new();
190 metadata.insert("type_name".to_string(), type_name.clone());
191 let base_schema = CustomDataDecoder::get_schema(Some(metadata));
192 base_schema.field_with_name("ts_init").map_err(|_| {
193 to_pyruntime_err(format!(
194 "custom data type '{type_name}' is not registered with an Arrow schema containing ts_init"
195 ))
196 })?;
197
198 self.add_file::<CustomDataDecoder>(
202 table_name,
203 file_path,
204 sql_query,
205 Some(type_name.as_str()),
206 )
207 .map_err(to_pyruntime_err)
208 }
209 NautilusDataType::Instrument => Err(to_pyvalue_err(format!(
210 "DataBackendSession does not support data type {data_type}"
211 ))),
212 #[cfg(feature = "defi")]
213 NautilusDataType::Defi => Err(to_pyvalue_err(format!(
214 "DataBackendSession does not support data type {data_type}"
215 ))),
216 }
217 }
218}
219
220#[pymethods]
221#[pyo3_stub_gen::derive::gen_stub_pymethods]
222impl DataQueryResult {
223 #[pyo3(name = "to_list")]
229 fn py_to_list(mut slf: PyRefMut<'_, Self>) -> PyResult<Vec<Py<PyAny>>> {
230 let py = slf.py();
231 let ptr = SendPtr(&raw mut *slf);
232
233 let data = unsafe {
237 py.detach(move || -> Result<Vec<_>, QueryError> {
238 let p = ptr;
239 let result = &mut *p.0;
240 let mut data = Vec::new();
241
242 for chunk in result.by_ref() {
243 let chunk = chunk?;
244
245 if chunk.is_empty() {
246 break;
247 }
248 data.extend(chunk);
249 }
250
251 Ok(data)
252 })
253 }
254 .map_err(to_pyruntime_err)?;
255
256 data.into_iter()
257 .map(|item| data_to_pyobject(py, item))
258 .collect()
259 }
260
261 const fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
263 slf
264 }
265
266 fn __next__(mut slf: PyRefMut<'_, Self>) -> PyResult<Option<Py<PyAny>>> {
272 let py = slf.py();
273 let ptr = SendPtr(&raw mut *slf);
274
275 let acc = unsafe {
285 py.detach(move || {
286 let p = ptr;
287 (*p.0).next()
288 })
289 };
290
291 match acc {
292 Some(Ok(acc)) if !acc.is_empty() => {
293 let objects: Vec<Py<PyAny>> = acc
294 .into_iter()
295 .map(|item| data_to_pyobject(py, item))
296 .collect::<PyResult<_>>()?;
297 Ok(Some(objects.into_py_any(py)?))
298 }
299 Some(Err(e)) => Err(to_pyruntime_err(e)),
300 _ => Ok(None),
301 }
302 }
303}