kailash 0.6.5__py3-none-any.whl → 0.7.0__py3-none-any.whl

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.
Files changed (64) hide show
  1. kailash/__init__.py +35 -4
  2. kailash/adapters/__init__.py +5 -0
  3. kailash/adapters/mcp_platform_adapter.py +273 -0
  4. kailash/channels/__init__.py +21 -0
  5. kailash/channels/api_channel.py +409 -0
  6. kailash/channels/base.py +271 -0
  7. kailash/channels/cli_channel.py +661 -0
  8. kailash/channels/event_router.py +496 -0
  9. kailash/channels/mcp_channel.py +648 -0
  10. kailash/channels/session.py +423 -0
  11. kailash/mcp_server/discovery.py +1 -1
  12. kailash/middleware/core/agent_ui.py +5 -0
  13. kailash/middleware/mcp/enhanced_server.py +22 -16
  14. kailash/nexus/__init__.py +21 -0
  15. kailash/nexus/factory.py +413 -0
  16. kailash/nexus/gateway.py +545 -0
  17. kailash/nodes/__init__.py +2 -0
  18. kailash/nodes/ai/iterative_llm_agent.py +988 -17
  19. kailash/nodes/ai/llm_agent.py +29 -9
  20. kailash/nodes/api/__init__.py +2 -2
  21. kailash/nodes/api/monitoring.py +1 -1
  22. kailash/nodes/base_async.py +54 -14
  23. kailash/nodes/code/async_python.py +1 -1
  24. kailash/nodes/data/bulk_operations.py +939 -0
  25. kailash/nodes/data/query_builder.py +373 -0
  26. kailash/nodes/data/query_cache.py +512 -0
  27. kailash/nodes/monitoring/__init__.py +10 -0
  28. kailash/nodes/monitoring/deadlock_detector.py +964 -0
  29. kailash/nodes/monitoring/performance_anomaly.py +1078 -0
  30. kailash/nodes/monitoring/race_condition_detector.py +1151 -0
  31. kailash/nodes/monitoring/transaction_metrics.py +790 -0
  32. kailash/nodes/monitoring/transaction_monitor.py +931 -0
  33. kailash/nodes/system/__init__.py +17 -0
  34. kailash/nodes/system/command_parser.py +820 -0
  35. kailash/nodes/transaction/__init__.py +48 -0
  36. kailash/nodes/transaction/distributed_transaction_manager.py +983 -0
  37. kailash/nodes/transaction/saga_coordinator.py +652 -0
  38. kailash/nodes/transaction/saga_state_storage.py +411 -0
  39. kailash/nodes/transaction/saga_step.py +467 -0
  40. kailash/nodes/transaction/transaction_context.py +756 -0
  41. kailash/nodes/transaction/two_phase_commit.py +978 -0
  42. kailash/nodes/transform/processors.py +17 -1
  43. kailash/nodes/validation/__init__.py +21 -0
  44. kailash/nodes/validation/test_executor.py +532 -0
  45. kailash/nodes/validation/validation_nodes.py +447 -0
  46. kailash/resources/factory.py +1 -1
  47. kailash/runtime/async_local.py +84 -21
  48. kailash/runtime/local.py +21 -2
  49. kailash/runtime/parameter_injector.py +187 -31
  50. kailash/security.py +16 -1
  51. kailash/servers/__init__.py +32 -0
  52. kailash/servers/durable_workflow_server.py +430 -0
  53. kailash/servers/enterprise_workflow_server.py +466 -0
  54. kailash/servers/gateway.py +183 -0
  55. kailash/servers/workflow_server.py +290 -0
  56. kailash/utils/data_validation.py +192 -0
  57. kailash/workflow/builder.py +291 -12
  58. kailash/workflow/validation.py +144 -8
  59. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/METADATA +1 -1
  60. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/RECORD +64 -26
  61. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/WHEEL +0 -0
  62. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/entry_points.txt +0 -0
  63. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/licenses/LICENSE +0 -0
  64. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,373 @@
1
+ """Query Builder Integration for Database Nodes.
2
+
3
+ This module provides MongoDB-style query operators for SQL databases
4
+ with database-specific optimization and type validation.
5
+
6
+ Key Features:
7
+ - MongoDB-style operators ($eq, $ne, $lt, $gte, $in, $like, etc.)
8
+ - Database-specific SQL generation
9
+ - Type validation for operators
10
+ - Query optimization
11
+ - Multi-tenant support
12
+ """
13
+
14
+ import logging
15
+ from enum import Enum
16
+ from typing import Any, Dict, List, Optional, Tuple, Union
17
+
18
+ from kailash.sdk_exceptions import NodeValidationError
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class QueryOperator(Enum):
24
+ """MongoDB-style query operators."""
25
+
26
+ # Comparison operators
27
+ EQ = "$eq" # Equal to
28
+ NE = "$ne" # Not equal to
29
+ LT = "$lt" # Less than
30
+ LTE = "$lte" # Less than or equal to
31
+ GT = "$gt" # Greater than
32
+ GTE = "$gte" # Greater than or equal to
33
+
34
+ # Array/List operators
35
+ IN = "$in" # In array
36
+ NIN = "$nin" # Not in array
37
+
38
+ # String operators
39
+ LIKE = "$like" # SQL LIKE pattern
40
+ ILIKE = "$ilike" # Case-insensitive LIKE
41
+ REGEX = "$regex" # Regular expression
42
+
43
+ # JSON/Array operators
44
+ CONTAINS = "$contains" # Array/JSON contains
45
+ CONTAINED_BY = "$contained_by" # Array/JSON contained by
46
+ HAS_KEY = "$has_key" # JSON has key
47
+
48
+ # Logical operators
49
+ AND = "$and" # Logical AND
50
+ OR = "$or" # Logical OR
51
+ NOT = "$not" # Logical NOT
52
+
53
+
54
+ class DatabaseDialect(Enum):
55
+ """Supported database dialects."""
56
+
57
+ POSTGRESQL = "postgresql"
58
+ MYSQL = "mysql"
59
+ SQLITE = "sqlite"
60
+
61
+
62
+ class QueryBuilder:
63
+ """MongoDB-style query builder for SQL databases."""
64
+
65
+ def __init__(self, dialect: DatabaseDialect = DatabaseDialect.POSTGRESQL):
66
+ """Initialize query builder with database dialect.
67
+
68
+ Args:
69
+ dialect: Database dialect for SQL generation
70
+ """
71
+ self.dialect = dialect
72
+ self.table_name: Optional[str] = None
73
+ self.tenant_id: Optional[str] = None
74
+ self.conditions: List[Dict[str, Any]] = []
75
+ self.parameters: List[Any] = []
76
+ self.parameter_counter = 0
77
+
78
+ def table(self, name: str) -> "QueryBuilder":
79
+ """Set table name."""
80
+ self.table_name = name
81
+ return self
82
+
83
+ def tenant(self, tenant_id: str) -> "QueryBuilder":
84
+ """Set tenant ID for multi-tenant queries."""
85
+ self.tenant_id = tenant_id
86
+ return self
87
+
88
+ def where(
89
+ self, field: str, operator: Union[str, QueryOperator], value: Any
90
+ ) -> "QueryBuilder":
91
+ """Add WHERE condition with MongoDB-style operator.
92
+
93
+ Args:
94
+ field: Field name
95
+ operator: Query operator
96
+ value: Value to compare against
97
+
98
+ Returns:
99
+ Self for method chaining
100
+ """
101
+ if isinstance(operator, str):
102
+ operator = QueryOperator(operator)
103
+
104
+ self._validate_operator_value(operator, value)
105
+
106
+ condition = {"field": field, "operator": operator, "value": value}
107
+
108
+ self.conditions.append(condition)
109
+ return self
110
+
111
+ def find(self, query: Dict[str, Any]) -> "QueryBuilder":
112
+ """Add conditions from MongoDB-style query object.
113
+
114
+ Args:
115
+ query: MongoDB-style query dictionary
116
+
117
+ Returns:
118
+ Self for method chaining
119
+ """
120
+ self._parse_query_object(query)
121
+ return self
122
+
123
+ def build_select(self, fields: List[str] = None) -> Tuple[str, List[Any]]:
124
+ """Build SELECT query.
125
+
126
+ Args:
127
+ fields: Fields to select (default: all)
128
+
129
+ Returns:
130
+ Tuple of (SQL query, parameters)
131
+ """
132
+ if not self.table_name:
133
+ raise NodeValidationError("Table name is required")
134
+
135
+ fields_str = ", ".join(fields) if fields else "*"
136
+ base_query = f"SELECT {fields_str} FROM {self.table_name}"
137
+
138
+ where_clause, parameters = self._build_where_clause()
139
+
140
+ if where_clause:
141
+ query = f"{base_query} WHERE {where_clause}"
142
+ else:
143
+ query = base_query
144
+
145
+ return query, parameters
146
+
147
+ def build_update(self, updates: Dict[str, Any]) -> Tuple[str, List[Any]]:
148
+ """Build UPDATE query.
149
+
150
+ Args:
151
+ updates: Fields to update
152
+
153
+ Returns:
154
+ Tuple of (SQL query, parameters)
155
+ """
156
+ if not self.table_name:
157
+ raise NodeValidationError("Table name is required")
158
+
159
+ if not updates:
160
+ raise NodeValidationError("Updates are required")
161
+
162
+ # Build SET clause
163
+ set_clauses = []
164
+ update_params = []
165
+
166
+ for field, value in updates.items():
167
+ set_clauses.append(f"{field} = ${self._next_parameter()}")
168
+ update_params.append(value)
169
+
170
+ set_clause = ", ".join(set_clauses)
171
+ base_query = f"UPDATE {self.table_name} SET {set_clause}"
172
+
173
+ # Build WHERE clause
174
+ where_clause, where_params = self._build_where_clause()
175
+
176
+ if where_clause:
177
+ query = f"{base_query} WHERE {where_clause}"
178
+ else:
179
+ query = base_query
180
+
181
+ return query, update_params + where_params
182
+
183
+ def build_delete(self) -> Tuple[str, List[Any]]:
184
+ """Build DELETE query.
185
+
186
+ Returns:
187
+ Tuple of (SQL query, parameters)
188
+ """
189
+ if not self.table_name:
190
+ raise NodeValidationError("Table name is required")
191
+
192
+ base_query = f"DELETE FROM {self.table_name}"
193
+
194
+ where_clause, parameters = self._build_where_clause()
195
+
196
+ if where_clause:
197
+ query = f"{base_query} WHERE {where_clause}"
198
+ else:
199
+ query = base_query
200
+
201
+ return query, parameters
202
+
203
+ def _parse_query_object(self, query: Dict[str, Any]) -> None:
204
+ """Parse MongoDB-style query object into conditions."""
205
+ for field, condition in query.items():
206
+ if field.startswith("$"):
207
+ # Logical operator
208
+ self._parse_logical_operator(field, condition)
209
+ else:
210
+ # Field condition
211
+ self._parse_field_condition(field, condition)
212
+
213
+ def _parse_logical_operator(self, operator: str, condition: Any) -> None:
214
+ """Parse logical operator ($and, $or, $not)."""
215
+ if operator == "$and":
216
+ if not isinstance(condition, list):
217
+ raise NodeValidationError("$and requires a list of conditions")
218
+ for sub_condition in condition:
219
+ self._parse_query_object(sub_condition)
220
+ elif operator == "$or":
221
+ if not isinstance(condition, list):
222
+ raise NodeValidationError("$or requires a list of conditions")
223
+ # For simplicity, we'll convert OR to individual conditions
224
+ # In a full implementation, we'd need to track grouping
225
+ for sub_condition in condition:
226
+ self._parse_query_object(sub_condition)
227
+ elif operator == "$not":
228
+ # Handle NOT operator
229
+ self._parse_query_object(condition)
230
+
231
+ def _parse_field_condition(self, field: str, condition: Any) -> None:
232
+ """Parse field condition."""
233
+ if isinstance(condition, dict):
234
+ # Operator-based condition
235
+ for op, value in condition.items():
236
+ if op in [e.value for e in QueryOperator]:
237
+ self.where(field, op, value)
238
+ else:
239
+ raise NodeValidationError(f"Unknown operator: {op}")
240
+ else:
241
+ # Simple equality
242
+ self.where(field, QueryOperator.EQ, condition)
243
+
244
+ def _build_where_clause(self) -> Tuple[str, List[Any]]:
245
+ """Build WHERE clause from conditions."""
246
+ clauses = []
247
+ parameters = []
248
+
249
+ # Add tenant filtering first if specified
250
+ if self.tenant_id:
251
+ clauses.append(f"tenant_id = ${self._next_parameter()}")
252
+ parameters.append(self.tenant_id)
253
+
254
+ # Add other conditions
255
+ for condition in self.conditions:
256
+ clause, params = self._build_condition_clause(condition)
257
+ clauses.append(clause)
258
+ parameters.extend(params)
259
+
260
+ if not clauses:
261
+ return "", []
262
+
263
+ where_clause = " AND ".join(clauses)
264
+
265
+ # If we have tenant filtering and other conditions, group the other conditions
266
+ if self.tenant_id and len(clauses) > 1:
267
+ other_clauses = " AND ".join(clauses[1:])
268
+ where_clause = f"{clauses[0]} AND ({other_clauses})"
269
+
270
+ return where_clause, parameters
271
+
272
+ def _build_condition_clause(
273
+ self, condition: Dict[str, Any]
274
+ ) -> Tuple[str, List[Any]]:
275
+ """Build SQL clause for a single condition."""
276
+ field = condition["field"]
277
+ operator = condition["operator"]
278
+ value = condition["value"]
279
+
280
+ if operator == QueryOperator.EQ:
281
+ return f"{field} = ${self._next_parameter()}", [value]
282
+ elif operator == QueryOperator.NE:
283
+ return f"{field} != ${self._next_parameter()}", [value]
284
+ elif operator == QueryOperator.LT:
285
+ return f"{field} < ${self._next_parameter()}", [value]
286
+ elif operator == QueryOperator.LTE:
287
+ return f"{field} <= ${self._next_parameter()}", [value]
288
+ elif operator == QueryOperator.GT:
289
+ return f"{field} > ${self._next_parameter()}", [value]
290
+ elif operator == QueryOperator.GTE:
291
+ return f"{field} >= ${self._next_parameter()}", [value]
292
+ elif operator == QueryOperator.IN:
293
+ if not isinstance(value, (list, tuple)):
294
+ raise NodeValidationError("$in requires a list or tuple")
295
+ placeholders = ", ".join([f"${self._next_parameter()}" for _ in value])
296
+ return f"{field} IN ({placeholders})", list(value)
297
+ elif operator == QueryOperator.NIN:
298
+ if not isinstance(value, (list, tuple)):
299
+ raise NodeValidationError("$nin requires a list or tuple")
300
+ placeholders = ", ".join([f"${self._next_parameter()}" for _ in value])
301
+ return f"{field} NOT IN ({placeholders})", list(value)
302
+ elif operator == QueryOperator.LIKE:
303
+ return f"{field} LIKE ${self._next_parameter()}", [value]
304
+ elif operator == QueryOperator.ILIKE:
305
+ if self.dialect == DatabaseDialect.POSTGRESQL:
306
+ return f"{field} ILIKE ${self._next_parameter()}", [value]
307
+ else:
308
+ return f"LOWER({field}) LIKE LOWER(${self._next_parameter()})", [value]
309
+ elif operator == QueryOperator.REGEX:
310
+ if self.dialect == DatabaseDialect.POSTGRESQL:
311
+ return f"{field} ~ ${self._next_parameter()}", [value]
312
+ else:
313
+ return f"{field} REGEXP ${self._next_parameter()}", [value]
314
+ elif operator == QueryOperator.CONTAINS:
315
+ if self.dialect == DatabaseDialect.POSTGRESQL:
316
+ return f"{field} @> ${self._next_parameter()}", [value]
317
+ else:
318
+ return f"JSON_CONTAINS({field}, ${self._next_parameter()})", [value]
319
+ elif operator == QueryOperator.CONTAINED_BY:
320
+ if self.dialect == DatabaseDialect.POSTGRESQL:
321
+ return f"{field} <@ ${self._next_parameter()}", [value]
322
+ else:
323
+ return f"JSON_CONTAINS(${self._next_parameter()}, {field})", [value]
324
+ elif operator == QueryOperator.HAS_KEY:
325
+ if self.dialect == DatabaseDialect.POSTGRESQL:
326
+ return f"{field} ? ${self._next_parameter()}", [value]
327
+ else:
328
+ return f"JSON_EXTRACT({field}, '$.{value}') IS NOT NULL", []
329
+ else:
330
+ raise NodeValidationError(f"Unsupported operator: {operator}")
331
+
332
+ def _validate_operator_value(self, operator: QueryOperator, value: Any) -> None:
333
+ """Validate operator-value combination."""
334
+ if operator in [QueryOperator.IN, QueryOperator.NIN]:
335
+ if not isinstance(value, (list, tuple)):
336
+ raise NodeValidationError(f"{operator.value} requires a list or tuple")
337
+ elif operator in [QueryOperator.LIKE, QueryOperator.ILIKE, QueryOperator.REGEX]:
338
+ if not isinstance(value, str):
339
+ raise NodeValidationError(f"{operator.value} requires a string value")
340
+ elif operator == QueryOperator.HAS_KEY:
341
+ if not isinstance(value, str):
342
+ raise NodeValidationError(f"{operator.value} requires a string key")
343
+
344
+ def _next_parameter(self) -> int:
345
+ """Get next parameter placeholder number."""
346
+ self.parameter_counter += 1
347
+ return self.parameter_counter
348
+
349
+ def reset(self) -> "QueryBuilder":
350
+ """Reset builder state."""
351
+ self.table_name = None
352
+ self.tenant_id = None
353
+ self.conditions = []
354
+ self.parameters = []
355
+ self.parameter_counter = 0
356
+ return self
357
+
358
+
359
+ # Factory function for creating query builders
360
+ def create_query_builder(dialect: str = "postgresql") -> QueryBuilder:
361
+ """Create a query builder for the specified database dialect.
362
+
363
+ Args:
364
+ dialect: Database dialect (postgresql, mysql, sqlite)
365
+
366
+ Returns:
367
+ QueryBuilder instance
368
+ """
369
+ try:
370
+ db_dialect = DatabaseDialect(dialect.lower())
371
+ return QueryBuilder(db_dialect)
372
+ except ValueError:
373
+ raise NodeValidationError(f"Unsupported database dialect: {dialect}")