use crate::{CacheStrategy, PostgresUriRegister, UriService};
use pyo3::exceptions::{PyRuntimeError, PyValueError};
use pyo3::prelude::*;
use std::collections::HashMap;
use tokio::runtime::Runtime;
#[pyclass(name = "UriRegister")]
struct PyUriRegister {
inner: PostgresUriRegister,
rt: Runtime,
}
fn parse_cache_strategy(cache_strategy: Option<String>) -> PyResult<Option<CacheStrategy>> {
if let Some(strategy_str) = cache_strategy {
let strategy = match strategy_str.to_lowercase().as_str() {
"moka" => CacheStrategy::Moka,
"lru" => CacheStrategy::Lru,
_ => {
return Err(PyValueError::new_err(format!(
"Invalid cache_strategy '{}'. Must be 'moka' or 'lru'",
strategy_str
)))
}
};
Ok(Some(strategy))
} else {
Ok(None) }
}
fn validate_params(database_url: &str, max_connections: u32, cache_size: usize) -> PyResult<()> {
if database_url.is_empty() {
return Err(PyValueError::new_err("database_url cannot be empty"));
}
if max_connections == 0 {
return Err(PyValueError::new_err(
"max_connections must be greater than 0",
));
}
if max_connections > 10_000 {
return Err(PyValueError::new_err(
"max_connections must be 10000 or less",
));
}
if cache_size == 0 {
return Err(PyValueError::new_err("cache_size must be greater than 0"));
}
Ok(())
}
#[pymethods]
impl PyUriRegister {
#[new]
#[pyo3(signature = (database_url, table_name, max_connections, cache_size, cache_strategy=None, use_tls=None, ca_cert_path=None))]
fn new(
database_url: String,
table_name: String,
max_connections: u32,
cache_size: usize,
cache_strategy: Option<String>,
use_tls: Option<bool>,
ca_cert_path: Option<String>,
) -> PyResult<Self> {
validate_params(&database_url, max_connections, cache_size)?;
let cache_strat = parse_cache_strategy(cache_strategy)?;
let rt = Runtime::new()
.map_err(|e| PyRuntimeError::new_err(format!("Failed to create runtime: {}", e)))?;
let inner = rt
.block_on(PostgresUriRegister::new_with_cache_strategy(
&database_url,
&table_name,
max_connections,
cache_size,
cache_strat,
use_tls,
ca_cert_path.as_deref(),
))
.map_err(|e| PyRuntimeError::new_err(format!("Failed to connect: {}", e)))?;
Ok(Self { inner, rt })
}
#[staticmethod]
#[pyo3(signature = (database_url, table_name, max_connections, cache_size, cache_strategy=None, use_tls=None, ca_cert_path=None))]
#[allow(clippy::too_many_arguments)]
fn new_async<'py>(
py: Python<'py>,
database_url: String,
table_name: String,
max_connections: u32,
cache_size: usize,
cache_strategy: Option<String>,
use_tls: Option<bool>,
ca_cert_path: Option<String>,
) -> PyResult<Bound<'py, PyAny>> {
validate_params(&database_url, max_connections, cache_size)?;
let cache_strat = parse_cache_strategy(cache_strategy)?;
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let rt = Runtime::new()
.map_err(|e| PyRuntimeError::new_err(format!("Failed to create runtime: {}", e)))?;
let inner = PostgresUriRegister::new_with_cache_strategy(
&database_url,
&table_name,
max_connections,
cache_size,
cache_strat,
use_tls,
ca_cert_path.as_deref(),
)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Failed to connect: {}", e)))?;
Ok(PyUriRegister { inner, rt })
})
}
fn register_uri(&self, uri: String) -> PyResult<u64> {
self.rt
.block_on(self.inner.register_uri(&uri))
.map_err(|e| PyRuntimeError::new_err(format!("Registration failed: {}", e)))
}
fn register_uri_batch(&self, uris: Vec<String>) -> PyResult<Vec<u64>> {
self.rt
.block_on(self.inner.register_uri_batch(&uris))
.map_err(|e| PyRuntimeError::new_err(format!("Batch registration failed: {}", e)))
}
fn register_uri_batch_hashmap(&self, uris: Vec<String>) -> PyResult<HashMap<String, u64>> {
self.rt
.block_on(self.inner.register_uri_batch_hashmap(&uris))
.map_err(|e| {
PyRuntimeError::new_err(format!("Batch hashmap registration failed: {}", e))
})
}
fn stats(&self) -> PyResult<HashMap<&'static str, u64>> {
let stats = self
.rt
.block_on(self.inner.stats())
.map_err(|e| PyRuntimeError::new_err(format!("Failed to get stats: {}", e)))?;
let mut result = HashMap::new();
result.insert("total_uris", stats.total_uris);
result.insert("size_bytes", stats.size_bytes);
Ok(result)
}
fn register_uri_async<'py>(&self, py: Python<'py>, uri: String) -> PyResult<Bound<'py, PyAny>> {
let inner = self.inner.clone_inner();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let id = inner
.register_uri(&uri)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Registration failed: {}", e)))?;
Ok(id)
})
}
fn register_uri_batch_async<'py>(
&self,
py: Python<'py>,
uris: Vec<String>,
) -> PyResult<Bound<'py, PyAny>> {
let inner = self.inner.clone_inner();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let ids = inner.register_uri_batch(&uris).await.map_err(|e| {
PyRuntimeError::new_err(format!("Batch registration failed: {}", e))
})?;
Ok(ids)
})
}
fn register_uri_batch_hashmap_async<'py>(
&self,
py: Python<'py>,
uris: Vec<String>,
) -> PyResult<Bound<'py, PyAny>> {
let inner = self.inner.clone_inner();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let map = inner.register_uri_batch_hashmap(&uris).await.map_err(|e| {
PyRuntimeError::new_err(format!("Batch hashmap registration failed: {}", e))
})?;
Ok(map)
})
}
fn stats_async<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let inner = self.inner.clone_inner();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let stats = inner
.stats()
.await
.map_err(|e| PyRuntimeError::new_err(format!("Failed to get stats: {}", e)))?;
let mut result = HashMap::new();
result.insert("total_uris", stats.total_uris);
result.insert("size_bytes", stats.size_bytes);
Ok(result)
})
}
fn __repr__(&self) -> String {
"UriRegister(connected)".to_string()
}
}
#[pymodule]
fn _uri_register(m: &Bound<'_, PyModule>) -> PyResult<()> {
tracing_log::LogTracer::init().ok(); let _ = pyo3_log::try_init();
m.add_class::<PyUriRegister>()?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
m.add(
"__doc__",
"URI Register - A high-performance PostgreSQL-backed URI to ID mapping service",
)?;
Ok(())
}