#[cfg(feature = "python")]
use pyo3::exceptions::{PyRecursionError, PyRuntimeError, PyTypeError, PyValueError};
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg(feature = "python")]
use pyo3::types::{PyDict, PyList, PySet};
#[cfg(feature = "python")]
use std::collections::{HashMap, HashSet};
#[cfg(feature = "python")]
use crate::pattern::{Pattern, StructureAnalysis, ValidationError, ValidationRules};
#[cfg(feature = "python")]
use crate::subject::{RangeValue, Subject, Symbol, Value};
#[cfg(feature = "python")]
pub(crate) fn validation_error_to_python(err: &ValidationError) -> PyErr {
PyValueError::new_err(format!("Validation error: {:?}", err))
}
#[cfg(feature = "python")]
pub(crate) fn type_error_to_python(msg: &str) -> PyErr {
PyTypeError::new_err(msg.to_string())
}
#[cfg(feature = "python")]
pub(crate) fn runtime_error_to_python(msg: &str) -> PyErr {
PyRuntimeError::new_err(msg.to_string())
}
#[cfg(feature = "python")]
#[allow(dead_code)]
pub(crate) fn recursion_error_to_python(msg: &str) -> PyErr {
PyRecursionError::new_err(msg.to_string())
}
#[cfg(feature = "python")]
#[allow(clippy::only_used_in_recursion)]
fn python_to_value(py: Python, obj: &Bound<'_, PyAny>) -> PyResult<Value> {
if let Ok(s) = obj.extract::<String>() {
return Ok(Value::VString(s));
}
if let Ok(i) = obj.extract::<i64>() {
return Ok(Value::VInteger(i));
}
if let Ok(f) = obj.extract::<f64>() {
return Ok(Value::VDecimal(f));
}
if let Ok(b) = obj.extract::<bool>() {
return Ok(Value::VBoolean(b));
}
if let Ok(list) = obj.downcast::<PyList>() {
let mut vec = Vec::new();
for item in list.iter() {
vec.push(python_to_value(py, &item)?);
}
return Ok(Value::VArray(vec));
}
if let Ok(dict) = obj.downcast::<PyDict>() {
let mut map = HashMap::new();
for (key, value) in dict.iter() {
let key_str: String = key.extract()?;
let value_obj = python_to_value(py, &value)?;
map.insert(key_str, value_obj);
}
return Ok(Value::VMap(map));
}
if let Ok(value_obj) = obj.extract::<PyValue>() {
return Ok(value_obj.value.clone());
}
Err(type_error_to_python(&format!(
"Cannot convert Python object to Value: {:?}",
obj.get_type().name()?
)))
}
#[cfg(feature = "python")]
#[allow(deprecated)]
fn value_to_python(py: Python, value: &Value) -> PyResult<PyObject> {
match value {
Value::VString(s) => Ok(s.to_object(py)),
Value::VInteger(i) => Ok(i.to_object(py)),
Value::VDecimal(f) => Ok(f.to_object(py)),
Value::VBoolean(b) => Ok(b.to_object(py)),
Value::VSymbol(s) => Ok(s.to_object(py)),
Value::VTaggedString { tag, content } => {
let dict = PyDict::new_bound(py);
dict.set_item("tag", tag)?;
dict.set_item("content", content)?;
Ok(dict.to_object(py))
}
Value::VArray(vec) => {
let list = PyList::empty_bound(py);
for item in vec {
list.append(value_to_python(py, item)?.into_bound(py))?;
}
Ok(list.to_object(py))
}
Value::VMap(map) => {
let dict = PyDict::new_bound(py);
for (key, value) in map {
dict.set_item(key, value_to_python(py, value)?.into_bound(py))?;
}
Ok(dict.to_object(py))
}
Value::VRange(range) => {
let dict = PyDict::new_bound(py);
dict.set_item("lower", range.lower.to_object(py))?;
dict.set_item("upper", range.upper.to_object(py))?;
Ok(dict.to_object(py))
}
Value::VMeasurement { unit, value } => {
let dict = PyDict::new_bound(py);
dict.set_item("unit", unit)?;
dict.set_item("value", *value)?;
Ok(dict.to_object(py))
}
}
}
#[cfg(feature = "python")]
fn python_set_to_hashset(py: Python, py_set: &Bound<'_, PySet>) -> PyResult<HashSet<String>> {
let mut set = HashSet::new();
for item in py_set.iter() {
let s: String = item.extract()?;
set.insert(s);
}
Ok(set)
}
#[cfg(feature = "python")]
fn hashset_to_python_set(py: Python, set: &HashSet<String>) -> PyResult<PyObject> {
let py_set = PySet::empty_bound(py)?;
for item in set {
py_set.add(item)?;
}
Ok(py_set.to_object(py))
}
#[cfg(feature = "python")]
fn python_dict_to_value_map(
py: Python,
py_dict: &Bound<'_, PyDict>,
) -> PyResult<HashMap<String, Value>> {
let mut map = HashMap::new();
for (key, value) in py_dict.iter() {
let key_str: String = key.extract()?;
let value_obj = python_to_value(py, &value)?;
map.insert(key_str, value_obj);
}
Ok(map)
}
#[cfg(feature = "python")]
fn value_map_to_python_dict(py: Python, map: &HashMap<String, Value>) -> PyResult<PyObject> {
let dict = PyDict::new_bound(py);
for (key, value) in map {
dict.set_item(key, value_to_python(py, value)?.into_bound(py))?;
}
Ok(dict.to_object(py))
}
#[cfg(feature = "python")]
#[pyclass(name = "Value")]
#[derive(Clone)]
pub struct PyValue {
value: Value,
}
#[cfg(feature = "python")]
#[pymethods]
impl PyValue {
#[staticmethod]
fn string(s: String) -> Self {
Self {
value: Value::VString(s),
}
}
#[staticmethod]
fn int(i: i64) -> Self {
Self {
value: Value::VInteger(i),
}
}
#[staticmethod]
fn decimal(f: f64) -> Self {
Self {
value: Value::VDecimal(f),
}
}
#[staticmethod]
fn boolean(b: bool) -> Self {
Self {
value: Value::VBoolean(b),
}
}
#[staticmethod]
fn symbol(s: String) -> Self {
Self {
value: Value::VSymbol(s),
}
}
#[staticmethod]
fn array(py: Python, items: &Bound<'_, PyList>) -> PyResult<Self> {
let mut vec = Vec::new();
for item in items.iter() {
vec.push(python_to_value(py, &item)?);
}
Ok(Self {
value: Value::VArray(vec),
})
}
#[staticmethod]
fn map(py: Python, items: &Bound<'_, PyDict>) -> PyResult<Self> {
Ok(Self {
value: Value::VMap(python_dict_to_value_map(py, items)?),
})
}
#[staticmethod]
#[pyo3(signature = (lower=None, upper=None))]
fn range(lower: Option<f64>, upper: Option<f64>) -> Self {
Self {
value: Value::VRange(RangeValue { lower, upper }),
}
}
#[staticmethod]
fn measurement(value: f64, unit: String) -> Self {
Self {
value: Value::VMeasurement { unit, value },
}
}
fn as_string(&self) -> PyResult<String> {
match &self.value {
Value::VString(s) => Ok(s.clone()),
Value::VSymbol(s) => Ok(s.clone()),
_ => Err(type_error_to_python("Value is not a string or symbol")),
}
}
fn as_int(&self) -> PyResult<i64> {
match &self.value {
Value::VInteger(i) => Ok(*i),
_ => Err(type_error_to_python("Value is not an integer")),
}
}
fn as_decimal(&self) -> PyResult<f64> {
match &self.value {
Value::VDecimal(f) => Ok(*f),
_ => Err(type_error_to_python("Value is not a decimal")),
}
}
fn as_boolean(&self) -> PyResult<bool> {
match &self.value {
Value::VBoolean(b) => Ok(*b),
_ => Err(type_error_to_python("Value is not a boolean")),
}
}
fn as_array(&self, py: Python) -> PyResult<PyObject> {
match &self.value {
Value::VArray(vec) => {
let list = PyList::empty_bound(py);
for item in vec {
list.append(value_to_python(py, item)?.into_bound(py))?;
}
Ok(list.to_object(py))
}
_ => Err(type_error_to_python("Value is not an array")),
}
}
fn as_map(&self, py: Python) -> PyResult<PyObject> {
match &self.value {
Value::VMap(map) => value_map_to_python_dict(py, map),
_ => Err(type_error_to_python("Value is not a map")),
}
}
fn __repr__(&self) -> String {
format!("Value({:?})", self.value)
}
fn __str__(&self) -> String {
format!("{}", self.value)
}
}
#[cfg(feature = "python")]
#[pyclass(name = "Subject")]
#[derive(Clone)]
pub struct PySubject {
subject: Subject,
}
#[cfg(feature = "python")]
#[pymethods]
impl PySubject {
#[new]
#[pyo3(signature = (identity, labels = None, properties = None))]
fn new(
py: Python,
identity: String,
labels: Option<&Bound<'_, PySet>>,
properties: Option<&Bound<'_, PyDict>>,
) -> PyResult<Self> {
let labels_set = if let Some(labels) = labels {
python_set_to_hashset(py, labels)?
} else {
HashSet::new()
};
let properties_map = if let Some(properties) = properties {
python_dict_to_value_map(py, properties)?
} else {
HashMap::new()
};
Ok(Self {
subject: Subject {
identity: Symbol(identity),
labels: labels_set,
properties: properties_map,
},
})
}
#[getter]
fn identity(&self) -> String {
self.subject.identity.0.clone()
}
fn get_labels(&self, py: Python) -> PyResult<PyObject> {
hashset_to_python_set(py, &self.subject.labels)
}
fn get_properties(&self, py: Python) -> PyResult<PyObject> {
value_map_to_python_dict(py, &self.subject.properties)
}
fn add_label(&mut self, label: String) {
self.subject.labels.insert(label);
}
fn remove_label(&mut self, label: String) {
self.subject.labels.remove(&label);
}
fn has_label(&self, label: String) -> bool {
self.subject.labels.contains(&label)
}
fn get_property(&self, _py: Python, name: String) -> PyResult<Option<PyValue>> {
if let Some(value) = self.subject.properties.get(&name) {
Ok(Some(PyValue {
value: value.clone(),
}))
} else {
Ok(None)
}
}
fn set_property(&mut self, py: Python, name: String, value: &Bound<'_, PyAny>) -> PyResult<()> {
let rust_value = python_to_value(py, value)?;
self.subject.properties.insert(name, rust_value);
Ok(())
}
fn remove_property(&mut self, name: String) {
self.subject.properties.remove(&name);
}
fn __repr__(&self) -> String {
format!("Subject(identity={:?})", self.subject.identity.0)
}
#[staticmethod]
fn from_id(identity: String) -> PySubject {
PySubject {
subject: Subject::from_id(identity),
}
}
#[staticmethod]
fn build(identity: String) -> PySubjectBuilder {
PySubjectBuilder {
identity,
labels: Vec::new(),
properties: HashMap::new(),
}
}
}
fn collect_pattern_values(py: Python, pattern: &PyPattern, result: &mut Vec<PyObject>) {
result.push(pattern.value.clone_ref(py));
for elem in &pattern.elements {
collect_pattern_values(py, elem, result);
}
}
fn filter_pattern_recursive(
pattern: &PyPattern,
predicate: &Bound<'_, PyAny>,
result: &mut Vec<PyPattern>,
) {
let py_pattern = pattern.clone();
match predicate.call1((py_pattern.clone(),)) {
Ok(pred_result) => {
if pred_result.extract::<bool>().unwrap_or(false) {
result.push(py_pattern);
}
}
Err(_) => {}
}
for elem in &pattern.elements {
filter_pattern_recursive(elem, predicate, result);
}
}
fn find_first_pattern_recursive(
pattern: &PyPattern,
predicate: &Bound<'_, PyAny>,
) -> Option<PyPattern> {
let py_pattern = pattern.clone();
let matches = match predicate.call1((py_pattern.clone(),)) {
Ok(pred_result) => pred_result.extract::<bool>().unwrap_or(false),
Err(_) => false,
};
if matches {
return Some(py_pattern);
}
for elem in &pattern.elements {
if let Some(found) = find_first_pattern_recursive(elem, predicate) {
return Some(found);
}
}
None
}
fn indices_at_pattern_recursive(
py: Python,
pattern: &PyPattern,
path: &mut Vec<usize>,
) -> PyPattern {
let path_list = PyList::new(py, path.iter().copied()).unwrap();
let mut new_elements = Vec::new();
for (i, elem) in pattern.elements.iter().enumerate() {
path.push(i);
new_elements.push(indices_at_pattern_recursive(py, elem, path));
path.pop();
}
PyPattern {
value: path_list.into(),
elements: new_elements,
}
}
fn to_rust_pattern(pattern: &PyPattern) -> Pattern<String> {
Python::with_gil(|py| {
let value_str = pattern.value.bind(py).str().unwrap().to_string();
Pattern {
value: value_str,
elements: pattern
.elements
.iter()
.map(|e| to_rust_pattern(e))
.collect(),
}
})
}
#[cfg(feature = "python")]
#[pyclass(name = "Pattern")]
pub struct PyPattern {
value: Py<PyAny>, elements: Vec<PyPattern>,
}
#[cfg(feature = "python")]
impl Clone for PyPattern {
fn clone(&self) -> Self {
Python::with_gil(|py| Self {
value: self.value.clone_ref(py),
elements: self.elements.clone(),
})
}
}
#[cfg(feature = "python")]
#[pymethods]
impl PyPattern {
#[staticmethod]
fn point(py: Python, value: &Bound<'_, PyAny>) -> PyResult<Self> {
Ok(Self {
value: value.clone().unbind(),
elements: vec![],
})
}
#[staticmethod]
fn pattern(py: Python, value: &Bound<'_, PyAny>, elements: Vec<PyPattern>) -> PyResult<Self> {
Ok(Self {
value: value.clone().unbind(),
elements,
})
}
#[staticmethod]
fn of(py: Python, value: &Bound<'_, PyAny>) -> PyResult<Self> {
Self::point(py, value)
}
#[staticmethod]
fn from_values(py: Python, values: &Bound<'_, PyList>) -> PyResult<Vec<Self>> {
let mut patterns = Vec::new();
for item in values.iter() {
patterns.push(PyPattern::point(py, &item)?);
}
Ok(patterns)
}
#[getter]
fn value(&self, py: Python) -> PyObject {
self.value.clone_ref(py)
}
#[getter]
fn elements(&self) -> Vec<PyPattern> {
self.elements.clone()
}
fn is_atomic(&self) -> bool {
self.elements.is_empty()
}
fn length(&self) -> usize {
self.elements.len()
}
fn size(&self) -> usize {
1 + self.elements.iter().map(|e| e.size()).sum::<usize>()
}
fn depth(&self) -> usize {
if self.elements.is_empty() {
0
} else {
1 + self.elements.iter().map(|e| e.depth()).max().unwrap_or(0)
}
}
fn values(&self, py: Python) -> Vec<PyObject> {
let mut result = Vec::new();
collect_pattern_values(py, self, &mut result);
result
}
fn any_value(&self, py: Python, predicate: &Bound<'_, PyAny>) -> PyResult<bool> {
let values = self.values(py);
let result = values.iter().any(|v| {
let bound_value = v.bind(py);
match predicate.call1((bound_value,)) {
Ok(result) => result.extract::<bool>().unwrap_or(false),
Err(_) => false,
}
});
Ok(result)
}
fn all_values(&self, py: Python, predicate: &Bound<'_, PyAny>) -> PyResult<bool> {
let values = self.values(py);
let result = values.iter().all(|v| {
let bound_value = v.bind(py);
match predicate.call1((bound_value,)) {
Ok(result) => result.extract::<bool>().unwrap_or(false),
Err(_) => false,
}
});
Ok(result)
}
fn filter(&self, py: Python, predicate: &Bound<'_, PyAny>) -> PyResult<Vec<PyPattern>> {
let mut result = Vec::new();
filter_pattern_recursive(self, predicate, &mut result);
Ok(result)
}
fn find_first(&self, py: Python, predicate: &Bound<'_, PyAny>) -> PyResult<Option<PyPattern>> {
Ok(find_first_pattern_recursive(self, predicate))
}
fn matches(&self, py: Python, other: &PyPattern) -> bool {
let values_equal = Python::with_gil(|py| {
self.value
.bind(py)
.eq(other.value.bind(py))
.unwrap_or(false)
});
if !values_equal || self.elements.len() != other.elements.len() {
return false;
}
self.elements
.iter()
.zip(other.elements.iter())
.all(|(a, b)| a.matches(py, b))
}
fn contains(&self, py: Python, other: &PyPattern) -> bool {
if self.matches(py, other) {
return true;
}
self.elements.iter().any(|e| e.contains(py, other))
}
fn map(&self, py: Python, func: &Bound<'_, PyAny>) -> PyResult<PyPattern> {
let bound_value = self.value.bind(py);
let new_value = match func.call1((bound_value,)) {
Ok(result) => result.unbind(),
Err(_) => self.value.clone_ref(py),
};
let new_elements: Vec<PyPattern> = self
.elements
.iter()
.map(|e| e.map(py, func))
.collect::<PyResult<Vec<_>>>()?;
Ok(PyPattern {
value: new_value,
elements: new_elements,
})
}
fn fold(
&self,
py: Python,
init: &Bound<'_, PyAny>,
func: &Bound<'_, PyAny>,
) -> PyResult<PyObject> {
let mut acc = init.to_object(py);
for value in self.values(py) {
acc = func.call1((acc.bind(py), value.bind(py)))?.to_object(py);
}
Ok(acc)
}
fn para(&self, py: Python, func: &Bound<'_, PyAny>) -> PyResult<PyObject> {
let element_results = self
.elements
.iter()
.map(|elem| elem.para(py, func))
.collect::<PyResult<Vec<PyObject>>>()?;
let py_element_results = PyList::new(py, &element_results)?;
let pattern_view = PyPattern {
value: self.value.clone_ref(py),
elements: self.elements.clone(),
};
func.call1((pattern_view, py_element_results))
.map(|result| result.to_object(py))
}
fn combine(&self, py: Python, other: PyPattern) -> PyResult<PyPattern> {
use crate::Combinable;
let combined_value = Python::with_gil(|py| {
let left_val = self.value.bind(py);
let right_val = other.value.bind(py);
if let (Ok(left_subj), Ok(right_subj)) = (
left_val.extract::<PySubject>(),
right_val.extract::<PySubject>(),
) {
let combined_subject = left_subj.subject.combine(right_subj.subject);
return PySubject {
subject: combined_subject,
}
.into_py(py);
}
match left_val.call_method1("__add__", (right_val,)) {
Ok(result) => result.unbind(),
Err(_) => self.value.clone_ref(py),
}
});
let mut combined_elements = self.elements.clone();
combined_elements.extend(other.elements);
Ok(PyPattern {
value: combined_value,
elements: combined_elements,
})
}
#[staticmethod]
fn zip3(
py: Python,
left: Vec<PyPattern>,
right: Vec<PyPattern>,
values: &Bound<'_, PyList>,
) -> PyResult<Vec<PyPattern>> {
let mut results = Vec::new();
for ((l, r), val) in left.into_iter().zip(right).zip(values.iter()) {
results.push(PyPattern {
value: val.unbind(),
elements: vec![l, r],
});
}
Ok(results)
}
#[staticmethod]
fn zip_with(
py: Python,
left: Vec<PyPattern>,
right: Vec<PyPattern>,
value_fn: &Bound<'_, PyAny>,
) -> PyResult<Vec<PyPattern>> {
let mut results = Vec::new();
for (l, r) in left.iter().zip(right.iter()) {
let value_obj = value_fn.call1((l.clone(), r.clone()))?;
results.push(PyPattern {
value: value_obj.unbind(),
elements: vec![l.clone(), r.clone()],
});
}
Ok(results)
}
fn extract(&self, py: Python) -> PyObject {
self.value.clone_ref(py)
}
fn extend(&self, py: Python, func: &Bound<'_, PyAny>) -> PyResult<PyPattern> {
let py_pattern = PyPattern {
value: self.value.clone_ref(py),
elements: self.elements.clone(),
};
let new_value = match func.call1((py_pattern,)) {
Ok(result) => result.unbind(),
Err(_) => self.value.clone_ref(py),
};
let new_elements: Vec<PyPattern> = self
.elements
.iter()
.map(|e| e.extend(py, func))
.collect::<PyResult<Vec<_>>>()?;
Ok(PyPattern {
value: new_value,
elements: new_elements,
})
}
fn depth_at(&self, py: Python) -> PyResult<PyPattern> {
let depth = self.depth();
let new_elements: Vec<PyPattern> = self
.elements
.iter()
.map(|e| e.depth_at(py))
.collect::<PyResult<Vec<_>>>()?;
Ok(PyPattern {
value: depth.to_object(py),
elements: new_elements,
})
}
fn size_at(&self, py: Python) -> PyResult<PyPattern> {
let size = self.size();
let new_elements: Vec<PyPattern> = self
.elements
.iter()
.map(|e| e.size_at(py))
.collect::<PyResult<Vec<_>>>()?;
Ok(PyPattern {
value: size.to_object(py),
elements: new_elements,
})
}
fn indices_at(&self, py: Python) -> PyResult<PyPattern> {
Ok(indices_at_pattern_recursive(py, self, &mut Vec::new()))
}
fn validate(&self, rules: &PyValidationRules) -> PyResult<()> {
let rust_pattern = to_rust_pattern(self);
rust_pattern
.validate(&rules.rules)
.map_err(|e| validation_error_to_python(&e))?;
Ok(())
}
fn analyze_structure(&self) -> PyStructureAnalysis {
let rust_pattern = to_rust_pattern(self);
let analysis = rust_pattern.analyze_structure();
PyStructureAnalysis { analysis }
}
fn __repr__(&self, py: Python) -> PyResult<String> {
let value_repr = self.value.bind(py).repr()?.to_string();
Ok(format!(
"Pattern(value={}, elements={})",
value_repr,
self.elements.len()
))
}
}
#[cfg(feature = "python")]
#[pyclass(name = "ValidationRules")]
#[derive(Clone)]
pub struct PyValidationRules {
rules: ValidationRules,
}
#[cfg(feature = "python")]
#[pymethods]
impl PyValidationRules {
#[new]
#[pyo3(signature = (max_depth = None, max_elements = None))]
fn new(max_depth: Option<usize>, max_elements: Option<usize>) -> Self {
Self {
rules: ValidationRules {
max_depth,
max_elements,
required_fields: vec![],
},
}
}
#[getter]
fn max_depth(&self) -> Option<usize> {
self.rules.max_depth
}
#[getter]
fn max_elements(&self) -> Option<usize> {
self.rules.max_elements
}
}
#[cfg(feature = "python")]
#[pyclass(name = "ValidationError", extends = PyValueError)]
pub struct PyValidationError {
message: String,
rule: String,
location: Vec<String>,
}
#[cfg(feature = "python")]
#[pymethods]
impl PyValidationError {
#[getter]
fn message(&self) -> String {
self.message.clone()
}
#[getter]
fn rule(&self) -> String {
self.rule.clone()
}
#[getter]
fn location(&self) -> Vec<String> {
self.location.clone()
}
}
#[cfg(feature = "python")]
#[pyclass(name = "StructureAnalysis")]
#[derive(Clone)]
pub struct PyStructureAnalysis {
analysis: StructureAnalysis,
}
#[cfg(feature = "python")]
#[pymethods]
impl PyStructureAnalysis {
#[getter]
fn summary(&self) -> String {
self.analysis.summary.clone()
}
#[getter]
fn depth_distribution(&self) -> Vec<usize> {
self.analysis.depth_distribution.clone()
}
#[getter]
fn element_counts(&self) -> Vec<usize> {
self.analysis.element_counts.clone()
}
#[getter]
fn nesting_patterns(&self) -> Vec<String> {
self.analysis.nesting_patterns.clone()
}
}
#[cfg(feature = "python")]
use crate::graph::StandardGraph;
#[cfg(feature = "python")]
fn subject_pattern_to_py(py: Python, p: &Pattern<Subject>) -> PyResult<PyPattern> {
let py_subject = PySubject {
subject: p.value.clone(),
};
let value: Py<PyAny> = py_subject.into_py(py);
let elements: Vec<PyPattern> = p
.elements
.iter()
.map(|e| subject_pattern_to_py(py, e))
.collect::<PyResult<Vec<_>>>()?;
Ok(PyPattern { value, elements })
}
#[cfg(feature = "python")]
fn py_pattern_to_subject_pattern(py: Python, p: &PyPattern) -> PyResult<Pattern<Subject>> {
let subject: PySubject = p.value.extract(py)?;
let elements: Vec<Pattern<Subject>> = p
.elements
.iter()
.map(|e| py_pattern_to_subject_pattern(py, e))
.collect::<PyResult<Vec<_>>>()?;
Ok(Pattern {
value: subject.subject,
elements,
})
}
#[cfg(feature = "python")]
#[pyclass(name = "StandardGraph")]
pub struct PyStandardGraph {
inner: StandardGraph,
}
#[cfg(feature = "python")]
#[pymethods]
impl PyStandardGraph {
#[new]
fn new() -> Self {
PyStandardGraph {
inner: StandardGraph::new(),
}
}
#[staticmethod]
fn from_patterns(py: Python, patterns: &Bound<'_, PyList>) -> PyResult<Self> {
let mut subject_patterns = Vec::new();
for item in patterns.iter() {
let py_pattern: PyPattern = item.extract()?;
let subject_pattern = py_pattern_to_subject_pattern(py, &py_pattern)?;
subject_patterns.push(subject_pattern);
}
Ok(PyStandardGraph {
inner: StandardGraph::from_patterns(subject_patterns),
})
}
fn add_node<'a>(mut slf: PyRefMut<'a, Self>, subject: &PySubject) -> PyRefMut<'a, Self> {
slf.inner.add_node(subject.subject.clone());
slf
}
fn add_relationship<'a>(
mut slf: PyRefMut<'a, Self>,
subject: &PySubject,
source: &PySubject,
target: &PySubject,
) -> PyRefMut<'a, Self> {
slf.inner
.add_relationship(subject.subject.clone(), &source.subject, &target.subject);
slf
}
fn add_walk<'a>(
mut slf: PyRefMut<'a, Self>,
subject: &PySubject,
relationships: &Bound<'_, PyList>,
) -> PyResult<PyRefMut<'a, Self>> {
let subjects: Vec<Subject> = relationships
.iter()
.map(|item| {
let py_subj: PyRef<'_, PySubject> = item.extract()?;
Ok(py_subj.subject.clone())
})
.collect::<PyResult<Vec<_>>>()?;
slf.inner.add_walk(subject.subject.clone(), &subjects);
Ok(slf)
}
fn add_annotation<'a>(
mut slf: PyRefMut<'a, Self>,
subject: &PySubject,
element: &PySubject,
) -> PyRefMut<'a, Self> {
slf.inner
.add_annotation(subject.subject.clone(), &element.subject);
slf
}
fn add_pattern<'a>(
mut slf: PyRefMut<'a, Self>,
py: Python,
pattern: &PyPattern,
) -> PyResult<PyRefMut<'a, Self>> {
let subject_pattern = py_pattern_to_subject_pattern(py, pattern)?;
slf.inner.add_pattern(subject_pattern);
Ok(slf)
}
fn node(&self, py: Python, id: &str) -> PyResult<Option<PyPattern>> {
self.inner
.node(&Symbol(id.to_string()))
.map(|p| subject_pattern_to_py(py, p))
.transpose()
}
fn relationship(&self, py: Python, id: &str) -> PyResult<Option<PyPattern>> {
self.inner
.relationship(&Symbol(id.to_string()))
.map(|p| subject_pattern_to_py(py, p))
.transpose()
}
fn walk(&self, py: Python, id: &str) -> PyResult<Option<PyPattern>> {
self.inner
.walk(&Symbol(id.to_string()))
.map(|p| subject_pattern_to_py(py, p))
.transpose()
}
fn annotation(&self, py: Python, id: &str) -> PyResult<Option<PyPattern>> {
self.inner
.annotation(&Symbol(id.to_string()))
.map(|p| subject_pattern_to_py(py, p))
.transpose()
}
#[getter]
fn node_count(&self) -> usize {
self.inner.node_count()
}
#[getter]
fn relationship_count(&self) -> usize {
self.inner.relationship_count()
}
#[getter]
fn walk_count(&self) -> usize {
self.inner.walk_count()
}
#[getter]
fn annotation_count(&self) -> usize {
self.inner.annotation_count()
}
#[getter]
fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[getter]
fn has_conflicts(&self) -> bool {
self.inner.has_conflicts()
}
fn source(&self, py: Python, rel_id: &str) -> PyResult<Option<PyPattern>> {
self.inner
.source(&Symbol(rel_id.to_string()))
.map(|p| subject_pattern_to_py(py, p))
.transpose()
}
fn target(&self, py: Python, rel_id: &str) -> PyResult<Option<PyPattern>> {
self.inner
.target(&Symbol(rel_id.to_string()))
.map(|p| subject_pattern_to_py(py, p))
.transpose()
}
fn neighbors(&self, py: Python, node_id: &str) -> PyResult<Vec<PyPattern>> {
self.inner
.neighbors(&Symbol(node_id.to_string()))
.into_iter()
.map(|p| subject_pattern_to_py(py, p))
.collect()
}
fn degree(&self, node_id: &str) -> usize {
self.inner.degree(&Symbol(node_id.to_string()))
}
fn nodes(&self, py: Python) -> PyResult<Vec<(String, PyPattern)>> {
self.inner
.nodes()
.map(|(id, p)| subject_pattern_to_py(py, p).map(|pp| (id.0.clone(), pp)))
.collect()
}
fn relationships(&self, py: Python) -> PyResult<Vec<(String, PyPattern)>> {
self.inner
.relationships()
.map(|(id, p)| subject_pattern_to_py(py, p).map(|pp| (id.0.clone(), pp)))
.collect()
}
fn walks(&self, py: Python) -> PyResult<Vec<(String, PyPattern)>> {
self.inner
.walks()
.map(|(id, p)| subject_pattern_to_py(py, p).map(|pp| (id.0.clone(), pp)))
.collect()
}
fn annotations(&self, py: Python) -> PyResult<Vec<(String, PyPattern)>> {
self.inner
.annotations()
.map(|(id, p)| subject_pattern_to_py(py, p).map(|pp| (id.0.clone(), pp)))
.collect()
}
fn __repr__(&self) -> String {
format!(
"StandardGraph(nodes={}, relationships={}, walks={}, annotations={})",
self.inner.node_count(),
self.inner.relationship_count(),
self.inner.walk_count(),
self.inner.annotation_count(),
)
}
fn __len__(&self) -> usize {
self.inner.node_count()
+ self.inner.relationship_count()
+ self.inner.walk_count()
+ self.inner.annotation_count()
}
}
#[cfg(feature = "python")]
#[pyclass(name = "SubjectBuilder")]
pub struct PySubjectBuilder {
identity: String,
labels: Vec<String>,
properties: HashMap<String, Value>,
}
#[cfg(feature = "python")]
#[pymethods]
impl PySubjectBuilder {
fn label<'a>(mut slf: PyRefMut<'a, Self>, label: String) -> PyRefMut<'a, Self> {
slf.labels.push(label);
slf
}
fn property<'a>(
mut slf: PyRefMut<'a, Self>,
py: Python,
key: String,
value: &Bound<'_, PyAny>,
) -> PyResult<PyRefMut<'a, Self>> {
let rust_value = python_to_value(py, value)?;
slf.properties.insert(key, rust_value);
Ok(slf)
}
fn done(&self) -> PySubject {
PySubject {
subject: Subject {
identity: Symbol(self.identity.clone()),
labels: self.labels.iter().cloned().collect(),
properties: self.properties.clone(),
},
}
}
}
#[cfg(feature = "python")]
#[pymodule]
fn pattern_core(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyValue>()?;
m.add_class::<PySubject>()?;
m.add_class::<PyPattern>()?;
m.add_class::<PyValidationRules>()?;
m.add_class::<PyStructureAnalysis>()?;
m.add_class::<PyValidationError>()?;
m.add_class::<PyStandardGraph>()?;
m.add_class::<PySubjectBuilder>()?;
Ok(())
}