pub mod cloud;
pub mod memory;
pub mod preflight;
pub mod registry;
pub mod sqlite;
#[cfg(feature = "monitor")]
pub mod trueno;
pub use cloud::{
ArtifactBackend, ArtifactMetadata, AzureConfig, BackendConfig, CloudError, GCSConfig,
InMemoryBackend, LocalBackend, MockS3Backend, S3Config,
};
pub use memory::InMemoryStorage;
pub use preflight::{
CheckMetadata, CheckResult, CheckType, Preflight, PreflightCheck, PreflightContext,
PreflightError, PreflightResults,
};
pub use registry::{
Comparison, InMemoryRegistry, MetricRequirement, ModelRegistry, ModelStage, ModelVersion,
PolicyCheckResult, PromotionPolicy, RegistryError, StageTransition, VersionComparison,
};
pub use sqlite::{
ArtifactRef, Experiment, FilterOp, ParamFilter, ParameterValue, Run, SqliteBackend,
};
#[cfg(feature = "monitor")]
pub use trueno::TruenoBackend;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum StorageError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Experiment not found: {0}")]
ExperimentNotFound(String),
#[error("Run not found: {0}")]
RunNotFound(String),
#[error("Invalid state transition: {0}")]
InvalidState(String),
#[error("Storage backend error: {0}")]
Backend(String),
}
pub type Result<T> = std::result::Result<T, StorageError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RunStatus {
Pending,
Running,
Success,
Failed,
Cancelled,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MetricPoint {
pub step: u64,
pub value: f64,
pub timestamp: DateTime<Utc>,
}
impl MetricPoint {
pub fn new(step: u64, value: f64) -> Self {
Self { step, value, timestamp: Utc::now() }
}
pub fn with_timestamp(step: u64, value: f64, timestamp: DateTime<Utc>) -> Self {
Self { step, value, timestamp }
}
}
pub trait ExperimentStorage: Send + Sync {
fn create_experiment(
&mut self,
name: &str,
config: Option<serde_json::Value>,
) -> Result<String>;
fn create_run(&mut self, experiment_id: &str) -> Result<String>;
fn start_run(&mut self, run_id: &str) -> Result<()>;
fn complete_run(&mut self, run_id: &str, status: RunStatus) -> Result<()>;
fn log_metric(&mut self, run_id: &str, key: &str, step: u64, value: f64) -> Result<()>;
fn log_artifact(&mut self, run_id: &str, key: &str, data: &[u8]) -> Result<String>;
fn get_metrics(&self, run_id: &str, key: &str) -> Result<Vec<MetricPoint>>;
fn get_run_status(&self, run_id: &str) -> Result<RunStatus>;
fn set_span_id(&mut self, run_id: &str, span_id: &str) -> Result<()>;
fn get_span_id(&self, run_id: &str) -> Result<Option<String>>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metric_point_new() {
let point = MetricPoint::new(10, 0.5);
assert_eq!(point.step, 10);
assert!((point.value - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_metric_point_with_timestamp() {
let ts = Utc::now();
let point = MetricPoint::with_timestamp(5, 0.3, ts);
assert_eq!(point.step, 5);
assert_eq!(point.timestamp, ts);
}
#[test]
fn test_run_status_variants() {
assert_ne!(RunStatus::Pending, RunStatus::Running);
assert_ne!(RunStatus::Success, RunStatus::Failed);
}
#[test]
fn test_storage_error_display() {
let err = StorageError::ExperimentNotFound("exp-1".to_string());
assert!(err.to_string().contains("exp-1"));
let err = StorageError::RunNotFound("run-1".to_string());
assert!(err.to_string().contains("run-1"));
let err = StorageError::InvalidState("cannot start".to_string());
assert!(err.to_string().contains("cannot start"));
}
}