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.
- kailash/__init__.py +3 -3
- kailash/api/custom_nodes_secure.py +3 -3
- kailash/api/gateway.py +1 -1
- kailash/api/studio.py +2 -3
- kailash/api/workflow_api.py +3 -4
- kailash/core/resilience/bulkhead.py +460 -0
- kailash/core/resilience/circuit_breaker.py +92 -10
- kailash/edge/discovery.py +86 -0
- kailash/mcp_server/__init__.py +334 -0
- kailash/mcp_server/advanced_features.py +1022 -0
- kailash/{mcp → mcp_server}/ai_registry_server.py +29 -4
- kailash/mcp_server/auth.py +789 -0
- kailash/mcp_server/client.py +712 -0
- kailash/mcp_server/discovery.py +1593 -0
- kailash/mcp_server/errors.py +673 -0
- kailash/mcp_server/oauth.py +1727 -0
- kailash/mcp_server/protocol.py +1126 -0
- kailash/mcp_server/registry_integration.py +587 -0
- kailash/mcp_server/server.py +1747 -0
- kailash/{mcp → mcp_server}/servers/ai_registry.py +2 -2
- kailash/mcp_server/transports.py +1169 -0
- kailash/mcp_server/utils/cache.py +510 -0
- kailash/middleware/auth/auth_manager.py +3 -3
- kailash/middleware/communication/api_gateway.py +2 -9
- kailash/middleware/communication/realtime.py +1 -1
- kailash/middleware/mcp/client_integration.py +1 -1
- kailash/middleware/mcp/enhanced_server.py +2 -2
- kailash/nodes/__init__.py +2 -0
- kailash/nodes/admin/audit_log.py +6 -6
- kailash/nodes/admin/permission_check.py +8 -8
- kailash/nodes/admin/role_management.py +32 -28
- kailash/nodes/admin/schema.sql +6 -1
- kailash/nodes/admin/schema_manager.py +13 -13
- kailash/nodes/admin/security_event.py +16 -20
- kailash/nodes/admin/tenant_isolation.py +3 -3
- kailash/nodes/admin/transaction_utils.py +3 -3
- kailash/nodes/admin/user_management.py +21 -22
- kailash/nodes/ai/a2a.py +11 -11
- kailash/nodes/ai/ai_providers.py +9 -12
- kailash/nodes/ai/embedding_generator.py +13 -14
- kailash/nodes/ai/intelligent_agent_orchestrator.py +19 -19
- kailash/nodes/ai/iterative_llm_agent.py +3 -3
- kailash/nodes/ai/llm_agent.py +213 -36
- kailash/nodes/ai/self_organizing.py +2 -2
- kailash/nodes/alerts/discord.py +4 -4
- kailash/nodes/api/graphql.py +6 -6
- kailash/nodes/api/http.py +12 -17
- kailash/nodes/api/rate_limiting.py +4 -4
- kailash/nodes/api/rest.py +15 -15
- kailash/nodes/auth/mfa.py +3 -4
- kailash/nodes/auth/risk_assessment.py +2 -2
- kailash/nodes/auth/session_management.py +5 -5
- kailash/nodes/auth/sso.py +143 -0
- kailash/nodes/base.py +6 -2
- kailash/nodes/base_async.py +16 -2
- kailash/nodes/base_with_acl.py +2 -2
- kailash/nodes/cache/__init__.py +9 -0
- kailash/nodes/cache/cache.py +1172 -0
- kailash/nodes/cache/cache_invalidation.py +870 -0
- kailash/nodes/cache/redis_pool_manager.py +595 -0
- kailash/nodes/code/async_python.py +2 -1
- kailash/nodes/code/python.py +196 -35
- kailash/nodes/compliance/data_retention.py +6 -6
- kailash/nodes/compliance/gdpr.py +5 -5
- kailash/nodes/data/__init__.py +10 -0
- kailash/nodes/data/optimistic_locking.py +906 -0
- kailash/nodes/data/readers.py +8 -8
- kailash/nodes/data/redis.py +349 -0
- kailash/nodes/data/sql.py +314 -3
- kailash/nodes/data/streaming.py +21 -0
- kailash/nodes/enterprise/__init__.py +8 -0
- kailash/nodes/enterprise/audit_logger.py +285 -0
- kailash/nodes/enterprise/batch_processor.py +22 -3
- kailash/nodes/enterprise/data_lineage.py +1 -1
- kailash/nodes/enterprise/mcp_executor.py +205 -0
- kailash/nodes/enterprise/service_discovery.py +150 -0
- kailash/nodes/enterprise/tenant_assignment.py +108 -0
- kailash/nodes/logic/async_operations.py +2 -2
- kailash/nodes/logic/convergence.py +1 -1
- kailash/nodes/logic/operations.py +1 -1
- kailash/nodes/monitoring/__init__.py +11 -1
- kailash/nodes/monitoring/health_check.py +456 -0
- kailash/nodes/monitoring/log_processor.py +817 -0
- kailash/nodes/monitoring/metrics_collector.py +627 -0
- kailash/nodes/monitoring/performance_benchmark.py +137 -11
- kailash/nodes/rag/advanced.py +7 -7
- kailash/nodes/rag/agentic.py +49 -2
- kailash/nodes/rag/conversational.py +3 -3
- kailash/nodes/rag/evaluation.py +3 -3
- kailash/nodes/rag/federated.py +3 -3
- kailash/nodes/rag/graph.py +3 -3
- kailash/nodes/rag/multimodal.py +3 -3
- kailash/nodes/rag/optimized.py +5 -5
- kailash/nodes/rag/privacy.py +3 -3
- kailash/nodes/rag/query_processing.py +6 -6
- kailash/nodes/rag/realtime.py +1 -1
- kailash/nodes/rag/registry.py +2 -6
- kailash/nodes/rag/router.py +1 -1
- kailash/nodes/rag/similarity.py +7 -7
- kailash/nodes/rag/strategies.py +4 -4
- kailash/nodes/security/abac_evaluator.py +6 -6
- kailash/nodes/security/behavior_analysis.py +5 -6
- kailash/nodes/security/credential_manager.py +1 -1
- kailash/nodes/security/rotating_credentials.py +11 -11
- kailash/nodes/security/threat_detection.py +8 -8
- kailash/nodes/testing/credential_testing.py +2 -2
- kailash/nodes/transform/processors.py +5 -5
- kailash/runtime/local.py +162 -14
- kailash/runtime/parameter_injection.py +425 -0
- kailash/runtime/parameter_injector.py +657 -0
- kailash/runtime/testing.py +2 -2
- kailash/testing/fixtures.py +2 -2
- kailash/workflow/builder.py +99 -18
- kailash/workflow/builder_improvements.py +207 -0
- kailash/workflow/input_handling.py +170 -0
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/METADATA +21 -8
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/RECORD +126 -101
- kailash/mcp/__init__.py +0 -53
- kailash/mcp/client.py +0 -445
- kailash/mcp/server.py +0 -292
- kailash/mcp/server_enhanced.py +0 -449
- kailash/mcp/utils/cache.py +0 -267
- /kailash/{mcp → mcp_server}/client_new.py +0 -0
- /kailash/{mcp → mcp_server}/utils/__init__.py +0 -0
- /kailash/{mcp → mcp_server}/utils/config.py +0 -0
- /kailash/{mcp → mcp_server}/utils/formatters.py +0 -0
- /kailash/{mcp → mcp_server}/utils/metrics.py +0 -0
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/WHEEL +0 -0
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/entry_points.txt +0 -0
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/top_level.txt +0 -0
kailash/nodes/data/readers.py
CHANGED
@@ -105,7 +105,7 @@ class CSVReaderNode(Node):
|
|
105
105
|
Examples:
|
106
106
|
>>> # Basic CSV reading with headers
|
107
107
|
>>> reader = CSVReaderNode()
|
108
|
-
>>> result = reader.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|