use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyString};
use std::sync::Arc;
use tokio::runtime::Runtime;
use crate::{Client, Error};
#[pyclass(extends=pyo3::exceptions::PyException)]
pub struct SupabaseError {
message: String,
}
impl From<Error> for PyErr {
fn from(err: Error) -> Self {
let message = match err {
Error::InvalidInput { message } => format!("Invalid input: {}", message),
Error::Network { message, .. } => format!("Network error: {}", message),
Error::Auth { message, .. } => format!("Authentication error: {}", message),
Error::Database { message, .. } => format!("Database error: {}", message),
Error::Storage { message, .. } => format!("Storage error: {}", message),
Error::Functions { message, .. } => format!("Functions error: {}", message),
Error::Realtime { message, .. } => format!("Realtime error: {}", message),
_ => format!("Runtime error: {}", err),
};
PyErr::new::<SupabaseError, _>(message)
}
}
#[pyclass]
pub struct PySupabaseClient {
client: Arc<Client>,
runtime: Arc<Runtime>,
}
#[pymethods]
impl PySupabaseClient {
#[new]
fn new(url: &str, key: &str) -> PyResult<Self> {
let client = Client::new(url, key)?;
let runtime = Runtime::new()
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("Failed to create async runtime: {}", e)
))?;
Ok(PySupabaseClient {
client: Arc::new(client),
runtime: Arc::new(runtime),
})
}
#[getter]
fn auth(&self) -> PyAuth {
PyAuth::new(self.client.clone(), self.runtime.clone())
}
#[getter]
fn database(&self) -> PyDatabase {
PyDatabase::new(self.client.clone(), self.runtime.clone())
}
#[getter]
fn storage(&self) -> PyStorage {
PyStorage::new(self.client.clone(), self.runtime.clone())
}
#[getter]
fn functions(&self) -> PyFunctions {
PyFunctions::new(self.client.clone(), self.runtime.clone())
}
}
#[pyclass]
pub struct PyAuth {
client: Arc<Client>,
runtime: Arc<Runtime>,
}
impl PyAuth {
fn new(client: Arc<Client>, runtime: Arc<Runtime>) -> Self {
Self { client, runtime }
}
}
#[pymethods]
impl PyAuth {
fn sign_in(&self, py: Python<'_>, email: &str, password: &str) -> PyResult<PyObject> {
let client = self.client.clone();
let email = email.to_string();
let password = password.to_string();
py.allow_threads(|| {
let result = self.runtime.block_on(async {
client.auth().sign_in_with_email_and_password(&email, &password).await
})?;
Python::with_gil(|py| {
let session_json = serde_json::to_string(&result)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON serialization error: {}", e)
))?;
let parsed: serde_json::Value = serde_json::from_str(&session_json)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON parsing error: {}", e)
))?;
json_to_python(py, &parsed)
})
})
}
fn sign_up(&self, py: Python<'_>, email: &str, password: &str) -> PyResult<PyObject> {
let client = self.client.clone();
let email = email.to_string();
let password = password.to_string();
py.allow_threads(|| {
let result = self.runtime.block_on(async {
client.auth().sign_up(&email, &password, None, None).await
})?;
Python::with_gil(|py| {
let session_json = serde_json::to_string(&result)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON serialization error: {}", e)
))?;
let parsed: serde_json::Value = serde_json::from_str(&session_json)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON parsing error: {}", e)
))?;
json_to_python(py, &parsed)
})
})
}
fn sign_out(&self, py: Python<'_>) -> PyResult<()> {
let client = self.client.clone();
py.allow_threads(|| {
self.runtime.block_on(async {
client.auth().sign_out().await
})
})
}
fn get_session(&self, py: Python<'_>) -> PyResult<PyObject> {
let client = self.client.clone();
py.allow_threads(|| {
match client.auth().get_session() {
Some(session) => {
Python::with_gil(|py| {
let session_json = serde_json::to_string(&session)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON serialization error: {}", e)
))?;
let parsed: serde_json::Value = serde_json::from_str(&session_json)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON parsing error: {}", e)
))?;
json_to_python(py, &parsed)
})
}
None => Ok(Python::with_gil(|py| py.None())),
}
})
}
}
#[pyclass]
pub struct PyDatabase {
client: Arc<Client>,
runtime: Arc<Runtime>,
}
impl PyDatabase {
fn new(client: Arc<Client>, runtime: Arc<Runtime>) -> Self {
Self { client, runtime }
}
}
#[pymethods]
impl PyDatabase {
fn from_(&self, table: &str) -> PyQueryBuilder {
PyQueryBuilder::new(
self.client.clone(),
self.runtime.clone(),
table.to_string(),
)
}
}
#[pyclass]
pub struct PyQueryBuilder {
client: Arc<Client>,
runtime: Arc<Runtime>,
table: String,
select_columns: Option<String>,
filters: Vec<(String, String, String)>,
order_by: Vec<(String, bool)>,
limit_value: Option<u32>,
offset_value: Option<u32>,
}
impl PyQueryBuilder {
fn new(client: Arc<Client>, runtime: Arc<Runtime>, table: String) -> Self {
Self {
client,
runtime,
table,
select_columns: None,
filters: Vec::new(),
order_by: Vec::new(),
limit_value: None,
offset_value: None,
}
}
}
#[pymethods]
impl PyQueryBuilder {
fn select(slf: PyRef<'_, Self>, columns: &str) -> Py<Self> {
let mut new_builder = PyQueryBuilder {
client: slf.client.clone(),
runtime: slf.runtime.clone(),
table: slf.table.clone(),
select_columns: Some(columns.to_string()),
filters: slf.filters.clone(),
order_by: slf.order_by.clone(),
limit_value: slf.limit_value,
offset_value: slf.offset_value,
};
Py::new(slf.py(), new_builder).unwrap()
}
fn filter(slf: PyRef<'_, Self>, column: &str, operator: &str, value: &str) -> Py<Self> {
let mut new_builder = PyQueryBuilder {
client: slf.client.clone(),
runtime: slf.runtime.clone(),
table: slf.table.clone(),
select_columns: slf.select_columns.clone(),
filters: {
let mut filters = slf.filters.clone();
filters.push((column.to_string(), operator.to_string(), value.to_string()));
filters
},
order_by: slf.order_by.clone(),
limit_value: slf.limit_value,
offset_value: slf.offset_value,
};
Py::new(slf.py(), new_builder).unwrap()
}
fn insert(&self, py: Python<'_>, data: &PyAny) -> PyResult<PyObject> {
let client = self.client.clone();
let table = self.table.clone();
let json_data = python_to_json(py, data)?;
py.allow_threads(|| {
let result = self.runtime.block_on(async {
let query = client.database().from(&table);
query.select("*").execute().await
})?;
Python::with_gil(|py| {
let result_json = serde_json::to_string(&result)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON serialization error: {}", e)
))?;
let parsed: serde_json::Value = serde_json::from_str(&result_json)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON parsing error: {}", e)
))?;
json_to_python(py, &parsed)
})
})
}
fn execute(&self, py: Python<'_>) -> PyResult<PyObject> {
let client = self.client.clone();
let table = self.table.clone();
let select_columns = self.select_columns.clone().unwrap_or_else(|| "*".to_string());
let filters = self.filters.clone();
let limit_value = self.limit_value;
py.allow_threads(|| {
let result = self.runtime.block_on(async {
let mut query = client.database().from(&table).select(&select_columns);
for (column, operator, value) in filters {
query = match operator.as_str() {
"eq" => query.eq(&column, &value),
"neq" => query.neq(&column, &value),
"gt" => query.gt(&column, &value),
"gte" => query.gte(&column, &value),
"lt" => query.lt(&column, &value),
"lte" => query.lte(&column, &value),
"like" => query.like(&column, &value),
"ilike" => query.ilike(&column, &value),
"in" => query.in(&column, vec![value]),
_ => return Err(Error::InvalidInput {
message: format!("Unknown operator: {}", operator),
}),
};
}
if let Some(limit) = limit_value {
query = query.limit(limit);
}
query.execute().await
})?;
Python::with_gil(|py| {
let result_json = serde_json::to_string(&result)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON serialization error: {}", e)
))?;
let parsed: serde_json::Value = serde_json::from_str(&result_json)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON parsing error: {}", e)
))?;
json_to_python(py, &parsed)
})
})
}
}
#[pyclass]
pub struct PyStorage {
client: Arc<Client>,
runtime: Arc<Runtime>,
}
impl PyStorage {
fn new(client: Arc<Client>, runtime: Arc<Runtime>) -> Self {
Self { client, runtime }
}
}
#[pymethods]
impl PyStorage {
fn list_buckets(&self, py: Python<'_>) -> PyResult<PyObject> {
let client = self.client.clone();
py.allow_threads(|| {
let result = self.runtime.block_on(async {
client.storage().list_buckets().await
})?;
Python::with_gil(|py| {
let buckets_json = serde_json::to_string(&result)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON serialization error: {}", e)
))?;
let parsed: serde_json::Value = serde_json::from_str(&buckets_json)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("JSON parsing error: {}", e)
))?;
json_to_python(py, &parsed)
})
})
}
}
#[pyclass]
pub struct PyFunctions {
client: Arc<Client>,
runtime: Arc<Runtime>,
}
impl PyFunctions {
fn new(client: Arc<Client>, runtime: Arc<Runtime>) -> Self {
Self { client, runtime }
}
}
#[pymethods]
impl PyFunctions {
fn invoke(&self, py: Python<'_>, function_name: &str, payload: Option<&PyAny>) -> PyResult<String> {
let client = self.client.clone();
let function_name = function_name.to_string();
let json_payload = if let Some(payload) = payload {
Some(python_to_json(py, payload)?)
} else {
None
};
py.allow_threads(|| {
self.runtime.block_on(async {
client.functions().invoke(&function_name, json_payload).await
})
})
}
}
fn json_to_python(py: Python<'_>, value: &serde_json::Value) -> PyResult<PyObject> {
match value {
serde_json::Value::Null => Ok(py.None()),
serde_json::Value::Bool(b) => Ok(b.to_object(py)),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Ok(i.to_object(py))
} else if let Some(f) = n.as_f64() {
Ok(f.to_object(py))
} else {
Ok(n.to_string().to_object(py))
}
}
serde_json::Value::String(s) => Ok(s.to_object(py)),
serde_json::Value::Array(arr) => {
let py_list = PyList::empty(py);
for item in arr {
py_list.append(json_to_python(py, item)?)?;
}
Ok(py_list.to_object(py))
}
serde_json::Value::Object(obj) => {
let py_dict = PyDict::new(py);
for (key, value) in obj {
py_dict.set_item(key, json_to_python(py, value)?)?;
}
Ok(py_dict.to_object(py))
}
}
}
fn python_to_json(py: Python<'_>, obj: &PyAny) -> PyResult<serde_json::Value> {
if obj.is_none() {
Ok(serde_json::Value::Null)
} else if let Ok(b) = obj.extract::<bool>() {
Ok(serde_json::Value::Bool(b))
} else if let Ok(i) = obj.extract::<i64>() {
Ok(serde_json::Value::Number(serde_json::Number::from(i)))
} else if let Ok(f) = obj.extract::<f64>() {
match serde_json::Number::from_f64(f) {
Some(n) => Ok(serde_json::Value::Number(n)),
None => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid float value")),
}
} else if let Ok(s) = obj.extract::<String>() {
Ok(serde_json::Value::String(s))
} else if let Ok(list) = obj.downcast::<PyList>() {
let mut arr = Vec::new();
for item in list.iter() {
arr.push(python_to_json(py, item)?);
}
Ok(serde_json::Value::Array(arr))
} else if let Ok(dict) = obj.downcast::<PyDict>() {
let mut map = serde_json::Map::new();
for (key, value) in dict.iter() {
let key_str = key.extract::<String>()?;
map.insert(key_str, python_to_json(py, value)?);
}
Ok(serde_json::Value::Object(map))
} else {
Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"Cannot convert Python type {} to JSON",
obj.get_type().name()?
)))
}
}
#[pymodule]
fn supabase_lib_rs(py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add("SupabaseError", py.get_type::<SupabaseError>())?;
m.add_class::<PySupabaseClient>()?;
m.add_class::<PyAuth>()?;
m.add_class::<PyDatabase>()?;
m.add_class::<PyQueryBuilder>()?;
m.add_class::<PyStorage>()?;
m.add_class::<PyFunctions>()?;
m.add("Client", py.get_type::<PySupabaseClient>())?;
m.add("__version__", "0.5.3")?;
m.add("__author__", "Nick Nizovtsev")?;
m.add(
"__doc__",
"Fast and comprehensive Supabase client for Python, powered by Rust",
)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module_creation() {
pyo3::Python::with_gil(|py| {
let module = PyModule::new(py, "test_supabase").unwrap();
assert!(supabase_lib_rs(py, &module).is_ok());
});
}
#[test]
fn test_json_conversion() {
pyo3::Python::with_gil(|py| {
let json_val = serde_json::json!({
"name": "test",
"count": 42,
"active": true,
"data": [1, 2, 3]
});
let py_obj = json_to_python(py, &json_val).unwrap();
let back_to_json = python_to_json(py, py_obj.as_ref(py)).unwrap();
assert_eq!(json_val, back_to_json);
});
}
}