use std::collections::HashSet;
use pyo3::prelude::*;
#[pyclass(skip_from_py_object)]
#[pyo3(name = "Finding")]
#[derive(Clone)]
pub struct Finding {
#[pyo3(get)]
pub kind: String,
#[pyo3(get)]
pub token: String,
#[pyo3(get)]
pub start: usize,
#[pyo3(get)]
pub end: usize,
#[pyo3(get)]
pub detail: String,
#[pyo3(get)]
pub reason: String,
}
#[pymethods]
impl Finding {
fn __repr__(&self) -> String {
format!(
"Finding(kind={:?}, token={:?}, start={}, end={}, detail={:?})",
self.kind, self.token, self.start, self.end, self.detail
)
}
}
impl From<crate::api::Finding> for Finding {
fn from(f: crate::api::Finding) -> Self {
let reason = f.reason();
Finding {
kind: f.kind.as_str().to_string(),
token: f.token,
start: f.start,
end: f.end,
detail: f.detail,
reason,
}
}
}
#[pyclass(skip_from_py_object)]
#[pyo3(name = "AnomalyReport")]
#[derive(Clone)]
pub struct AnomalyReport {
#[pyo3(get)]
pub anomalous: bool,
#[pyo3(get)]
pub kinds: Vec<String>,
#[pyo3(get)]
pub findings: Vec<Finding>,
#[pyo3(get)]
pub reason: Option<String>,
}
#[pymethods]
impl AnomalyReport {
fn __repr__(&self) -> String {
let kinds = self
.kinds
.iter()
.map(|k| format!("'{k}'"))
.collect::<Vec<_>>()
.join(", ");
format!(
"AnomalyReport(anomalous={}, kinds=[{kinds}])",
if self.anomalous { "True" } else { "False" }
)
}
}
impl From<crate::api::AnomalyReport> for AnomalyReport {
fn from(r: crate::api::AnomalyReport) -> Self {
AnomalyReport {
anomalous: r.anomalous,
kinds: r.kinds.iter().map(|k| k.as_str().to_string()).collect(),
findings: r.findings.into_iter().map(Finding::from).collect(),
reason: r.reason,
}
}
}
#[pyclass(frozen)]
#[pyo3(name = "Lexicon")]
pub struct Lexicon {
pub(crate) inner: HashSet<String>,
}
#[pymethods]
impl Lexicon {
#[new]
fn new(words: Vec<String>) -> Self {
Self {
inner: crate::api::lexicon(words),
}
}
fn __len__(&self) -> usize {
self.inner.len()
}
}
fn lexicon_from_py(words: Option<Bound<'_, PyAny>>) -> PyResult<HashSet<String>> {
let Some(words) = words else {
return Ok(HashSet::new());
};
let mut set = HashSet::new();
for item in words.try_iter()? {
set.insert(item?.extract::<String>()?.to_lowercase());
}
Ok(set)
}
#[pyfunction]
#[pyo3(signature = (text, lexicon=None))]
pub fn _has_anomalies(text: &str, lexicon: Option<Bound<'_, PyAny>>) -> PyResult<bool> {
let lexicon = lexicon_from_py(lexicon)?;
Ok(crate::api::has_anomalies(text, &lexicon))
}
#[pyfunction]
#[pyo3(signature = (text, lexicon=None))]
pub fn _inspect_anomalies(
text: &str,
lexicon: Option<Bound<'_, PyAny>>,
) -> PyResult<AnomalyReport> {
let lexicon = lexicon_from_py(lexicon)?;
Ok(crate::api::inspect_anomalies(text, &lexicon).into())
}
#[pyfunction]
pub fn _has_anomalies_lex(text: &str, lexicon: PyRef<'_, Lexicon>) -> bool {
crate::api::has_anomalies(text, &lexicon.inner)
}
#[pyfunction]
pub fn _inspect_anomalies_lex(text: &str, lexicon: PyRef<'_, Lexicon>) -> AnomalyReport {
crate::api::inspect_anomalies(text, &lexicon.inner).into()
}