Skip to main content

ommx_pyo3_bridge/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod protocol;
4
5use pyo3::{prelude::*, types::PyAny};
6use pyo3_stub_gen::{PyStubType, TypeInfo};
7
8/// Output wrapper converting a Rust [`ommx::Function`] into `ommx.Function`.
9///
10/// The function is intrinsic data and needs no owner-side context.
11#[derive(Debug, Clone)]
12pub struct PyFunction(ommx::Function);
13
14impl PyFunction {
15    /// Create a Python output wrapper for `function`.
16    pub fn new(function: ommx::Function) -> Self {
17        Self(function)
18    }
19}
20
21impl From<ommx::Function> for PyFunction {
22    fn from(function: ommx::Function) -> Self {
23        Self::new(function)
24    }
25}
26
27impl<'py> IntoPyObject<'py> for PyFunction {
28    type Target = PyAny;
29    type Output = Bound<'py, PyAny>;
30    type Error = PyErr;
31
32    fn into_pyobject(self, py: Python<'py>) -> PyResult<Self::Output> {
33        protocol::v0::function_into_py(self.0, py)
34    }
35}
36
37impl PyStubType for PyFunction {
38    fn type_output() -> TypeInfo {
39        TypeInfo::with_module("ommx.Function", "ommx".into())
40    }
41}
42
43/// Output wrapper converting a detached Rust constraint into `ommx.Constraint`.
44///
45/// A detached constraint consists of its intrinsic row and its complete
46/// [`ommx::ConstraintContext`], including its modeling label and provenance.
47/// Its collection-owned constraint ID is intentionally not part of this type.
48#[derive(Debug, Clone)]
49pub struct PyConstraint {
50    constraint: ommx::Constraint,
51    context: ommx::ConstraintContext,
52}
53
54impl PyConstraint {
55    /// Create a Python output wrapper from the complete detached constraint.
56    pub fn new(constraint: ommx::Constraint, context: ommx::ConstraintContext) -> Self {
57        Self {
58            constraint,
59            context,
60        }
61    }
62}
63
64impl From<(ommx::Constraint, ommx::ConstraintContext)> for PyConstraint {
65    fn from((constraint, context): (ommx::Constraint, ommx::ConstraintContext)) -> Self {
66        Self::new(constraint, context)
67    }
68}
69
70impl<'py> IntoPyObject<'py> for PyConstraint {
71    type Target = PyAny;
72    type Output = Bound<'py, PyAny>;
73    type Error = PyErr;
74
75    fn into_pyobject(self, py: Python<'py>) -> PyResult<Self::Output> {
76        protocol::v0::constraint_into_py(self.constraint, self.context, py)
77    }
78}
79
80impl PyStubType for PyConstraint {
81    fn type_output() -> TypeInfo {
82        TypeInfo::with_module("ommx.Constraint", "ommx".into())
83    }
84}
85
86/// Output wrapper converting a detached Rust decision variable into
87/// `ommx.DecisionVariable`.
88///
89/// The variable ID and modeling label are supplied explicitly because they
90/// are owned by an enclosing decision-variable table in the Rust SDK. Fixed
91/// values remain instance-owned and are intentionally not transferred.
92#[derive(Debug, Clone)]
93pub struct PyDecisionVariable {
94    id: ommx::VariableID,
95    decision_variable: ommx::DecisionVariable,
96    label: ommx::ModelingLabel,
97}
98
99impl PyDecisionVariable {
100    /// Create a Python output wrapper from the complete detached variable.
101    pub fn new(
102        id: ommx::VariableID,
103        decision_variable: ommx::DecisionVariable,
104        label: ommx::ModelingLabel,
105    ) -> Self {
106        Self {
107            id,
108            decision_variable,
109            label,
110        }
111    }
112}
113
114impl
115    From<(
116        ommx::VariableID,
117        ommx::DecisionVariable,
118        ommx::ModelingLabel,
119    )> for PyDecisionVariable
120{
121    fn from(
122        (id, decision_variable, label): (
123            ommx::VariableID,
124            ommx::DecisionVariable,
125            ommx::ModelingLabel,
126        ),
127    ) -> Self {
128        Self::new(id, decision_variable, label)
129    }
130}
131
132impl<'py> IntoPyObject<'py> for PyDecisionVariable {
133    type Target = PyAny;
134    type Output = Bound<'py, PyAny>;
135    type Error = PyErr;
136
137    fn into_pyobject(self, py: Python<'py>) -> PyResult<Self::Output> {
138        protocol::v0::decision_variable_into_py(self.id, self.decision_variable, self.label, py)
139    }
140}
141
142impl PyStubType for PyDecisionVariable {
143    fn type_output() -> TypeInfo {
144        TypeInfo::with_module("ommx.DecisionVariable", "ommx".into())
145    }
146}
147
148/// Output wrapper converting a Rust [`ommx::Instance`] into `ommx.Instance`.
149///
150/// Instances already have a public, owner-complete v2 root serialization, so
151/// this wrapper uses `ommx.Instance.from_v2_bytes` rather than a component-only
152/// reconstruction endpoint.
153#[derive(Debug, Clone)]
154pub struct PyInstance(ommx::Instance);
155
156impl PyInstance {
157    /// Create a Python output wrapper for `instance`.
158    pub fn new(instance: ommx::Instance) -> Self {
159        Self(instance)
160    }
161}
162
163impl From<ommx::Instance> for PyInstance {
164    fn from(instance: ommx::Instance) -> Self {
165        Self::new(instance)
166    }
167}
168
169impl<'py> IntoPyObject<'py> for PyInstance {
170    type Target = PyAny;
171    type Output = Bound<'py, PyAny>;
172    type Error = PyErr;
173
174    fn into_pyobject(self, py: Python<'py>) -> PyResult<Self::Output> {
175        protocol::v0::instance_into_py(self.0, py)
176    }
177}
178
179impl PyStubType for PyInstance {
180    fn type_output() -> TypeInfo {
181        TypeInfo::with_module("ommx.Instance", "ommx".into())
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn stub_types_are_canonical_top_level_ommx_classes() {
191        assert_eq!(PyFunction::type_output().name, "ommx.Function");
192        assert_eq!(PyConstraint::type_output().name, "ommx.Constraint");
193        assert_eq!(
194            PyDecisionVariable::type_output().name,
195            "ommx.DecisionVariable"
196        );
197        assert_eq!(PyInstance::type_output().name, "ommx.Instance");
198    }
199}