swadhinbiswas WIP save-point: production hardening, H3 removal, fmt gate, and accumulated phase work · 7/15/2026
2080d40
55 lines · 1.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
use justapi_core::rate_limit::RateLimiter;
use pyo3::prelude::*;
use std::sync::Arc;

#[pyclass(name = "RateLimiter")]
pub struct PyRateLimiter {
    inner: Arc<RateLimiter>,
}

#[pymethods]
impl PyRateLimiter {
    #[staticmethod]
    pub fn new_redis<'py>(py: Python<'py>, redis_url: String) -> PyResult<Bound<'py, PyAny>> {
        let fut = async move {
            let limiter = RateLimiter::new_redis(&redis_url)
                .await
                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
            Ok(PyRateLimiter { inner: Arc::new(limiter) })
        };
        pyo3_async_runtimes::tokio::future_into_py(py, fut)
    }

    pub fn check_limit<'py>(
        &self,
        py: Python<'py>,
        key: String,
        capacity: u64,
        replenish_rate: u64,
        tokens: u64,
    ) -> PyResult<Bound<'py, PyAny>> {
        let limiter = self.inner.clone();
        let fut = async move {
            let res = limiter
                .check_limit(&key, capacity, replenish_rate, tokens)
                .await
                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
            Ok(PyRateLimitResult { allowed: res.allowed, retry_after_ms: res.retry_after_ms })
        };
        pyo3_async_runtimes::tokio::future_into_py(py, fut)
    }
}

#[pyclass(name = "RateLimitResult", get_all, from_py_object)]
#[derive(Clone)]
pub struct PyRateLimitResult {
    pub allowed: bool,
    pub retry_after_ms: u64,
}

pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<PyRateLimiter>()?;
    m.add_class::<PyRateLimitResult>()?;
    Ok(())
}