use pyo3::create_exception;
use pyo3::exceptions::PyException;
use pyo3::prelude::*;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::runtime::Runtime;
use crate::client::TorClient;
use crate::config::Config;
use crate::error::Error;
use super::response::PyResponse;
#[allow(missing_docs)]
mod exceptions {
use super::*;
create_exception!(hypertor, HypertorError, PyException);
create_exception!(hypertor, TorBootstrapError, HypertorError);
create_exception!(hypertor, TorConnectionError, HypertorError);
create_exception!(hypertor, TorTimeoutError, HypertorError);
create_exception!(hypertor, TlsError, HypertorError);
}
pub use exceptions::*;
pub fn to_py_err(err: Error) -> PyErr {
match &err {
Error::Bootstrap { .. } => TorBootstrapError::new_err(err.to_string()),
Error::Connection { .. } | Error::Circuit { .. } => {
TorConnectionError::new_err(err.to_string())
}
Error::Timeout { .. } | Error::PoolExhausted { .. } => {
TorTimeoutError::new_err(err.to_string())
}
Error::TlsHandshake { .. } | Error::TlsConfig { .. } => TlsError::new_err(err.to_string()),
_ => HypertorError::new_err(err.to_string()),
}
}
#[pyclass(name = "Client")]
pub struct PyClient {
client: Arc<TorClient>,
runtime: Arc<Runtime>,
}
#[pymethods]
impl PyClient {
#[new]
#[pyo3(signature = (timeout=30, max_connections=10))]
fn new(timeout: u64, max_connections: usize) -> PyResult<Self> {
let runtime = Runtime::new().map_err(|e| HypertorError::new_err(e.to_string()))?;
let config = Config::builder()
.timeout(std::time::Duration::from_secs(timeout))
.max_connections(max_connections)
.build()
.map_err(to_py_err)?;
let client = runtime
.block_on(TorClient::with_config(config))
.map_err(to_py_err)?;
Ok(Self {
client: Arc::new(client),
runtime: Arc::new(runtime),
})
}
fn get(&self, url: &str) -> PyResult<PyResponse> {
let client = Arc::clone(&self.client);
let url = url.to_string();
self.runtime.block_on(async move {
let builder = client.get(&url).map_err(to_py_err)?;
let response = builder.send().await.map_err(to_py_err)?;
Ok(PyResponse::from(response))
})
}
#[pyo3(signature = (url, body=None, json=None, data=None))]
fn post(
&self,
url: &str,
body: Option<&[u8]>,
json: Option<&str>,
data: Option<HashMap<String, String>>,
) -> PyResult<PyResponse> {
let client = Arc::clone(&self.client);
let url = url.to_string();
let body = body.map(|b| b.to_vec());
let json = json.map(|s| s.to_string());
self.runtime.block_on(async move {
let mut builder = client.post(&url).map_err(to_py_err)?;
if let Some(j) = json {
builder = builder.json(&j);
} else if let Some(d) = data {
builder = builder
.form(d.iter().map(|(k, v)| (k.as_str(), v.as_str())))
.map_err(to_py_err)?;
} else if let Some(b) = body {
builder = builder.body(b);
}
let response = builder.send().await.map_err(to_py_err)?;
Ok(PyResponse::from(response))
})
}
#[pyo3(signature = (url, body=None, json=None, data=None))]
fn put(
&self,
url: &str,
body: Option<&[u8]>,
json: Option<&str>,
data: Option<HashMap<String, String>>,
) -> PyResult<PyResponse> {
let client = Arc::clone(&self.client);
let url = url.to_string();
let body = body.map(|b| b.to_vec());
let json = json.map(|s| s.to_string());
self.runtime.block_on(async move {
let mut builder = client.put(&url).map_err(to_py_err)?;
if let Some(j) = json {
builder = builder.json(&j);
} else if let Some(d) = data {
builder = builder
.form(d.iter().map(|(k, v)| (k.as_str(), v.as_str())))
.map_err(to_py_err)?;
} else if let Some(b) = body {
builder = builder.body(b);
}
let response = builder.send().await.map_err(to_py_err)?;
Ok(PyResponse::from(response))
})
}
fn delete(&self, url: &str) -> PyResult<PyResponse> {
let client = Arc::clone(&self.client);
let url = url.to_string();
self.runtime.block_on(async move {
let builder = client.delete(&url).map_err(to_py_err)?;
let response = builder.send().await.map_err(to_py_err)?;
Ok(PyResponse::from(response))
})
}
fn pool_size(&self) -> usize {
self.client.pool_size()
}
fn clear_pool(&self) {
self.client.clear_pool();
}
fn __enter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
#[pyo3(signature = (_exc_type=None, _exc_val=None, _exc_tb=None))]
fn __exit__(
&self,
_exc_type: Option<&Bound<'_, PyAny>>,
_exc_val: Option<&Bound<'_, PyAny>>,
_exc_tb: Option<&Bound<'_, PyAny>>,
) -> bool {
self.clear_pool();
false
}
}
#[pyclass(name = "AsyncClient")]
pub struct PyAsyncClient {
client: Arc<TorClient>,
#[allow(dead_code)]
runtime: Arc<Runtime>, }
#[pymethods]
impl PyAsyncClient {
#[new]
#[pyo3(signature = (timeout=30, max_connections=10))]
fn new(timeout: u64, max_connections: usize) -> PyResult<Self> {
let runtime =
tokio::runtime::Runtime::new().map_err(|e| HypertorError::new_err(e.to_string()))?;
let config = Config::builder()
.timeout(std::time::Duration::from_secs(timeout))
.max_connections(max_connections)
.build()
.map_err(to_py_err)?;
let client = runtime
.block_on(TorClient::with_config(config))
.map_err(to_py_err)?;
Ok(Self {
client: Arc::new(client),
runtime: Arc::new(runtime),
})
}
#[staticmethod]
#[pyo3(signature = (timeout=30, max_connections=10))]
fn create<'py>(
py: Python<'py>,
timeout: u64,
max_connections: usize,
) -> PyResult<Bound<'py, PyAny>> {
let runtime = Arc::new(
tokio::runtime::Runtime::new().map_err(|e| HypertorError::new_err(e.to_string()))?,
);
let rt = Arc::clone(&runtime);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let config = Config::builder()
.timeout(std::time::Duration::from_secs(timeout))
.max_connections(max_connections)
.build()
.map_err(to_py_err)?;
let client = rt
.spawn(async move { TorClient::with_config(config).await })
.await
.map_err(|e| HypertorError::new_err(e.to_string()))?
.map_err(to_py_err)?;
Ok(PyAsyncClient {
client: Arc::new(client),
runtime,
})
})
}
fn get<'py>(&self, py: Python<'py>, url: String) -> PyResult<Bound<'py, PyAny>> {
let client = Arc::clone(&self.client);
let runtime = Arc::clone(&self.runtime);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let result = runtime
.spawn(async move {
let builder = client.get(&url)?;
builder.send().await
})
.await
.map_err(|e| HypertorError::new_err(e.to_string()))?;
Ok(PyResponse::from(result.map_err(to_py_err)?))
})
}
#[pyo3(signature = (url, body=None, json=None, data=None))]
fn post<'py>(
&self,
py: Python<'py>,
url: String,
body: Option<Vec<u8>>,
json: Option<String>,
data: Option<HashMap<String, String>>,
) -> PyResult<Bound<'py, PyAny>> {
let client = Arc::clone(&self.client);
let runtime = Arc::clone(&self.runtime);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let result = runtime
.spawn(async move {
let mut builder = client.post(&url)?;
if let Some(j) = json {
builder = builder.json(&j);
} else if let Some(d) = data {
builder = builder.form(d.iter().map(|(k, v)| (k.as_str(), v.as_str())))?;
} else if let Some(b) = body {
builder = builder.body(b);
}
builder.send().await
})
.await
.map_err(|e| HypertorError::new_err(e.to_string()))?;
Ok(PyResponse::from(result.map_err(to_py_err)?))
})
}
fn pool_size(&self) -> usize {
self.client.pool_size()
}
fn __aenter__<'py>(slf: PyRef<'py, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let client = PyAsyncClient {
client: Arc::clone(&slf.client),
runtime: Arc::clone(&slf.runtime),
};
pyo3_async_runtimes::tokio::future_into_py(py, async move { Ok(client) })
}
#[pyo3(signature = (_exc_type=None, _exc_val=None, _exc_tb=None))]
fn __aexit__<'py>(
&self,
py: Python<'py>,
_exc_type: Option<&Bound<'_, PyAny>>,
_exc_val: Option<&Bound<'_, PyAny>>,
_exc_tb: Option<&Bound<'_, PyAny>>,
) -> PyResult<Bound<'py, PyAny>> {
let client = Arc::clone(&self.client);
pyo3_async_runtimes::tokio::future_into_py(py, async move {
client.clear_pool();
Ok(false)
})
}
}