oatdb 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
oatdb-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,364 @@
1
+ Metadata-Version: 2.1
2
+ Name: oatdb
3
+ Version: 0.4.0
4
+ Summary: Python client for OAT (Optimization and Analysis Tooling) database
5
+ License: MIT
6
+ Keywords: optimization,constraint-solving,database
7
+ Author: Rikard Olsson
8
+ Requires-Python: >=3.10,<4.0
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Dist: more-itertools (>=10.8.0,<11.0.0)
18
+ Requires-Dist: pldag (>=0.10.17,<0.11.0)
19
+ Requires-Dist: requests (>=2.31.0,<3.0.0)
20
+ Description-Content-Type: text/markdown
21
+
22
+ # OatDB Python SDK
23
+
24
+ A Python client library for interacting with the OatDB (Optimization and Analysis Tooling) database backend.
25
+
26
+ ## Features
27
+
28
+ - ✅ Full support for all 30 OatDB API functions
29
+ - ✅ Logical operations (AND, OR, XOR, NOT, IMPLY, EQUIV)
30
+ - ✅ Cardinality constraints (AtLeast, AtMost, Equal)
31
+ - ✅ Linear inequality constraints (GeLineq)
32
+ - ✅ Property management
33
+ - ✅ DAG operations (sub, sub_many, validate, ranks)
34
+ - ✅ Constraint propagation
35
+ - ✅ Optimization solver (solve, solve_many)
36
+ - ✅ Node deletion and management
37
+ - ✅ Alias support for named constraints
38
+ - ✅ Type hints for better IDE support
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install oat-python-sdk
44
+ ```
45
+
46
+ Or with Poetry:
47
+
48
+ ```bash
49
+ poetry add oat-python-sdk
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ```python
55
+ from oatdb import OatClient, set_primitive, set_property, set_and, sub, solve
56
+
57
+ # Initialize client
58
+ client = OatClient("http://localhost:7061")
59
+
60
+ # Create primitives with bounds [min, max]
61
+ x = set_primitive("x", bound=1j) # [0, 1]
62
+ y = set_primitive("y", bound=10j) # [0, 10]
63
+
64
+ # Add properties
65
+ x_name = set_property(x, "name", "Variable X")
66
+
67
+ # Create constraints
68
+ constraint = set_and([x, y], alias="my_constraint")
69
+
70
+ # Extract DAG and solve
71
+ dag = sub(constraint)
72
+ solution = solve(
73
+ dag=dag,
74
+ objective={
75
+ x: 1,
76
+ y: 2
77
+ },
78
+ assume={
79
+ # Force constraint to be true
80
+ constraint: 1+1j
81
+ },
82
+ maximize=True
83
+ )
84
+
85
+ # Execute and get results
86
+ result = client.execute(solution)
87
+ print(result)
88
+ ```
89
+
90
+ ## Core Concepts
91
+
92
+ ### Bounds
93
+
94
+ Bounds are represented as complex numbers where:
95
+ - Real part = lower bound
96
+ - Imaginary part = upper bound
97
+
98
+ ```python
99
+ # Bound [0, 1]
100
+ bound = 1j
101
+
102
+ # Bound [5, 10]
103
+ bound = 5 + 10j
104
+
105
+ # Access bounds from solution
106
+ solution_data = result[solution.out]
107
+ x_bounds = solution_data["x"] # [lower, upper] as list
108
+ ```
109
+
110
+ ### Function Calls and Execution
111
+
112
+ All operations return `FunctionCall` objects that you execute using the client:
113
+
114
+ ```python
115
+ from oatdb import OatClient, set_primitive, set_and, sub
116
+
117
+ client = OatClient("http://localhost:7061")
118
+
119
+ # Create function calls
120
+ x = set_primitive("x", bound=1j)
121
+ y = set_primitive("y", bound=1j)
122
+ constraint = set_and([x, y])
123
+
124
+ # Execute a single operation
125
+ result = client.execute(constraint)
126
+
127
+ # Execute multiple operations
128
+ dag = sub(constraint)
129
+ result = client.execute_many([x, y, constraint, dag])
130
+
131
+ # Access results by the function call's output key
132
+ dag_data = result[dag.out]
133
+ ```
134
+
135
+ ## Available Functions
136
+
137
+ All functions return `FunctionCall` objects that are executed using `client.execute()` or `client.execute_many()`.
138
+
139
+ ### Primitive Operations
140
+ - `set_primitive(id: str, bound: complex = 1j, alias: Optional[str] = None)` - Create a single primitive
141
+ - `set_primitives(ids: List[str], bound: complex = 1j)` - Create multiple primitives
142
+ - `set_property(id: Union[str, FunctionCall], property: str, value: Any)` - Set node property
143
+
144
+ ### Logical Operations
145
+ - `set_and(references: List, alias: Optional[str] = None)` - AND operation
146
+ - `set_or(references: List, alias: Optional[str] = None)` - OR operation
147
+ - `set_xor(references: List, alias: Optional[str] = None)` - XOR operation
148
+ - `set_not(references: List, alias: Optional[str] = None)` - NOT operation
149
+ - `set_imply(lhs, rhs, alias: Optional[str] = None)` - Implication (lhs → rhs)
150
+ - `set_equiv(lhs, rhs, alias: Optional[str] = None)` - Equivalence (lhs ↔ rhs)
151
+
152
+ ### Cardinality Constraints
153
+ - `set_atleast(references: List, value: int, alias: Optional[str] = None)` - At least N must be true
154
+ - `set_atmost(references: List, value: int, alias: Optional[str] = None)` - At most N must be true
155
+ - `set_equal(references: List, value: Union[int, str], alias: Optional[str] = None)` - Exactly N must be true
156
+
157
+ ### Linear Constraints
158
+ - `set_gelineq(coefficients: Dict, bias: int, alias: Optional[str] = None)` - Greater-or-equal linear inequality (ax + b >= 0)
159
+
160
+ ### DAG Operations
161
+ - `sub(root)` - Extract sub-DAG from a root node
162
+ - `sub_many(roots: List)` - Extract multiple sub-DAGs
163
+ - `get_node_ids(dag)` - Get all node IDs in a DAG
164
+ - `get_ids_from_dag(dag)` - Get all node IDs from a DAG (alternative)
165
+ - `validate(dag)` - Validate DAG structure
166
+ - `ranks(dag)` - Compute topological ranks
167
+
168
+ ### Alias Operations
169
+ - `get_id_from_alias(alias: str)` - Get node ID from alias
170
+ - `get_alias(id)` - Get alias for a node ID
171
+ - `get_aliases_from_id(id)` - Get all aliases for a node ID
172
+ - `get_ids_from_aliases(aliases: List[str])` - Get IDs for multiple aliases
173
+
174
+ ### Node Operations
175
+ - `get_node(id)` - Get a single node
176
+ - `get_nodes(ids: List)` - Get multiple nodes
177
+ - `get_property_values(property: str)` - Get all nodes with a specific property
178
+
179
+ ### Propagation
180
+ - `propagate(assignments: Dict)` - Propagate constraints with assignments
181
+ - `propagate_many(many_assignments: List[Dict])` - Propagate multiple assignment sets
182
+
183
+ ### Solver
184
+ - `solve(dag, objective: Dict, assume: Optional[Dict] = None, maximize: bool = True)` - Solve single optimization
185
+ - `solve_many(dag, objectives: List[Dict], assume: Optional[Dict] = None, maximize: bool = True)` - Solve multiple optimizations
186
+
187
+ ### Deletion
188
+ - `delete_node(id)` - Delete a single node
189
+ - `delete_sub(roots: List)` - Delete sub-DAGs from roots
190
+
191
+ ### Client Methods
192
+ - `OatClient(url: str)` - Initialize client with server URL
193
+ - `client.execute(call: FunctionCall)` - Execute a single function call
194
+ - `client.execute_many(calls: List[FunctionCall])` - Execute multiple function calls
195
+
196
+ ## Complete Example
197
+
198
+ ```python
199
+ from oatdb import (
200
+ OatClient, set_primitive, set_property, set_and, set_or,
201
+ set_imply, set_atleast, set_gelineq, sub, solve
202
+ )
203
+
204
+ # Initialize
205
+ client = OatClient("http://localhost:7061")
206
+
207
+ # Create primitives
208
+ x = set_primitive("x", bound=10j)
209
+ y = set_primitive("y", bound=10j)
210
+ z = set_primitive("z", bound=10j)
211
+
212
+ # Add metadata
213
+ x_type = set_property(x, "type", "variable")
214
+ x_priority = set_property(x, "priority", 10)
215
+
216
+ # Create constraints
217
+ and_constraint = set_and([x, y], alias="both_xy")
218
+ or_constraint = set_or([y, z])
219
+ imply_constraint = set_imply(x, y) # x → y
220
+
221
+ # Cardinality: at least 2 must be true
222
+ atleast_2 = set_atleast([x, y, z], 2)
223
+
224
+ # Linear constraint: 2x + 3y - z + 5 >= 0
225
+ linear = set_gelineq(
226
+ coefficients={x: 2, y: 3, z: -1},
227
+ bias=5
228
+ )
229
+
230
+ # Combine all constraints
231
+ root = set_and([atleast_2, linear], alias="root")
232
+
233
+ # Extract DAG
234
+ dag = sub(root)
235
+
236
+ # Solve optimization: maximize 3x + 2y + z
237
+ solution = solve(
238
+ dag=dag,
239
+ objective={
240
+ x: 3,
241
+ y: 2,
242
+ z: 1
243
+ },
244
+ assume={
245
+ root: 1+1j
246
+ },
247
+ maximize=True
248
+ )
249
+
250
+ # Execute and get results
251
+ result = client.execute(solution)
252
+
253
+ print("Solution:")
254
+ for var, bounds in result.items():
255
+ if isinstance(bounds, complex):
256
+ print(f" {var}: [{bounds.real}, {bounds.imag}]")
257
+ ```
258
+
259
+ ## Working with Aliases
260
+
261
+ ```python
262
+ from oatdb import OatClient, set_primitive, set_and, get_id_from_alias, get_alias
263
+
264
+ client = OatClient("http://localhost:7061")
265
+
266
+ # Create constraint with alias
267
+ x = set_primitive("x", bound=1j)
268
+ y = set_primitive("y", bound=1j)
269
+ constraint = set_and([x, y], alias="my_constraint")
270
+
271
+ # Execute creation
272
+ client.execute_many([x, y, constraint])
273
+
274
+ # Query by alias
275
+ id_from_alias = get_id_from_alias("my_constraint")
276
+ alias_from_id = get_alias(id_from_alias)
277
+
278
+ result = client.execute_many([id_from_alias, alias_from_id])
279
+ print(f"ID: {result[id_from_alias.out]}")
280
+ print(f"Alias: {result[alias_from_id.out]}")
281
+ ```
282
+
283
+ ## Propagation Example
284
+
285
+ ```python
286
+ from oatdb import OatClient, set_primitive, set_and, propagate
287
+
288
+ client = OatClient("http://localhost:7061")
289
+
290
+ # Create AND constraint
291
+ a = set_primitive("a", bound=1j)
292
+ b = set_primitive("b", bound=1j)
293
+ c = set_primitive("c", bound=1j)
294
+ and_gate = set_and([a, b, c], alias="and_gate")
295
+
296
+ # Propagate: if AND is true, what can we infer?
297
+ prop_result = propagate(
298
+ assignments={
299
+ a: 1+1j,
300
+ b: 1+1j,
301
+ c: 1+1j,
302
+ and_gate: 1j # Upper bound only
303
+ }
304
+ )
305
+
306
+ result = client.execute(prop_result)
307
+ # Result will show that a, b, and c must all be [1, 1]
308
+ print(f"Inferred bounds: {result}")
309
+ ```
310
+
311
+ ## Testing
312
+
313
+ ### Run the comprehensive test suite:
314
+
315
+ ```bash
316
+ cd clients/Python
317
+ python tests/test_client.py
318
+ ```
319
+
320
+ Or with Poetry:
321
+
322
+ ```bash
323
+ poetry run python tests/test_client.py
324
+ ```
325
+
326
+ **Note**: Make sure the OatDB server is running on `http://localhost:7061` before running tests.
327
+
328
+ ### Run examples:
329
+
330
+ ```bash
331
+ python examples.py
332
+ ```
333
+
334
+ ## Requirements
335
+
336
+ - Python >= 3.10
337
+ - requests >= 2.31.0
338
+
339
+ ## Development
340
+
341
+ ```bash
342
+ # Install with Poetry
343
+ poetry install
344
+
345
+ # Run tests
346
+ poetry run pytest
347
+
348
+ # Format code
349
+ poetry run black .
350
+
351
+ # Type checking
352
+ poetry run mypy oatdb
353
+ ```
354
+
355
+ ## License
356
+
357
+ MIT
358
+
359
+ ## Links
360
+
361
+ - [GitHub Repository](https://github.com/yourusername/oat-db-rust-v2)
362
+ - [OatDB Documentation](https://github.com/yourusername/oat-db-rust-v2)
363
+ - [Rust Client](https://github.com/yourusername/oat-db-rust-v2/tree/main/clients/Rust)
364
+
oatdb-0.4.0/README.md ADDED
@@ -0,0 +1,342 @@
1
+ # OatDB Python SDK
2
+
3
+ A Python client library for interacting with the OatDB (Optimization and Analysis Tooling) database backend.
4
+
5
+ ## Features
6
+
7
+ - ✅ Full support for all 30 OatDB API functions
8
+ - ✅ Logical operations (AND, OR, XOR, NOT, IMPLY, EQUIV)
9
+ - ✅ Cardinality constraints (AtLeast, AtMost, Equal)
10
+ - ✅ Linear inequality constraints (GeLineq)
11
+ - ✅ Property management
12
+ - ✅ DAG operations (sub, sub_many, validate, ranks)
13
+ - ✅ Constraint propagation
14
+ - ✅ Optimization solver (solve, solve_many)
15
+ - ✅ Node deletion and management
16
+ - ✅ Alias support for named constraints
17
+ - ✅ Type hints for better IDE support
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install oat-python-sdk
23
+ ```
24
+
25
+ Or with Poetry:
26
+
27
+ ```bash
28
+ poetry add oat-python-sdk
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```python
34
+ from oatdb import OatClient, set_primitive, set_property, set_and, sub, solve
35
+
36
+ # Initialize client
37
+ client = OatClient("http://localhost:7061")
38
+
39
+ # Create primitives with bounds [min, max]
40
+ x = set_primitive("x", bound=1j) # [0, 1]
41
+ y = set_primitive("y", bound=10j) # [0, 10]
42
+
43
+ # Add properties
44
+ x_name = set_property(x, "name", "Variable X")
45
+
46
+ # Create constraints
47
+ constraint = set_and([x, y], alias="my_constraint")
48
+
49
+ # Extract DAG and solve
50
+ dag = sub(constraint)
51
+ solution = solve(
52
+ dag=dag,
53
+ objective={
54
+ x: 1,
55
+ y: 2
56
+ },
57
+ assume={
58
+ # Force constraint to be true
59
+ constraint: 1+1j
60
+ },
61
+ maximize=True
62
+ )
63
+
64
+ # Execute and get results
65
+ result = client.execute(solution)
66
+ print(result)
67
+ ```
68
+
69
+ ## Core Concepts
70
+
71
+ ### Bounds
72
+
73
+ Bounds are represented as complex numbers where:
74
+ - Real part = lower bound
75
+ - Imaginary part = upper bound
76
+
77
+ ```python
78
+ # Bound [0, 1]
79
+ bound = 1j
80
+
81
+ # Bound [5, 10]
82
+ bound = 5 + 10j
83
+
84
+ # Access bounds from solution
85
+ solution_data = result[solution.out]
86
+ x_bounds = solution_data["x"] # [lower, upper] as list
87
+ ```
88
+
89
+ ### Function Calls and Execution
90
+
91
+ All operations return `FunctionCall` objects that you execute using the client:
92
+
93
+ ```python
94
+ from oatdb import OatClient, set_primitive, set_and, sub
95
+
96
+ client = OatClient("http://localhost:7061")
97
+
98
+ # Create function calls
99
+ x = set_primitive("x", bound=1j)
100
+ y = set_primitive("y", bound=1j)
101
+ constraint = set_and([x, y])
102
+
103
+ # Execute a single operation
104
+ result = client.execute(constraint)
105
+
106
+ # Execute multiple operations
107
+ dag = sub(constraint)
108
+ result = client.execute_many([x, y, constraint, dag])
109
+
110
+ # Access results by the function call's output key
111
+ dag_data = result[dag.out]
112
+ ```
113
+
114
+ ## Available Functions
115
+
116
+ All functions return `FunctionCall` objects that are executed using `client.execute()` or `client.execute_many()`.
117
+
118
+ ### Primitive Operations
119
+ - `set_primitive(id: str, bound: complex = 1j, alias: Optional[str] = None)` - Create a single primitive
120
+ - `set_primitives(ids: List[str], bound: complex = 1j)` - Create multiple primitives
121
+ - `set_property(id: Union[str, FunctionCall], property: str, value: Any)` - Set node property
122
+
123
+ ### Logical Operations
124
+ - `set_and(references: List, alias: Optional[str] = None)` - AND operation
125
+ - `set_or(references: List, alias: Optional[str] = None)` - OR operation
126
+ - `set_xor(references: List, alias: Optional[str] = None)` - XOR operation
127
+ - `set_not(references: List, alias: Optional[str] = None)` - NOT operation
128
+ - `set_imply(lhs, rhs, alias: Optional[str] = None)` - Implication (lhs → rhs)
129
+ - `set_equiv(lhs, rhs, alias: Optional[str] = None)` - Equivalence (lhs ↔ rhs)
130
+
131
+ ### Cardinality Constraints
132
+ - `set_atleast(references: List, value: int, alias: Optional[str] = None)` - At least N must be true
133
+ - `set_atmost(references: List, value: int, alias: Optional[str] = None)` - At most N must be true
134
+ - `set_equal(references: List, value: Union[int, str], alias: Optional[str] = None)` - Exactly N must be true
135
+
136
+ ### Linear Constraints
137
+ - `set_gelineq(coefficients: Dict, bias: int, alias: Optional[str] = None)` - Greater-or-equal linear inequality (ax + b >= 0)
138
+
139
+ ### DAG Operations
140
+ - `sub(root)` - Extract sub-DAG from a root node
141
+ - `sub_many(roots: List)` - Extract multiple sub-DAGs
142
+ - `get_node_ids(dag)` - Get all node IDs in a DAG
143
+ - `get_ids_from_dag(dag)` - Get all node IDs from a DAG (alternative)
144
+ - `validate(dag)` - Validate DAG structure
145
+ - `ranks(dag)` - Compute topological ranks
146
+
147
+ ### Alias Operations
148
+ - `get_id_from_alias(alias: str)` - Get node ID from alias
149
+ - `get_alias(id)` - Get alias for a node ID
150
+ - `get_aliases_from_id(id)` - Get all aliases for a node ID
151
+ - `get_ids_from_aliases(aliases: List[str])` - Get IDs for multiple aliases
152
+
153
+ ### Node Operations
154
+ - `get_node(id)` - Get a single node
155
+ - `get_nodes(ids: List)` - Get multiple nodes
156
+ - `get_property_values(property: str)` - Get all nodes with a specific property
157
+
158
+ ### Propagation
159
+ - `propagate(assignments: Dict)` - Propagate constraints with assignments
160
+ - `propagate_many(many_assignments: List[Dict])` - Propagate multiple assignment sets
161
+
162
+ ### Solver
163
+ - `solve(dag, objective: Dict, assume: Optional[Dict] = None, maximize: bool = True)` - Solve single optimization
164
+ - `solve_many(dag, objectives: List[Dict], assume: Optional[Dict] = None, maximize: bool = True)` - Solve multiple optimizations
165
+
166
+ ### Deletion
167
+ - `delete_node(id)` - Delete a single node
168
+ - `delete_sub(roots: List)` - Delete sub-DAGs from roots
169
+
170
+ ### Client Methods
171
+ - `OatClient(url: str)` - Initialize client with server URL
172
+ - `client.execute(call: FunctionCall)` - Execute a single function call
173
+ - `client.execute_many(calls: List[FunctionCall])` - Execute multiple function calls
174
+
175
+ ## Complete Example
176
+
177
+ ```python
178
+ from oatdb import (
179
+ OatClient, set_primitive, set_property, set_and, set_or,
180
+ set_imply, set_atleast, set_gelineq, sub, solve
181
+ )
182
+
183
+ # Initialize
184
+ client = OatClient("http://localhost:7061")
185
+
186
+ # Create primitives
187
+ x = set_primitive("x", bound=10j)
188
+ y = set_primitive("y", bound=10j)
189
+ z = set_primitive("z", bound=10j)
190
+
191
+ # Add metadata
192
+ x_type = set_property(x, "type", "variable")
193
+ x_priority = set_property(x, "priority", 10)
194
+
195
+ # Create constraints
196
+ and_constraint = set_and([x, y], alias="both_xy")
197
+ or_constraint = set_or([y, z])
198
+ imply_constraint = set_imply(x, y) # x → y
199
+
200
+ # Cardinality: at least 2 must be true
201
+ atleast_2 = set_atleast([x, y, z], 2)
202
+
203
+ # Linear constraint: 2x + 3y - z + 5 >= 0
204
+ linear = set_gelineq(
205
+ coefficients={x: 2, y: 3, z: -1},
206
+ bias=5
207
+ )
208
+
209
+ # Combine all constraints
210
+ root = set_and([atleast_2, linear], alias="root")
211
+
212
+ # Extract DAG
213
+ dag = sub(root)
214
+
215
+ # Solve optimization: maximize 3x + 2y + z
216
+ solution = solve(
217
+ dag=dag,
218
+ objective={
219
+ x: 3,
220
+ y: 2,
221
+ z: 1
222
+ },
223
+ assume={
224
+ root: 1+1j
225
+ },
226
+ maximize=True
227
+ )
228
+
229
+ # Execute and get results
230
+ result = client.execute(solution)
231
+
232
+ print("Solution:")
233
+ for var, bounds in result.items():
234
+ if isinstance(bounds, complex):
235
+ print(f" {var}: [{bounds.real}, {bounds.imag}]")
236
+ ```
237
+
238
+ ## Working with Aliases
239
+
240
+ ```python
241
+ from oatdb import OatClient, set_primitive, set_and, get_id_from_alias, get_alias
242
+
243
+ client = OatClient("http://localhost:7061")
244
+
245
+ # Create constraint with alias
246
+ x = set_primitive("x", bound=1j)
247
+ y = set_primitive("y", bound=1j)
248
+ constraint = set_and([x, y], alias="my_constraint")
249
+
250
+ # Execute creation
251
+ client.execute_many([x, y, constraint])
252
+
253
+ # Query by alias
254
+ id_from_alias = get_id_from_alias("my_constraint")
255
+ alias_from_id = get_alias(id_from_alias)
256
+
257
+ result = client.execute_many([id_from_alias, alias_from_id])
258
+ print(f"ID: {result[id_from_alias.out]}")
259
+ print(f"Alias: {result[alias_from_id.out]}")
260
+ ```
261
+
262
+ ## Propagation Example
263
+
264
+ ```python
265
+ from oatdb import OatClient, set_primitive, set_and, propagate
266
+
267
+ client = OatClient("http://localhost:7061")
268
+
269
+ # Create AND constraint
270
+ a = set_primitive("a", bound=1j)
271
+ b = set_primitive("b", bound=1j)
272
+ c = set_primitive("c", bound=1j)
273
+ and_gate = set_and([a, b, c], alias="and_gate")
274
+
275
+ # Propagate: if AND is true, what can we infer?
276
+ prop_result = propagate(
277
+ assignments={
278
+ a: 1+1j,
279
+ b: 1+1j,
280
+ c: 1+1j,
281
+ and_gate: 1j # Upper bound only
282
+ }
283
+ )
284
+
285
+ result = client.execute(prop_result)
286
+ # Result will show that a, b, and c must all be [1, 1]
287
+ print(f"Inferred bounds: {result}")
288
+ ```
289
+
290
+ ## Testing
291
+
292
+ ### Run the comprehensive test suite:
293
+
294
+ ```bash
295
+ cd clients/Python
296
+ python tests/test_client.py
297
+ ```
298
+
299
+ Or with Poetry:
300
+
301
+ ```bash
302
+ poetry run python tests/test_client.py
303
+ ```
304
+
305
+ **Note**: Make sure the OatDB server is running on `http://localhost:7061` before running tests.
306
+
307
+ ### Run examples:
308
+
309
+ ```bash
310
+ python examples.py
311
+ ```
312
+
313
+ ## Requirements
314
+
315
+ - Python >= 3.10
316
+ - requests >= 2.31.0
317
+
318
+ ## Development
319
+
320
+ ```bash
321
+ # Install with Poetry
322
+ poetry install
323
+
324
+ # Run tests
325
+ poetry run pytest
326
+
327
+ # Format code
328
+ poetry run black .
329
+
330
+ # Type checking
331
+ poetry run mypy oatdb
332
+ ```
333
+
334
+ ## License
335
+
336
+ MIT
337
+
338
+ ## Links
339
+
340
+ - [GitHub Repository](https://github.com/yourusername/oat-db-rust-v2)
341
+ - [OatDB Documentation](https://github.com/yourusername/oat-db-rust-v2)
342
+ - [Rust Client](https://github.com/yourusername/oat-db-rust-v2/tree/main/clients/Rust)