kailash 0.6.3__py3-none-any.whl → 0.6.5__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 (122) hide show
  1. kailash/__init__.py +3 -3
  2. kailash/api/custom_nodes_secure.py +3 -3
  3. kailash/api/gateway.py +1 -1
  4. kailash/api/studio.py +1 -1
  5. kailash/api/workflow_api.py +2 -2
  6. kailash/core/resilience/bulkhead.py +475 -0
  7. kailash/core/resilience/circuit_breaker.py +92 -10
  8. kailash/core/resilience/health_monitor.py +578 -0
  9. kailash/edge/discovery.py +86 -0
  10. kailash/mcp_server/__init__.py +309 -33
  11. kailash/mcp_server/advanced_features.py +1022 -0
  12. kailash/mcp_server/ai_registry_server.py +27 -2
  13. kailash/mcp_server/auth.py +789 -0
  14. kailash/mcp_server/client.py +645 -378
  15. kailash/mcp_server/discovery.py +1593 -0
  16. kailash/mcp_server/errors.py +673 -0
  17. kailash/mcp_server/oauth.py +1727 -0
  18. kailash/mcp_server/protocol.py +1126 -0
  19. kailash/mcp_server/registry_integration.py +587 -0
  20. kailash/mcp_server/server.py +1228 -96
  21. kailash/mcp_server/transports.py +1169 -0
  22. kailash/mcp_server/utils/__init__.py +6 -1
  23. kailash/mcp_server/utils/cache.py +250 -7
  24. kailash/middleware/auth/auth_manager.py +3 -3
  25. kailash/middleware/communication/api_gateway.py +1 -1
  26. kailash/middleware/communication/realtime.py +1 -1
  27. kailash/middleware/mcp/enhanced_server.py +1 -1
  28. kailash/nodes/__init__.py +2 -0
  29. kailash/nodes/admin/audit_log.py +6 -6
  30. kailash/nodes/admin/permission_check.py +8 -8
  31. kailash/nodes/admin/role_management.py +32 -28
  32. kailash/nodes/admin/schema.sql +6 -1
  33. kailash/nodes/admin/schema_manager.py +13 -13
  34. kailash/nodes/admin/security_event.py +15 -15
  35. kailash/nodes/admin/tenant_isolation.py +3 -3
  36. kailash/nodes/admin/transaction_utils.py +3 -3
  37. kailash/nodes/admin/user_management.py +21 -21
  38. kailash/nodes/ai/a2a.py +11 -11
  39. kailash/nodes/ai/ai_providers.py +9 -12
  40. kailash/nodes/ai/embedding_generator.py +13 -14
  41. kailash/nodes/ai/intelligent_agent_orchestrator.py +19 -19
  42. kailash/nodes/ai/iterative_llm_agent.py +2 -2
  43. kailash/nodes/ai/llm_agent.py +210 -33
  44. kailash/nodes/ai/self_organizing.py +2 -2
  45. kailash/nodes/alerts/discord.py +4 -4
  46. kailash/nodes/api/graphql.py +6 -6
  47. kailash/nodes/api/http.py +10 -10
  48. kailash/nodes/api/rate_limiting.py +4 -4
  49. kailash/nodes/api/rest.py +15 -15
  50. kailash/nodes/auth/mfa.py +3 -3
  51. kailash/nodes/auth/risk_assessment.py +2 -2
  52. kailash/nodes/auth/session_management.py +5 -5
  53. kailash/nodes/auth/sso.py +143 -0
  54. kailash/nodes/base.py +8 -2
  55. kailash/nodes/base_async.py +16 -2
  56. kailash/nodes/base_with_acl.py +2 -2
  57. kailash/nodes/cache/__init__.py +9 -0
  58. kailash/nodes/cache/cache.py +1172 -0
  59. kailash/nodes/cache/cache_invalidation.py +874 -0
  60. kailash/nodes/cache/redis_pool_manager.py +595 -0
  61. kailash/nodes/code/async_python.py +2 -1
  62. kailash/nodes/code/python.py +194 -30
  63. kailash/nodes/compliance/data_retention.py +6 -6
  64. kailash/nodes/compliance/gdpr.py +5 -5
  65. kailash/nodes/data/__init__.py +10 -0
  66. kailash/nodes/data/async_sql.py +1956 -129
  67. kailash/nodes/data/optimistic_locking.py +906 -0
  68. kailash/nodes/data/readers.py +8 -8
  69. kailash/nodes/data/redis.py +378 -0
  70. kailash/nodes/data/sql.py +314 -3
  71. kailash/nodes/data/streaming.py +21 -0
  72. kailash/nodes/enterprise/__init__.py +8 -0
  73. kailash/nodes/enterprise/audit_logger.py +285 -0
  74. kailash/nodes/enterprise/batch_processor.py +22 -3
  75. kailash/nodes/enterprise/data_lineage.py +1 -1
  76. kailash/nodes/enterprise/mcp_executor.py +205 -0
  77. kailash/nodes/enterprise/service_discovery.py +150 -0
  78. kailash/nodes/enterprise/tenant_assignment.py +108 -0
  79. kailash/nodes/logic/async_operations.py +2 -2
  80. kailash/nodes/logic/convergence.py +1 -1
  81. kailash/nodes/logic/operations.py +1 -1
  82. kailash/nodes/monitoring/__init__.py +11 -1
  83. kailash/nodes/monitoring/health_check.py +456 -0
  84. kailash/nodes/monitoring/log_processor.py +817 -0
  85. kailash/nodes/monitoring/metrics_collector.py +627 -0
  86. kailash/nodes/monitoring/performance_benchmark.py +137 -11
  87. kailash/nodes/rag/advanced.py +7 -7
  88. kailash/nodes/rag/agentic.py +49 -2
  89. kailash/nodes/rag/conversational.py +3 -3
  90. kailash/nodes/rag/evaluation.py +3 -3
  91. kailash/nodes/rag/federated.py +3 -3
  92. kailash/nodes/rag/graph.py +3 -3
  93. kailash/nodes/rag/multimodal.py +3 -3
  94. kailash/nodes/rag/optimized.py +5 -5
  95. kailash/nodes/rag/privacy.py +3 -3
  96. kailash/nodes/rag/query_processing.py +6 -6
  97. kailash/nodes/rag/realtime.py +1 -1
  98. kailash/nodes/rag/registry.py +1 -1
  99. kailash/nodes/rag/router.py +1 -1
  100. kailash/nodes/rag/similarity.py +7 -7
  101. kailash/nodes/rag/strategies.py +4 -4
  102. kailash/nodes/security/abac_evaluator.py +6 -6
  103. kailash/nodes/security/behavior_analysis.py +5 -5
  104. kailash/nodes/security/credential_manager.py +1 -1
  105. kailash/nodes/security/rotating_credentials.py +11 -11
  106. kailash/nodes/security/threat_detection.py +8 -8
  107. kailash/nodes/testing/credential_testing.py +2 -2
  108. kailash/nodes/transform/processors.py +5 -5
  109. kailash/runtime/local.py +163 -9
  110. kailash/runtime/parameter_injection.py +425 -0
  111. kailash/runtime/parameter_injector.py +657 -0
  112. kailash/runtime/testing.py +2 -2
  113. kailash/testing/fixtures.py +2 -2
  114. kailash/workflow/builder.py +99 -14
  115. kailash/workflow/builder_improvements.py +207 -0
  116. kailash/workflow/input_handling.py +170 -0
  117. {kailash-0.6.3.dist-info → kailash-0.6.5.dist-info}/METADATA +22 -9
  118. {kailash-0.6.3.dist-info → kailash-0.6.5.dist-info}/RECORD +122 -95
  119. {kailash-0.6.3.dist-info → kailash-0.6.5.dist-info}/WHEEL +0 -0
  120. {kailash-0.6.3.dist-info → kailash-0.6.5.dist-info}/entry_points.txt +0 -0
  121. {kailash-0.6.3.dist-info → kailash-0.6.5.dist-info}/licenses/LICENSE +0 -0
  122. {kailash-0.6.3.dist-info → kailash-0.6.5.dist-info}/top_level.txt +0 -0
@@ -105,7 +105,7 @@ class CSVReaderNode(Node):
105
105
  Examples:
106
106
  >>> # Basic CSV reading with headers
107
107
  >>> reader = CSVReaderNode()
108
- >>> result = reader.run(
108
+ >>> result = reader.execute(
109
109
  ... file_path="customers.csv",
110
110
  ... headers=True
111
111
  ... )
@@ -118,28 +118,28 @@ class CSVReaderNode(Node):
118
118
  >>> # ]
119
119
  >>>
120
120
  >>> # Reading with custom delimiter
121
- >>> result = reader.run(
121
+ >>> result = reader.execute(
122
122
  ... file_path="data.tsv",
123
123
  ... delimiter="\\t",
124
124
  ... headers=True
125
125
  ... )
126
126
  >>>
127
127
  >>> # Reading without headers (returns list of lists)
128
- >>> result = reader.run(
128
+ >>> result = reader.execute(
129
129
  ... file_path="data.csv",
130
130
  ... headers=False
131
131
  ... )
132
132
  >>> assert all(isinstance(row, list) for row in result["data"])
133
133
  >>>
134
134
  >>> # Reading with specific encoding
135
- >>> result = reader.run(
135
+ >>> result = reader.execute(
136
136
  ... file_path="european_data.csv",
137
137
  ... encoding="iso-8859-1",
138
138
  ... headers=True
139
139
  ... )
140
140
  >>>
141
141
  >>> # Handling quoted fields
142
- >>> result = reader.run(
142
+ >>> result = reader.execute(
143
143
  ... file_path="complex.csv",
144
144
  ... headers=True,
145
145
  ... quotechar='"'
@@ -349,7 +349,7 @@ class CSVReaderNode(Node):
349
349
  import aiofiles
350
350
  except ImportError:
351
351
  # Fallback to sync version if async dependencies not available
352
- return self.run(**kwargs)
352
+ return self.execute(**kwargs)
353
353
 
354
354
  file_path = kwargs.get("file_path")
355
355
  encoding = kwargs.get("encoding", "utf-8")
@@ -591,7 +591,7 @@ class JSONReaderNode(Node):
591
591
  import aiofiles
592
592
  except ImportError:
593
593
  # Fallback to sync version if async dependencies not available
594
- return self.run(**kwargs)
594
+ return self.execute(**kwargs)
595
595
 
596
596
  file_path = kwargs.get("file_path") or self.config.get("file_path")
597
597
 
@@ -808,7 +808,7 @@ class DocumentProcessorNode(Node):
808
808
  ... extract_metadata=True,
809
809
  ... preserve_structure=True
810
810
  ... )
811
- >>> result = processor.run(
811
+ >>> result = processor.execute(
812
812
  ... file_path="document.pdf"
813
813
  ... )
814
814
  >>> content = result["content"]
@@ -0,0 +1,378 @@
1
+ """Redis node for data operations.
2
+
3
+ This module provides a Redis node for performing various Redis operations
4
+ including get, set, hget, hset, hgetall, and more.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ from typing import Any, Dict, List, Optional, Union
10
+
11
+ import redis
12
+
13
+ from kailash.nodes.base import Node, NodeParameter, register_node
14
+ from kailash.sdk_exceptions import NodeExecutionError
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @register_node()
20
+ class RedisNode(Node):
21
+ """Node for Redis operations.
22
+
23
+ Supports common Redis operations including:
24
+ - String operations: get, set, delete
25
+ - Hash operations: hget, hset, hgetall
26
+ - List operations: lpush, rpush, lrange
27
+ - Set operations: sadd, smembers
28
+ - Sorted set operations: zadd, zrange
29
+
30
+ Example:
31
+ >>> # String operations
32
+ >>> redis_node = RedisNode(
33
+ ... host="localhost",
34
+ ... port=6379,
35
+ ... operation="set",
36
+ ... key="user:123",
37
+ ... value="John Doe"
38
+ ... )
39
+ >>> result = redis_node.execute()
40
+ >>> # result = {"success": True, "result": "OK"}
41
+
42
+ >>> # Hash operations
43
+ >>> redis_node = RedisNode(
44
+ ... host="localhost",
45
+ ... port=6379,
46
+ ... operation="hgetall",
47
+ ... key="user:123:profile"
48
+ ... )
49
+ >>> result = redis_node.execute()
50
+ >>> # result = {"name": "John", "age": "30", "city": "NYC"}
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ host: str = "localhost",
56
+ port: int = 6379,
57
+ db: int = 0,
58
+ password: Optional[str] = None,
59
+ operation: str = "get",
60
+ key: Optional[str] = None,
61
+ value: Optional[Any] = None,
62
+ field: Optional[str] = None,
63
+ fields: Optional[List[str]] = None,
64
+ ttl: Optional[int] = None,
65
+ decode_responses: bool = True,
66
+ **kwargs,
67
+ ):
68
+ """Initialize Redis node.
69
+
70
+ Args:
71
+ host: Redis host
72
+ port: Redis port
73
+ db: Redis database number
74
+ password: Redis password (if required)
75
+ operation: Operation to perform (get, set, hget, hset, etc.)
76
+ key: Redis key
77
+ value: Value for set operations
78
+ field: Field name for hash operations
79
+ fields: Multiple fields for hash operations
80
+ ttl: Time to live in seconds
81
+ decode_responses: Whether to decode byte responses to strings
82
+ **kwargs: Additional parameters
83
+ """
84
+ super().__init__(**kwargs)
85
+
86
+ # Store all parameters in config for validation
87
+ self.config.update(
88
+ {
89
+ "host": host,
90
+ "port": port,
91
+ "db": db,
92
+ "password": password,
93
+ "operation": operation,
94
+ "key": key,
95
+ "value": value,
96
+ "field": field,
97
+ "fields": fields,
98
+ "ttl": ttl,
99
+ "decode_responses": decode_responses,
100
+ }
101
+ )
102
+
103
+ # Also store as instance attributes for backward compatibility
104
+ self.host = host
105
+ self.port = port
106
+ self.db = db
107
+ self.password = password
108
+ self.operation = operation
109
+ self._client = None # Initialize to avoid __del__ error
110
+ self.key = key
111
+ self.value = value
112
+ self.field = field
113
+ self.fields = fields
114
+ self.ttl = ttl
115
+ self.decode_responses = decode_responses
116
+
117
+ @classmethod
118
+ def get_parameters(cls) -> Dict[str, NodeParameter]:
119
+ """Define node parameters."""
120
+ return {
121
+ "host": NodeParameter(
122
+ name="host",
123
+ type=str,
124
+ required=False,
125
+ default="localhost",
126
+ description="Redis host",
127
+ ),
128
+ "port": NodeParameter(
129
+ name="port",
130
+ type=int,
131
+ required=False,
132
+ default=6379,
133
+ description="Redis port",
134
+ ),
135
+ "db": NodeParameter(
136
+ name="db",
137
+ type=int,
138
+ required=False,
139
+ default=0,
140
+ description="Redis database number",
141
+ ),
142
+ "password": NodeParameter(
143
+ name="password", type=str, required=False, description="Redis password"
144
+ ),
145
+ "operation": NodeParameter(
146
+ name="operation",
147
+ type=str,
148
+ required=True,
149
+ description="Redis operation (get, set, hget, hset, hgetall, etc.)",
150
+ ),
151
+ "key": NodeParameter(
152
+ name="key",
153
+ type=str,
154
+ required=False,
155
+ description="Redis key",
156
+ input=True,
157
+ ),
158
+ "value": NodeParameter(
159
+ name="value",
160
+ type=Any,
161
+ required=False,
162
+ description="Value for set operations",
163
+ input=True,
164
+ ),
165
+ "field": NodeParameter(
166
+ name="field", type=str, required=False, description="Hash field name"
167
+ ),
168
+ "fields": NodeParameter(
169
+ name="fields",
170
+ type=list,
171
+ required=False,
172
+ description="Multiple hash fields",
173
+ ),
174
+ "ttl": NodeParameter(
175
+ name="ttl",
176
+ type=int,
177
+ required=False,
178
+ description="Time to live in seconds",
179
+ ),
180
+ "decode_responses": NodeParameter(
181
+ name="decode_responses",
182
+ type=bool,
183
+ required=False,
184
+ default=True,
185
+ description="Decode byte responses to strings",
186
+ ),
187
+ "result": NodeParameter(
188
+ name="result",
189
+ type=Any,
190
+ required=False,
191
+ description="Operation result",
192
+ output=True,
193
+ ),
194
+ "success": NodeParameter(
195
+ name="success",
196
+ type=bool,
197
+ required=False,
198
+ description="Operation success status",
199
+ output=True,
200
+ ),
201
+ }
202
+
203
+ def _get_client(self) -> redis.Redis:
204
+ """Get or create Redis client."""
205
+ if not self._client:
206
+ self._client = redis.Redis(
207
+ host=self.host,
208
+ port=self.port,
209
+ db=self.db,
210
+ password=self.password,
211
+ decode_responses=self.decode_responses,
212
+ )
213
+ return self._client
214
+
215
+ def execute(
216
+ self, key: Optional[str] = None, value: Optional[Any] = None, **kwargs
217
+ ) -> Dict[str, Any]:
218
+ """Execute Redis operation.
219
+
220
+ Args:
221
+ key: Override key from config
222
+ value: Override value from config
223
+ **kwargs: Additional runtime parameters
224
+
225
+ Returns:
226
+ Dict with result and optional metadata
227
+ """
228
+ # Use runtime parameters if provided
229
+ key = key or self.key or kwargs.get("key")
230
+ value = (
231
+ value
232
+ if value is not None
233
+ else (self.value if self.value is not None else kwargs.get("value"))
234
+ )
235
+
236
+ if not key and self.operation not in ["ping", "info", "flushdb"]:
237
+ raise NodeExecutionError("Key is required for this operation")
238
+
239
+ try:
240
+ client = self._get_client()
241
+ result = None
242
+
243
+ # String operations
244
+ if self.operation == "get":
245
+ result = client.get(key)
246
+
247
+ elif self.operation == "set":
248
+ if value is None:
249
+ raise NodeExecutionError("Value is required for set operation")
250
+
251
+ # Serialize non-string values
252
+ if not isinstance(value, (str, bytes)):
253
+ value = json.dumps(value)
254
+
255
+ if self.ttl:
256
+ result = client.setex(key, self.ttl, value)
257
+ else:
258
+ result = client.set(key, value)
259
+
260
+ elif self.operation == "delete":
261
+ result = client.delete(key)
262
+
263
+ # Hash operations
264
+ elif self.operation == "hget":
265
+ if not self.field:
266
+ raise NodeExecutionError("Field is required for hget operation")
267
+ result = client.hget(key, self.field)
268
+
269
+ elif self.operation == "hset":
270
+ if not self.field:
271
+ raise NodeExecutionError("Field is required for hset operation")
272
+ if value is None:
273
+ raise NodeExecutionError("Value is required for hset operation")
274
+
275
+ # Serialize non-string values
276
+ if not isinstance(value, (str, bytes)):
277
+ value = json.dumps(value)
278
+
279
+ result = client.hset(key, self.field, value)
280
+
281
+ elif self.operation == "hgetall":
282
+ result = client.hgetall(key)
283
+ # Convert to regular dict for JSON serialization
284
+ if result:
285
+ result = dict(result)
286
+
287
+ elif self.operation == "hmget":
288
+ if not self.fields:
289
+ raise NodeExecutionError("Fields are required for hmget operation")
290
+ values = client.hmget(key, self.fields)
291
+ result = dict(zip(self.fields, values))
292
+
293
+ # List operations
294
+ elif self.operation == "lpush":
295
+ if value is None:
296
+ raise NodeExecutionError("Value is required for lpush operation")
297
+ result = client.lpush(key, value)
298
+
299
+ elif self.operation == "rpush":
300
+ if value is None:
301
+ raise NodeExecutionError("Value is required for rpush operation")
302
+ result = client.rpush(key, value)
303
+
304
+ elif self.operation == "lrange":
305
+ start = kwargs.get("start", 0)
306
+ end = kwargs.get("end", -1)
307
+ result = client.lrange(key, start, end)
308
+
309
+ # Set operations
310
+ elif self.operation == "sadd":
311
+ if value is None:
312
+ raise NodeExecutionError("Value is required for sadd operation")
313
+ result = client.sadd(key, value)
314
+
315
+ elif self.operation == "smembers":
316
+ result = list(client.smembers(key))
317
+
318
+ # Sorted set operations
319
+ elif self.operation == "zadd":
320
+ if value is None or "score" not in kwargs:
321
+ raise NodeExecutionError(
322
+ "Value and score are required for zadd operation"
323
+ )
324
+ result = client.zadd(key, {value: kwargs["score"]})
325
+
326
+ elif self.operation == "zrange":
327
+ start = kwargs.get("start", 0)
328
+ end = kwargs.get("end", -1)
329
+ result = client.zrange(
330
+ key, start, end, withscores=kwargs.get("withscores", False)
331
+ )
332
+
333
+ # Utility operations
334
+ elif self.operation == "exists":
335
+ result = client.exists(key)
336
+
337
+ elif self.operation == "ttl":
338
+ result = client.ttl(key)
339
+
340
+ elif self.operation == "ping":
341
+ result = client.ping()
342
+
343
+ elif self.operation == "info":
344
+ result = client.info()
345
+
346
+ elif self.operation == "flushdb":
347
+ result = client.flushdb()
348
+
349
+ else:
350
+ raise NodeExecutionError(f"Unsupported operation: {self.operation}")
351
+
352
+ # Handle Redis OK response
353
+ if (
354
+ result is True
355
+ or (isinstance(result, bytes) and result == b"OK")
356
+ or result == "OK"
357
+ ):
358
+ return {"result": result, "success": True}
359
+
360
+ return {"result": result}
361
+
362
+ except redis.RedisError as e:
363
+ logger.error(f"Redis error: {e}")
364
+ raise NodeExecutionError(f"Redis operation failed: {str(e)}")
365
+ except Exception as e:
366
+ logger.error(f"Unexpected error: {e}")
367
+ raise NodeExecutionError(f"Unexpected error: {str(e)}")
368
+ finally:
369
+ # Don't close the connection - keep it for connection pooling
370
+ pass
371
+
372
+ def __del__(self):
373
+ """Clean up Redis connection."""
374
+ if self._client:
375
+ try:
376
+ self._client.close()
377
+ except:
378
+ pass