nautilus_persistence/
config.rs1use ahash::AHashMap;
19#[cfg(feature = "python")]
20use nautilus_core::python::to_pyvalue_err;
21use serde::{Deserialize, Serialize};
22
23use crate::backend::catalog::ParquetDataCatalog;
24
25#[cfg_attr(
27 feature = "python",
28 pyo3::pyclass(module = "nautilus_trader.persistence", from_py_object, frozen, eq)
29)]
30#[cfg_attr(
31 feature = "python",
32 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.persistence")
33)]
34#[cfg_attr(
35 feature = "python",
36 expect(
37 clippy::unsafe_derive_deserialize,
38 reason = "config deserializes plain fields; unsafe methods come from generated PyO3 integration"
39 )
40)]
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct DataCatalogConfig {
44 pub path: String,
46 pub fs_protocol: Option<String>,
48 pub fs_rust_storage_options: Option<AHashMap<String, String>>,
50 pub name: Option<String>,
52}
53
54impl DataCatalogConfig {
55 #[must_use]
57 pub const fn new(
58 path: String,
59 fs_protocol: Option<String>,
60 fs_rust_storage_options: Option<AHashMap<String, String>>,
61 name: Option<String>,
62 ) -> Self {
63 Self {
64 path,
65 fs_protocol,
66 fs_rust_storage_options,
67 name,
68 }
69 }
70
71 pub fn create_catalog(&self) -> anyhow::Result<ParquetDataCatalog> {
77 let uri = match self.fs_protocol.as_deref() {
78 Some("file") => self.path.clone(),
79 Some(protocol) if !self.path.contains("://") => {
80 format!("{protocol}://{}", self.path)
81 }
82 _ => self.path.clone(),
83 };
84
85 ParquetDataCatalog::from_uri(&uri, self.fs_rust_storage_options.clone(), None, None, None)
86 }
87}
88
89#[cfg(feature = "python")]
90#[pyo3_stub_gen::derive::gen_stub_pymethods]
91#[pyo3::pymethods]
92impl DataCatalogConfig {
93 #[new]
95 #[pyo3(signature = (path, fs_protocol=None, fs_rust_storage_options=None, name=None))]
96 fn py_new(
97 path: String,
98 fs_protocol: Option<String>,
99 fs_rust_storage_options: Option<std::collections::HashMap<String, String>>,
100 name: Option<String>,
101 ) -> pyo3::PyResult<Self> {
102 if path.trim().is_empty() {
103 return Err(to_pyvalue_err("path must not be empty"));
104 }
105
106 if fs_protocol
107 .as_ref()
108 .is_some_and(|value| value.trim().is_empty())
109 {
110 return Err(to_pyvalue_err("fs_protocol must not be empty"));
111 }
112
113 if name.as_ref().is_some_and(|value| value.trim().is_empty()) {
114 return Err(to_pyvalue_err("name must not be empty"));
115 }
116
117 Ok(Self::new(
118 path,
119 fs_protocol,
120 fs_rust_storage_options.map(|values| values.into_iter().collect()),
121 name,
122 ))
123 }
124
125 #[getter]
126 fn path(&self) -> &str {
127 &self.path
128 }
129
130 #[getter]
131 fn fs_protocol(&self) -> Option<&str> {
132 self.fs_protocol.as_deref()
133 }
134
135 #[getter]
136 fn name(&self) -> Option<&str> {
137 self.name.as_deref()
138 }
139
140 #[getter]
142 fn fs_rust_storage_option_keys(&self) -> Option<Vec<String>> {
143 self.fs_rust_storage_options.as_ref().map(|options| {
144 let mut keys = options.keys().cloned().collect::<Vec<_>>();
145 keys.sort_unstable();
146 keys
147 })
148 }
149
150 fn __repr__(&self) -> String {
151 format!(
152 "DataCatalogConfig(path='{}', fs_protocol={:?}, name={:?})",
153 self.path, self.fs_protocol, self.name
154 )
155 }
156}