use ahash::AHashMap;
#[cfg(feature = "python")]
use nautilus_core::python::to_pyvalue_err;
use serde::{Deserialize, Serialize};
use crate::backend::catalog::ParquetDataCatalog;
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.persistence", from_py_object, frozen, eq)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.persistence")
)]
#[cfg_attr(
feature = "python",
expect(
clippy::unsafe_derive_deserialize,
reason = "config deserializes plain fields; unsafe methods come from generated PyO3 integration"
)
)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DataCatalogConfig {
pub path: String,
pub fs_protocol: Option<String>,
pub fs_rust_storage_options: Option<AHashMap<String, String>>,
pub name: Option<String>,
}
impl DataCatalogConfig {
#[must_use]
pub const fn new(
path: String,
fs_protocol: Option<String>,
fs_rust_storage_options: Option<AHashMap<String, String>>,
name: Option<String>,
) -> Self {
Self {
path,
fs_protocol,
fs_rust_storage_options,
name,
}
}
pub fn create_catalog(&self) -> anyhow::Result<ParquetDataCatalog> {
let uri = match self.fs_protocol.as_deref() {
Some("file") => self.path.clone(),
Some(protocol) if !self.path.contains("://") => {
format!("{protocol}://{}", self.path)
}
_ => self.path.clone(),
};
ParquetDataCatalog::from_uri(&uri, self.fs_rust_storage_options.clone(), None, None, None)
}
}
#[cfg(feature = "python")]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
#[pyo3::pymethods]
impl DataCatalogConfig {
#[new]
#[pyo3(signature = (path, fs_protocol=None, fs_rust_storage_options=None, name=None))]
fn py_new(
path: String,
fs_protocol: Option<String>,
fs_rust_storage_options: Option<std::collections::HashMap<String, String>>,
name: Option<String>,
) -> pyo3::PyResult<Self> {
if path.trim().is_empty() {
return Err(to_pyvalue_err("path must not be empty"));
}
if fs_protocol
.as_ref()
.is_some_and(|value| value.trim().is_empty())
{
return Err(to_pyvalue_err("fs_protocol must not be empty"));
}
if name.as_ref().is_some_and(|value| value.trim().is_empty()) {
return Err(to_pyvalue_err("name must not be empty"));
}
Ok(Self::new(
path,
fs_protocol,
fs_rust_storage_options.map(|values| values.into_iter().collect()),
name,
))
}
#[getter]
fn path(&self) -> &str {
&self.path
}
#[getter]
fn fs_protocol(&self) -> Option<&str> {
self.fs_protocol.as_deref()
}
#[getter]
fn name(&self) -> Option<&str> {
self.name.as_deref()
}
#[getter]
fn fs_rust_storage_option_keys(&self) -> Option<Vec<String>> {
self.fs_rust_storage_options.as_ref().map(|options| {
let mut keys = options.keys().cloned().collect::<Vec<_>>();
keys.sort_unstable();
keys
})
}
fn __repr__(&self) -> String {
format!(
"DataCatalogConfig(path='{}', fs_protocol={:?}, name={:?})",
self.path, self.fs_protocol, self.name
)
}
}