use crate::{PrefixRegistry, RetryConfig};
use pyo3::exceptions::{PyRuntimeError, PyValueError};
use pyo3::prelude::*;
use std::collections::HashMap;
use std::time::Duration;
#[pyclass(name = "PrefixRegistry")]
struct PyPrefixRegistry {
inner: PrefixRegistry,
}
#[pymethods]
impl PyPrefixRegistry {
#[staticmethod]
#[allow(clippy::new_ret_no_self)]
#[pyo3(signature = (database_url, max_connections))]
fn new<'p>(
py: Python<'p>,
database_url: String,
max_connections: u32,
) -> PyResult<Bound<'p, PyAny>> {
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",
));
}
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let registry = PrefixRegistry::new(&database_url, max_connections as usize)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Failed to connect: {}", e)))?;
Ok(PyPrefixRegistry { inner: registry })
})
}
#[staticmethod]
#[pyo3(signature = (database_url, max_connections, max_retries=5, initial_delay_ms=1000, max_delay_ms=30000))]
fn new_with_retry<'p>(
py: Python<'p>,
database_url: String,
max_connections: u32,
max_retries: u32,
initial_delay_ms: u64,
max_delay_ms: u64,
) -> PyResult<Bound<'p, PyAny>> {
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",
));
}
let retry_config = RetryConfig::new(
max_retries,
Duration::from_millis(initial_delay_ms),
Duration::from_millis(max_delay_ms),
);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let registry = PrefixRegistry::new_with_retry(
&database_url,
max_connections as usize,
retry_config,
)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Failed to connect: {}", e)))?;
Ok(PyPrefixRegistry { inner: registry })
})
}
fn store_prefix_if_new<'p>(
&self,
py: Python<'p>,
prefix: String,
uri: String,
) -> PyResult<Bound<'p, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let stored = inner
.store_prefix_if_new(&prefix, &uri)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Store failed: {}", e)))?;
Ok(stored)
})
}
fn store_prefixes_if_new<'p>(
&self,
py: Python<'p>,
prefixes: Vec<(String, String)>,
) -> PyResult<Bound<'p, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let prefix_refs: Vec<(&str, &str)> = prefixes
.iter()
.map(|(p, u)| (p.as_str(), u.as_str()))
.collect();
let result = inner
.store_prefixes_if_new(prefix_refs)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Batch store failed: {}", e)))?;
let mut dict = HashMap::new();
dict.insert("stored", result.stored);
dict.insert("skipped", result.skipped);
Ok(dict)
})
}
fn get_uri_for_prefix<'p>(&self, py: Python<'p>, prefix: String) -> PyResult<Bound<'p, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let uri = inner
.get_uri_for_prefix(&prefix)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Lookup failed: {}", e)))?;
Ok(uri)
})
}
fn get_prefix_for_uri<'p>(&self, py: Python<'p>, uri: String) -> PyResult<Bound<'p, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let prefix = inner
.get_prefix_for_uri(&uri)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Lookup failed: {}", e)))?;
Ok(prefix)
})
}
fn expand_curie<'p>(
&self,
py: Python<'p>,
prefix: String,
local_name: String,
) -> PyResult<Bound<'p, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let uri = inner
.expand_curie(&prefix, &local_name)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Expand failed: {}", e)))?;
Ok(uri)
})
}
fn get_all_prefixes<'p>(&self, py: Python<'p>) -> PyResult<Bound<'p, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let prefixes = inner.get_all_prefixes().await;
Ok(prefixes)
})
}
fn prefix_count<'p>(&self, py: Python<'p>) -> PyResult<Bound<'p, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let count = inner.prefix_count().await;
Ok(count)
})
}
fn shorten_uri<'p>(&self, py: Python<'p>, uri: String) -> PyResult<Bound<'p, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let result = inner
.shorten_uri(&uri)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Shorten failed: {}", e)))?;
Ok(result)
})
}
fn shorten_uri_or_full<'p>(&self, py: Python<'p>, uri: String) -> PyResult<Bound<'p, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let result = inner
.shorten_uri_or_full(&uri)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Shorten failed: {}", e)))?;
Ok(result)
})
}
fn shorten_uri_batch<'p>(
&self,
py: Python<'p>,
uris: Vec<String>,
) -> PyResult<Bound<'p, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let uri_refs: Vec<&str> = uris.iter().map(|s| s.as_str()).collect();
let results = inner
.shorten_uri_batch(uri_refs)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Batch shorten failed: {}", e)))?;
Ok(results)
})
}
fn expand_curie_batch<'p>(
&self,
py: Python<'p>,
curies: Vec<(String, String)>,
) -> PyResult<Bound<'p, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let curie_refs: Vec<(&str, &str)> = curies
.iter()
.map(|(p, l)| (p.as_str(), l.as_str()))
.collect();
let results = inner
.expand_curie_batch(curie_refs)
.await
.map_err(|e| PyRuntimeError::new_err(format!("Batch expand failed: {}", e)))?;
Ok(results)
})
}
fn __repr__(&self) -> String {
"PrefixRegistry(connected)".to_string()
}
}
#[pymodule]
fn _prefix_register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyPrefixRegistry>()?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
m.add(
"__doc__",
"Prefix Register - A PostgreSQL-backed namespace prefix registry for CURIE expansion",
)?;
Ok(())
}