Skip to main content

nautilus_system/python/
config.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Python bindings for system configuration types.
17
18use nautilus_core::{UnixNanos, python::to_pyvalue_err};
19use pyo3::prelude::*;
20
21use crate::config::{RotationConfig, StreamingConfig};
22
23const NANOSECONDS_PER_DAY: u64 = 86_400_000_000_000;
24
25#[pyo3_stub_gen::derive::gen_stub_pymethods]
26#[pymethods]
27impl StreamingConfig {
28    /// Creates a configuration for streaming data and events to Feather files.
29    #[new]
30    #[pyo3(signature = (
31        catalog_path,
32        fs_protocol=None,
33        flush_interval_ms=None,
34        replace_existing=false,
35        rotation_mode="NO_ROTATION",
36        max_file_size=1_073_741_824,
37        rotation_interval_ns=None,
38        schedule_ns=None,
39    ))]
40    #[expect(clippy::too_many_arguments)]
41    fn py_new(
42        catalog_path: String,
43        fs_protocol: Option<String>,
44        flush_interval_ms: Option<u64>,
45        replace_existing: bool,
46        rotation_mode: &str,
47        max_file_size: u64,
48        rotation_interval_ns: Option<u64>,
49        schedule_ns: Option<u64>,
50    ) -> PyResult<Self> {
51        let rotation_config = match rotation_mode.to_ascii_uppercase().as_str() {
52            "SIZE" => {
53                if max_file_size == 0 {
54                    return Err(to_pyvalue_err("max_file_size must be positive"));
55                }
56                RotationConfig::Size {
57                    max_size: max_file_size,
58                }
59            }
60            "INTERVAL" => RotationConfig::Interval {
61                interval_ns: positive_interval(rotation_interval_ns)?,
62            },
63            "SCHEDULED_DATES" => RotationConfig::ScheduledDates {
64                interval_ns: positive_interval(rotation_interval_ns)?,
65                schedule_ns: UnixNanos::from(schedule_ns.unwrap_or(0)),
66            },
67            "NO_ROTATION" => RotationConfig::NoRotation,
68            value => {
69                return Err(to_pyvalue_err(format!("Invalid rotation_mode: '{value}'")));
70            }
71        };
72
73        Self::builder()
74            .catalog_path(catalog_path)
75            .fs_protocol(fs_protocol.unwrap_or_else(|| "file".to_string()))
76            .flush_interval_ms(flush_interval_ms.unwrap_or(1_000))
77            .replace_existing(replace_existing)
78            .rotation_config(rotation_config)
79            .build()
80            .map_err(nautilus_common::python::config_error_to_pyvalue_err)
81    }
82
83    #[getter]
84    fn catalog_path(&self) -> &str {
85        &self.catalog_path
86    }
87
88    #[getter]
89    fn fs_protocol(&self) -> &str {
90        &self.fs_protocol
91    }
92
93    #[getter]
94    const fn flush_interval_ms(&self) -> u64 {
95        self.flush_interval_ms
96    }
97
98    #[getter]
99    const fn replace_existing(&self) -> bool {
100        self.replace_existing
101    }
102
103    #[getter]
104    fn rotation_mode(&self) -> &'static str {
105        match self.rotation_config {
106            RotationConfig::Size { .. } => "SIZE",
107            RotationConfig::Interval { .. } => "INTERVAL",
108            RotationConfig::ScheduledDates { .. } => "SCHEDULED_DATES",
109            RotationConfig::NoRotation => "NO_ROTATION",
110        }
111    }
112
113    #[getter]
114    fn max_file_size(&self) -> Option<u64> {
115        match self.rotation_config {
116            RotationConfig::Size { max_size } => Some(max_size),
117            _ => None,
118        }
119    }
120
121    #[getter]
122    fn rotation_interval_ns(&self) -> Option<u64> {
123        match self.rotation_config {
124            RotationConfig::Interval { interval_ns }
125            | RotationConfig::ScheduledDates { interval_ns, .. } => Some(interval_ns),
126            _ => None,
127        }
128    }
129
130    #[getter]
131    fn schedule_ns(&self) -> Option<u64> {
132        match self.rotation_config {
133            RotationConfig::ScheduledDates { schedule_ns, .. } => Some(schedule_ns.as_u64()),
134            _ => None,
135        }
136    }
137
138    fn __repr__(&self) -> String {
139        format!("{self:?}")
140    }
141}
142
143fn positive_interval(interval_ns: Option<u64>) -> PyResult<u64> {
144    let interval_ns = interval_ns.unwrap_or(NANOSECONDS_PER_DAY);
145    if interval_ns == 0 {
146        return Err(to_pyvalue_err("rotation_interval_ns must be positive"));
147    }
148    Ok(interval_ns)
149}