nautilus_coinapi/python/
mod.rs1pub mod config;
19pub mod csv;
20pub mod factories;
21
22use nautilus_common::factories::{ClientConfig, DataClientFactory};
23use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
24use nautilus_system::get_global_pyo3_registry;
25use pyo3::prelude::*;
26
27use crate::{
28 common::consts::{COINAPI, COINAPI_CLIENT_ID},
29 config::{CoinApiDataClientConfig, CoinApiInstrumentSelection},
30 factories::CoinApiDataClientFactory,
31};
32
33#[expect(clippy::needless_pass_by_value)]
34fn extract_coinapi_data_factory(
35 py: Python<'_>,
36 factory: Py<PyAny>,
37) -> PyResult<Box<dyn DataClientFactory>> {
38 match factory.extract::<CoinApiDataClientFactory>(py) {
39 Ok(f) => Ok(Box::new(f)),
40 Err(e) => Err(to_pyvalue_err(format!(
41 "Failed to extract CoinApiDataClientFactory: {e}"
42 ))),
43 }
44}
45
46#[expect(clippy::needless_pass_by_value)]
47fn extract_coinapi_data_config(
48 py: Python<'_>,
49 config: Py<PyAny>,
50) -> PyResult<Box<dyn ClientConfig>> {
51 match config.extract::<CoinApiDataClientConfig>(py) {
52 Ok(c) => Ok(Box::new(c)),
53 Err(e) => Err(to_pyvalue_err(format!(
54 "Failed to extract CoinApiDataClientConfig: {e}"
55 ))),
56 }
57}
58
59#[pymodule]
65pub fn coinapi(m: &Bound<'_, PyModule>) -> PyResult<()> {
66 m.add(stringify!(COINAPI), COINAPI)?;
67 m.add(stringify!(COINAPI_CLIENT_ID), *COINAPI_CLIENT_ID)?;
68 m.add_class::<CoinApiDataClientConfig>()?;
69 m.add_class::<CoinApiDataClientFactory>()?;
70 m.add_class::<CoinApiInstrumentSelection>()?;
71 m.add_class::<crate::csv::CoinApiOhlcvLoader>()?;
72 m.add_class::<csv::CoinApiDeltaStreamIterator>()?;
73 m.add_class::<csv::CoinApiQuoteStreamIterator>()?;
74 m.add_class::<csv::CoinApiTradeStreamIterator>()?;
75 m.add_function(wrap_pyfunction!(csv::py_load_coinapi_deltas, m)?)?;
76 m.add_function(wrap_pyfunction!(csv::py_load_coinapi_quotes, m)?)?;
77 m.add_function(wrap_pyfunction!(csv::py_load_coinapi_trades, m)?)?;
78 m.add_function(wrap_pyfunction!(csv::py_stream_coinapi_deltas, m)?)?;
79 m.add_function(wrap_pyfunction!(csv::py_stream_coinapi_quotes, m)?)?;
80 m.add_function(wrap_pyfunction!(csv::py_stream_coinapi_trades, m)?)?;
81
82 let registry = get_global_pyo3_registry();
83
84 if let Err(e) =
85 registry.register_factory_extractor(COINAPI.to_string(), extract_coinapi_data_factory)
86 {
87 return Err(to_pyruntime_err(format!(
88 "Failed to register CoinAPI data factory extractor: {e}"
89 )));
90 }
91
92 if let Err(e) = registry.register_config_extractor(
93 "CoinApiDataClientConfig".to_string(),
94 extract_coinapi_data_config,
95 ) {
96 return Err(to_pyruntime_err(format!(
97 "Failed to register CoinAPI data config extractor: {e}"
98 )));
99 }
100
101 Ok(())
102}
103
104#[cfg(test)]
105mod tests {
106 use nautilus_model::identifiers::ClientId;
107 use pyo3::prelude::*;
108 use rstest::rstest;
109
110 #[rstest]
111 fn test_module_registration() {
112 Python::initialize();
113 Python::attach(|py| {
114 let module = pyo3::wrap_pymodule!(super::coinapi)(py);
115 let module = module.bind(py);
116
117 assert_eq!(module.name().unwrap(), "coinapi");
118 assert_eq!(
119 module
120 .getattr("COINAPI")
121 .unwrap()
122 .extract::<String>()
123 .unwrap(),
124 "COINAPI",
125 );
126 assert_eq!(
127 module
128 .getattr("COINAPI_CLIENT_ID")
129 .unwrap()
130 .extract::<ClientId>()
131 .unwrap(),
132 ClientId::from("COINAPI"),
133 );
134
135 for name in [
136 "CoinApiOhlcvLoader",
137 "load_coinapi_deltas",
138 "load_coinapi_quotes",
139 "load_coinapi_trades",
140 "stream_coinapi_deltas",
141 "stream_coinapi_quotes",
142 "stream_coinapi_trades",
143 ] {
144 assert!(module.hasattr(name).unwrap(), "missing {name}");
145 }
146 });
147 }
148}