use crate::models::{PyDecisionSnapshot, PySnapshot, PySnapshotQuery};
use crate::runtime::PythonAsyncExt;
use briefcase_core::storage::{LakeFSBackend, SqliteBackend, StorageBackend};
use pyo3::prelude::*;
use pyo3::types::PyList;
#[pyclass(name = "SqliteBackend")]
pub struct PySqliteBackend {
pub inner: SqliteBackend,
}
#[pymethods]
impl PySqliteBackend {
#[new]
fn new(path: Option<String>) -> PyResult<Self> {
let backend = if let Some(path) = path {
SqliteBackend::new(path)
} else {
SqliteBackend::in_memory()
};
match backend {
Ok(backend) => Ok(Self { inner: backend }),
Err(e) => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
"Failed to create SQLite backend: {}",
e
))),
}
}
#[staticmethod]
fn in_memory() -> PyResult<Self> {
match SqliteBackend::in_memory() {
Ok(backend) => Ok(Self { inner: backend }),
Err(e) => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
"Failed to create in-memory SQLite backend: {}",
e
))),
}
}
fn save(&self, snapshot: PyRef<PySnapshot>) -> PyResult<String> {
let backend = self.inner.clone();
let snapshot_inner = snapshot.inner.clone();
backend.save(&snapshot_inner).block_on_python()
}
fn save_decision(&self, decision: PyRef<PyDecisionSnapshot>) -> PyResult<String> {
let backend = self.inner.clone();
let decision_inner = decision.inner.clone();
backend.save_decision(&decision_inner).block_on_python()
}
fn load(&self, snapshot_id: String) -> PyResult<PySnapshot> {
let backend = self.inner.clone();
backend
.load(&snapshot_id)
.block_on_python()
.map(|snapshot| PySnapshot { inner: snapshot })
}
fn load_decision(&self, decision_id: String) -> PyResult<PyDecisionSnapshot> {
let backend = self.inner.clone();
backend
.load_decision(&decision_id)
.block_on_python()
.map(|decision| PyDecisionSnapshot { inner: decision })
}
fn query(&self, query: PyRef<PySnapshotQuery>) -> PyResult<PyObject> {
let backend = self.inner.clone();
let query_inner = query.inner.clone();
let snapshots = backend.query(query_inner).block_on_python()?;
Python::with_gil(|py| {
let list = PyList::empty(py);
for snapshot in snapshots {
let py_snapshot = PySnapshot { inner: snapshot };
list.append(Py::new(py, py_snapshot)?)?;
}
Ok(list.into())
})
}
fn delete(&self, snapshot_id: String) -> PyResult<bool> {
let backend = self.inner.clone();
backend.delete(&snapshot_id).block_on_python()
}
fn health_check(&self) -> PyResult<bool> {
let backend = self.inner.clone();
backend.health_check().block_on_python()
}
fn __repr__(&self) -> String {
"SqliteBackend()".to_string()
}
}
#[pyclass(name = "LakeFSBackend")]
pub struct PyLakeFSBackend {
pub inner: LakeFSBackend,
}
#[pymethods]
impl PyLakeFSBackend {
#[new]
fn new(
endpoint: String,
repository: String,
branch: String,
access_key: String,
secret_key: String,
) -> PyResult<Self> {
let config = briefcase_core::storage::LakeFSConfig::new(
endpoint, repository, branch, access_key, secret_key,
);
match LakeFSBackend::new(config) {
Ok(backend) => Ok(Self { inner: backend }),
Err(e) => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
"Failed to create LakeFS backend: {}",
e
))),
}
}
fn save(&self, snapshot: PyRef<PySnapshot>) -> PyResult<String> {
let backend = self.inner.clone();
let snapshot_inner = snapshot.inner.clone();
backend.save(&snapshot_inner).block_on_python()
}
fn save_decision(&self, decision: PyRef<PyDecisionSnapshot>) -> PyResult<String> {
let backend = self.inner.clone();
let decision_inner = decision.inner.clone();
backend.save_decision(&decision_inner).block_on_python()
}
fn load(&self, snapshot_id: String) -> PyResult<PySnapshot> {
let backend = self.inner.clone();
backend
.load(&snapshot_id)
.block_on_python()
.map(|snapshot| PySnapshot { inner: snapshot })
}
fn load_decision(&self, decision_id: String) -> PyResult<PyDecisionSnapshot> {
let backend = self.inner.clone();
backend
.load_decision(&decision_id)
.block_on_python()
.map(|decision| PyDecisionSnapshot { inner: decision })
}
fn query(&self, query: PyRef<PySnapshotQuery>) -> PyResult<PyObject> {
let backend = self.inner.clone();
let query_inner = query.inner.clone();
let snapshots = backend.query(query_inner).block_on_python()?;
Python::with_gil(|py| {
let list = PyList::empty(py);
for snapshot in snapshots {
let py_snapshot = PySnapshot { inner: snapshot };
list.append(Py::new(py, py_snapshot)?)?;
}
Ok(list.into())
})
}
fn delete(&self, snapshot_id: String) -> PyResult<bool> {
let backend = self.inner.clone();
backend.delete(&snapshot_id).block_on_python()
}
fn health_check(&self) -> PyResult<bool> {
let backend = self.inner.clone();
backend.health_check().block_on_python()
}
fn flush(&self) -> PyResult<String> {
let backend = self.inner.clone();
let result = backend.flush().block_on_python()?;
Ok(format!(
"Flushed {} snapshots, {} bytes",
result.snapshots_written, result.bytes_written
))
}
fn __repr__(&self) -> String {
"LakeFSBackend()".to_string()
}
}
#[derive(Clone)]
pub enum PyStorageBackend {
Sqlite(SqliteBackend),
LakeFS(LakeFSBackend),
}
#[async_trait::async_trait]
impl StorageBackend for PyStorageBackend {
async fn save(
&self,
snapshot: &briefcase_core::models::Snapshot,
) -> Result<String, briefcase_core::storage::StorageError> {
match self {
PyStorageBackend::Sqlite(backend) => backend.save(snapshot).await,
PyStorageBackend::LakeFS(backend) => backend.save(snapshot).await,
}
}
async fn save_decision(
&self,
decision: &briefcase_core::models::DecisionSnapshot,
) -> Result<String, briefcase_core::storage::StorageError> {
match self {
PyStorageBackend::Sqlite(backend) => backend.save_decision(decision).await,
PyStorageBackend::LakeFS(backend) => backend.save_decision(decision).await,
}
}
async fn load(
&self,
snapshot_id: &str,
) -> Result<briefcase_core::models::Snapshot, briefcase_core::storage::StorageError> {
match self {
PyStorageBackend::Sqlite(backend) => backend.load(snapshot_id).await,
PyStorageBackend::LakeFS(backend) => backend.load(snapshot_id).await,
}
}
async fn load_decision(
&self,
snapshot_id: &str,
) -> Result<briefcase_core::models::DecisionSnapshot, briefcase_core::storage::StorageError>
{
match self {
PyStorageBackend::Sqlite(backend) => backend.load_decision(snapshot_id).await,
PyStorageBackend::LakeFS(backend) => backend.load_decision(snapshot_id).await,
}
}
async fn query(
&self,
query: briefcase_core::storage::SnapshotQuery,
) -> Result<Vec<briefcase_core::models::Snapshot>, briefcase_core::storage::StorageError> {
match self {
PyStorageBackend::Sqlite(backend) => backend.query(query).await,
PyStorageBackend::LakeFS(backend) => backend.query(query).await,
}
}
async fn delete(
&self,
snapshot_id: &str,
) -> Result<bool, briefcase_core::storage::StorageError> {
match self {
PyStorageBackend::Sqlite(backend) => backend.delete(snapshot_id).await,
PyStorageBackend::LakeFS(backend) => backend.delete(snapshot_id).await,
}
}
async fn health_check(&self) -> Result<bool, briefcase_core::storage::StorageError> {
match self {
PyStorageBackend::Sqlite(backend) => backend.health_check().await,
PyStorageBackend::LakeFS(backend) => backend.health_check().await,
}
}
async fn flush(
&self,
) -> Result<briefcase_core::storage::FlushResult, briefcase_core::storage::StorageError> {
match self {
PyStorageBackend::Sqlite(backend) => backend.flush().await,
PyStorageBackend::LakeFS(backend) => backend.flush().await,
}
}
}