kailash 0.6.2__py3-none-any.whl → 0.6.4__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 (131) 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 +2 -3
  5. kailash/api/workflow_api.py +3 -4
  6. kailash/core/resilience/bulkhead.py +460 -0
  7. kailash/core/resilience/circuit_breaker.py +92 -10
  8. kailash/edge/discovery.py +86 -0
  9. kailash/mcp_server/__init__.py +334 -0
  10. kailash/mcp_server/advanced_features.py +1022 -0
  11. kailash/{mcp → mcp_server}/ai_registry_server.py +29 -4
  12. kailash/mcp_server/auth.py +789 -0
  13. kailash/mcp_server/client.py +712 -0
  14. kailash/mcp_server/discovery.py +1593 -0
  15. kailash/mcp_server/errors.py +673 -0
  16. kailash/mcp_server/oauth.py +1727 -0
  17. kailash/mcp_server/protocol.py +1126 -0
  18. kailash/mcp_server/registry_integration.py +587 -0
  19. kailash/mcp_server/server.py +1747 -0
  20. kailash/{mcp → mcp_server}/servers/ai_registry.py +2 -2
  21. kailash/mcp_server/transports.py +1169 -0
  22. kailash/mcp_server/utils/cache.py +510 -0
  23. kailash/middleware/auth/auth_manager.py +3 -3
  24. kailash/middleware/communication/api_gateway.py +2 -9
  25. kailash/middleware/communication/realtime.py +1 -1
  26. kailash/middleware/mcp/client_integration.py +1 -1
  27. kailash/middleware/mcp/enhanced_server.py +2 -2
  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 +16 -20
  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 -22
  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 +3 -3
  43. kailash/nodes/ai/llm_agent.py +213 -36
  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 +12 -17
  48. kailash/nodes/api/rate_limiting.py +4 -4
  49. kailash/nodes/api/rest.py +15 -15
  50. kailash/nodes/auth/mfa.py +3 -4
  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 +6 -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 +870 -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 +196 -35
  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/optimistic_locking.py +906 -0
  67. kailash/nodes/data/readers.py +8 -8
  68. kailash/nodes/data/redis.py +349 -0
  69. kailash/nodes/data/sql.py +314 -3
  70. kailash/nodes/data/streaming.py +21 -0
  71. kailash/nodes/enterprise/__init__.py +8 -0
  72. kailash/nodes/enterprise/audit_logger.py +285 -0
  73. kailash/nodes/enterprise/batch_processor.py +22 -3
  74. kailash/nodes/enterprise/data_lineage.py +1 -1
  75. kailash/nodes/enterprise/mcp_executor.py +205 -0
  76. kailash/nodes/enterprise/service_discovery.py +150 -0
  77. kailash/nodes/enterprise/tenant_assignment.py +108 -0
  78. kailash/nodes/logic/async_operations.py +2 -2
  79. kailash/nodes/logic/convergence.py +1 -1
  80. kailash/nodes/logic/operations.py +1 -1
  81. kailash/nodes/monitoring/__init__.py +11 -1
  82. kailash/nodes/monitoring/health_check.py +456 -0
  83. kailash/nodes/monitoring/log_processor.py +817 -0
  84. kailash/nodes/monitoring/metrics_collector.py +627 -0
  85. kailash/nodes/monitoring/performance_benchmark.py +137 -11
  86. kailash/nodes/rag/advanced.py +7 -7
  87. kailash/nodes/rag/agentic.py +49 -2
  88. kailash/nodes/rag/conversational.py +3 -3
  89. kailash/nodes/rag/evaluation.py +3 -3
  90. kailash/nodes/rag/federated.py +3 -3
  91. kailash/nodes/rag/graph.py +3 -3
  92. kailash/nodes/rag/multimodal.py +3 -3
  93. kailash/nodes/rag/optimized.py +5 -5
  94. kailash/nodes/rag/privacy.py +3 -3
  95. kailash/nodes/rag/query_processing.py +6 -6
  96. kailash/nodes/rag/realtime.py +1 -1
  97. kailash/nodes/rag/registry.py +2 -6
  98. kailash/nodes/rag/router.py +1 -1
  99. kailash/nodes/rag/similarity.py +7 -7
  100. kailash/nodes/rag/strategies.py +4 -4
  101. kailash/nodes/security/abac_evaluator.py +6 -6
  102. kailash/nodes/security/behavior_analysis.py +5 -6
  103. kailash/nodes/security/credential_manager.py +1 -1
  104. kailash/nodes/security/rotating_credentials.py +11 -11
  105. kailash/nodes/security/threat_detection.py +8 -8
  106. kailash/nodes/testing/credential_testing.py +2 -2
  107. kailash/nodes/transform/processors.py +5 -5
  108. kailash/runtime/local.py +162 -14
  109. kailash/runtime/parameter_injection.py +425 -0
  110. kailash/runtime/parameter_injector.py +657 -0
  111. kailash/runtime/testing.py +2 -2
  112. kailash/testing/fixtures.py +2 -2
  113. kailash/workflow/builder.py +99 -18
  114. kailash/workflow/builder_improvements.py +207 -0
  115. kailash/workflow/input_handling.py +170 -0
  116. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/METADATA +21 -8
  117. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/RECORD +126 -101
  118. kailash/mcp/__init__.py +0 -53
  119. kailash/mcp/client.py +0 -445
  120. kailash/mcp/server.py +0 -292
  121. kailash/mcp/server_enhanced.py +0 -449
  122. kailash/mcp/utils/cache.py +0 -267
  123. /kailash/{mcp → mcp_server}/client_new.py +0 -0
  124. /kailash/{mcp → mcp_server}/utils/__init__.py +0 -0
  125. /kailash/{mcp → mcp_server}/utils/config.py +0 -0
  126. /kailash/{mcp → mcp_server}/utils/formatters.py +0 -0
  127. /kailash/{mcp → mcp_server}/utils/metrics.py +0 -0
  128. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/WHEEL +0 -0
  129. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/entry_points.txt +0 -0
  130. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/licenses/LICENSE +0 -0
  131. {kailash-0.6.2.dist-info → kailash-0.6.4.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,349 @@
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
+ 'host': host,
89
+ 'port': port,
90
+ 'db': db,
91
+ 'password': password,
92
+ 'operation': operation,
93
+ 'key': key,
94
+ 'value': value,
95
+ 'field': field,
96
+ 'fields': fields,
97
+ 'ttl': ttl,
98
+ 'decode_responses': decode_responses,
99
+ })
100
+
101
+ # Also store as instance attributes for backward compatibility
102
+ self.host = host
103
+ self.port = port
104
+ self.db = db
105
+ self.password = password
106
+ self.operation = operation
107
+ self._client = None # Initialize to avoid __del__ error
108
+ self.key = key
109
+ self.value = value
110
+ self.field = field
111
+ self.fields = fields
112
+ self.ttl = ttl
113
+ self.decode_responses = decode_responses
114
+
115
+ @classmethod
116
+ def get_parameters(cls) -> Dict[str, NodeParameter]:
117
+ """Define node parameters."""
118
+ return {
119
+ "host": NodeParameter(
120
+ name="host", type=str, required=False, default="localhost", description="Redis host"
121
+ ),
122
+ "port": NodeParameter(
123
+ name="port", type=int, required=False, default=6379, description="Redis port"
124
+ ),
125
+ "db": NodeParameter(
126
+ name="db", type=int, required=False, default=0, description="Redis database number"
127
+ ),
128
+ "password": NodeParameter(
129
+ name="password", type=str, required=False, description="Redis password"
130
+ ),
131
+ "operation": NodeParameter(
132
+ name="operation",
133
+ type=str,
134
+ required=True,
135
+ description="Redis operation (get, set, hget, hset, hgetall, etc.)",
136
+ ),
137
+ "key": NodeParameter(
138
+ name="key", type=str, required=False, description="Redis key", input=True
139
+ ),
140
+ "value": NodeParameter(
141
+ name="value",
142
+ type=Any,
143
+ required=False,
144
+ description="Value for set operations",
145
+ input=True,
146
+ ),
147
+ "field": NodeParameter(
148
+ name="field", type=str, required=False, description="Hash field name"
149
+ ),
150
+ "fields": NodeParameter(
151
+ name="fields", type=list, required=False, description="Multiple hash fields"
152
+ ),
153
+ "ttl": NodeParameter(
154
+ name="ttl", type=int, required=False, description="Time to live in seconds"
155
+ ),
156
+ "decode_responses": NodeParameter(
157
+ name="decode_responses", type=bool,
158
+ required=False,
159
+ default=True,
160
+ description="Decode byte responses to strings",
161
+ ),
162
+ "result": NodeParameter(
163
+ name="result", type=Any, required=False, description="Operation result", output=True
164
+ ),
165
+ "success": NodeParameter(
166
+ name="success",
167
+ type=bool,
168
+ required=False,
169
+ description="Operation success status",
170
+ output=True,
171
+ ),
172
+ }
173
+
174
+ def _get_client(self) -> redis.Redis:
175
+ """Get or create Redis client."""
176
+ if not self._client:
177
+ self._client = redis.Redis(
178
+ host=self.host,
179
+ port=self.port,
180
+ db=self.db,
181
+ password=self.password,
182
+ decode_responses=self.decode_responses,
183
+ )
184
+ return self._client
185
+
186
+ def execute(
187
+ self, key: Optional[str] = None, value: Optional[Any] = None, **kwargs
188
+ ) -> Dict[str, Any]:
189
+ """Execute Redis operation.
190
+
191
+ Args:
192
+ key: Override key from config
193
+ value: Override value from config
194
+ **kwargs: Additional runtime parameters
195
+
196
+ Returns:
197
+ Dict with result and optional metadata
198
+ """
199
+ # Use runtime parameters if provided
200
+ key = key or self.key or kwargs.get("key")
201
+ value = (
202
+ value
203
+ if value is not None
204
+ else (self.value if self.value is not None else kwargs.get("value"))
205
+ )
206
+
207
+ if not key and self.operation not in ["ping", "info", "flushdb"]:
208
+ raise NodeExecutionError("Key is required for this operation")
209
+
210
+ try:
211
+ client = self._get_client()
212
+ result = None
213
+
214
+ # String operations
215
+ if self.operation == "get":
216
+ result = client.get(key)
217
+
218
+ elif self.operation == "set":
219
+ if value is None:
220
+ raise NodeExecutionError("Value is required for set operation")
221
+
222
+ # Serialize non-string values
223
+ if not isinstance(value, (str, bytes)):
224
+ value = json.dumps(value)
225
+
226
+ if self.ttl:
227
+ result = client.setex(key, self.ttl, value)
228
+ else:
229
+ result = client.set(key, value)
230
+
231
+ elif self.operation == "delete":
232
+ result = client.delete(key)
233
+
234
+ # Hash operations
235
+ elif self.operation == "hget":
236
+ if not self.field:
237
+ raise NodeExecutionError("Field is required for hget operation")
238
+ result = client.hget(key, self.field)
239
+
240
+ elif self.operation == "hset":
241
+ if not self.field:
242
+ raise NodeExecutionError("Field is required for hset operation")
243
+ if value is None:
244
+ raise NodeExecutionError("Value is required for hset operation")
245
+
246
+ # Serialize non-string values
247
+ if not isinstance(value, (str, bytes)):
248
+ value = json.dumps(value)
249
+
250
+ result = client.hset(key, self.field, value)
251
+
252
+ elif self.operation == "hgetall":
253
+ result = client.hgetall(key)
254
+ # Convert to regular dict for JSON serialization
255
+ if result:
256
+ result = dict(result)
257
+
258
+ elif self.operation == "hmget":
259
+ if not self.fields:
260
+ raise NodeExecutionError("Fields are required for hmget operation")
261
+ values = client.hmget(key, self.fields)
262
+ result = dict(zip(self.fields, values))
263
+
264
+ # List operations
265
+ elif self.operation == "lpush":
266
+ if value is None:
267
+ raise NodeExecutionError("Value is required for lpush operation")
268
+ result = client.lpush(key, value)
269
+
270
+ elif self.operation == "rpush":
271
+ if value is None:
272
+ raise NodeExecutionError("Value is required for rpush operation")
273
+ result = client.rpush(key, value)
274
+
275
+ elif self.operation == "lrange":
276
+ start = kwargs.get("start", 0)
277
+ end = kwargs.get("end", -1)
278
+ result = client.lrange(key, start, end)
279
+
280
+ # Set operations
281
+ elif self.operation == "sadd":
282
+ if value is None:
283
+ raise NodeExecutionError("Value is required for sadd operation")
284
+ result = client.sadd(key, value)
285
+
286
+ elif self.operation == "smembers":
287
+ result = list(client.smembers(key))
288
+
289
+ # Sorted set operations
290
+ elif self.operation == "zadd":
291
+ if value is None or "score" not in kwargs:
292
+ raise NodeExecutionError(
293
+ "Value and score are required for zadd operation"
294
+ )
295
+ result = client.zadd(key, {value: kwargs["score"]})
296
+
297
+ elif self.operation == "zrange":
298
+ start = kwargs.get("start", 0)
299
+ end = kwargs.get("end", -1)
300
+ result = client.zrange(
301
+ key, start, end, withscores=kwargs.get("withscores", False)
302
+ )
303
+
304
+ # Utility operations
305
+ elif self.operation == "exists":
306
+ result = client.exists(key)
307
+
308
+ elif self.operation == "ttl":
309
+ result = client.ttl(key)
310
+
311
+ elif self.operation == "ping":
312
+ result = client.ping()
313
+
314
+ elif self.operation == "info":
315
+ result = client.info()
316
+
317
+ elif self.operation == "flushdb":
318
+ result = client.flushdb()
319
+
320
+ else:
321
+ raise NodeExecutionError(f"Unsupported operation: {self.operation}")
322
+
323
+ # Handle Redis OK response
324
+ if (
325
+ result is True
326
+ or (isinstance(result, bytes) and result == b"OK")
327
+ or result == "OK"
328
+ ):
329
+ return {"result": result, "success": True}
330
+
331
+ return {"result": result}
332
+
333
+ except redis.RedisError as e:
334
+ logger.error(f"Redis error: {e}")
335
+ raise NodeExecutionError(f"Redis operation failed: {str(e)}")
336
+ except Exception as e:
337
+ logger.error(f"Unexpected error: {e}")
338
+ raise NodeExecutionError(f"Unexpected error: {str(e)}")
339
+ finally:
340
+ # Don't close the connection - keep it for connection pooling
341
+ pass
342
+
343
+ def __del__(self):
344
+ """Clean up Redis connection."""
345
+ if self._client:
346
+ try:
347
+ self._client.close()
348
+ except:
349
+ pass