kailash 0.3.1__py3-none-any.whl → 0.4.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.
- kailash/__init__.py +33 -1
- kailash/access_control/__init__.py +129 -0
- kailash/access_control/managers.py +461 -0
- kailash/access_control/rule_evaluators.py +467 -0
- kailash/access_control_abac.py +825 -0
- kailash/config/__init__.py +27 -0
- kailash/config/database_config.py +359 -0
- kailash/database/__init__.py +28 -0
- kailash/database/execution_pipeline.py +499 -0
- kailash/middleware/__init__.py +306 -0
- kailash/middleware/auth/__init__.py +33 -0
- kailash/middleware/auth/access_control.py +436 -0
- kailash/middleware/auth/auth_manager.py +422 -0
- kailash/middleware/auth/jwt_auth.py +477 -0
- kailash/middleware/auth/kailash_jwt_auth.py +616 -0
- kailash/middleware/communication/__init__.py +37 -0
- kailash/middleware/communication/ai_chat.py +989 -0
- kailash/middleware/communication/api_gateway.py +802 -0
- kailash/middleware/communication/events.py +470 -0
- kailash/middleware/communication/realtime.py +710 -0
- kailash/middleware/core/__init__.py +21 -0
- kailash/middleware/core/agent_ui.py +890 -0
- kailash/middleware/core/schema.py +643 -0
- kailash/middleware/core/workflows.py +396 -0
- kailash/middleware/database/__init__.py +63 -0
- kailash/middleware/database/base.py +113 -0
- kailash/middleware/database/base_models.py +525 -0
- kailash/middleware/database/enums.py +106 -0
- kailash/middleware/database/migrations.py +12 -0
- kailash/{api/database.py → middleware/database/models.py} +183 -291
- kailash/middleware/database/repositories.py +685 -0
- kailash/middleware/database/session_manager.py +19 -0
- kailash/middleware/mcp/__init__.py +38 -0
- kailash/middleware/mcp/client_integration.py +585 -0
- kailash/middleware/mcp/enhanced_server.py +576 -0
- kailash/nodes/__init__.py +25 -3
- kailash/nodes/admin/__init__.py +35 -0
- kailash/nodes/admin/audit_log.py +794 -0
- kailash/nodes/admin/permission_check.py +864 -0
- kailash/nodes/admin/role_management.py +823 -0
- kailash/nodes/admin/security_event.py +1519 -0
- kailash/nodes/admin/user_management.py +944 -0
- kailash/nodes/ai/a2a.py +24 -7
- kailash/nodes/ai/ai_providers.py +1 -0
- kailash/nodes/ai/embedding_generator.py +11 -11
- kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
- kailash/nodes/ai/llm_agent.py +407 -2
- kailash/nodes/ai/self_organizing.py +85 -10
- kailash/nodes/api/auth.py +287 -6
- kailash/nodes/api/rest.py +151 -0
- kailash/nodes/auth/__init__.py +17 -0
- kailash/nodes/auth/directory_integration.py +1228 -0
- kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
- kailash/nodes/auth/mfa.py +2338 -0
- kailash/nodes/auth/risk_assessment.py +872 -0
- kailash/nodes/auth/session_management.py +1093 -0
- kailash/nodes/auth/sso.py +1040 -0
- kailash/nodes/base.py +344 -13
- kailash/nodes/base_cycle_aware.py +4 -2
- kailash/nodes/base_with_acl.py +1 -1
- kailash/nodes/code/python.py +293 -12
- kailash/nodes/compliance/__init__.py +9 -0
- kailash/nodes/compliance/data_retention.py +1888 -0
- kailash/nodes/compliance/gdpr.py +2004 -0
- kailash/nodes/data/__init__.py +22 -2
- kailash/nodes/data/async_connection.py +469 -0
- kailash/nodes/data/async_sql.py +757 -0
- kailash/nodes/data/async_vector.py +598 -0
- kailash/nodes/data/readers.py +767 -0
- kailash/nodes/data/retrieval.py +360 -1
- kailash/nodes/data/sharepoint_graph.py +397 -21
- kailash/nodes/data/sql.py +94 -5
- kailash/nodes/data/streaming.py +68 -8
- kailash/nodes/data/vector_db.py +54 -4
- kailash/nodes/enterprise/__init__.py +13 -0
- kailash/nodes/enterprise/batch_processor.py +741 -0
- kailash/nodes/enterprise/data_lineage.py +497 -0
- kailash/nodes/logic/convergence.py +31 -9
- kailash/nodes/logic/operations.py +14 -3
- kailash/nodes/mixins/__init__.py +8 -0
- kailash/nodes/mixins/event_emitter.py +201 -0
- kailash/nodes/mixins/mcp.py +9 -4
- kailash/nodes/mixins/security.py +165 -0
- kailash/nodes/monitoring/__init__.py +7 -0
- kailash/nodes/monitoring/performance_benchmark.py +2497 -0
- kailash/nodes/rag/__init__.py +284 -0
- kailash/nodes/rag/advanced.py +1615 -0
- kailash/nodes/rag/agentic.py +773 -0
- kailash/nodes/rag/conversational.py +999 -0
- kailash/nodes/rag/evaluation.py +875 -0
- kailash/nodes/rag/federated.py +1188 -0
- kailash/nodes/rag/graph.py +721 -0
- kailash/nodes/rag/multimodal.py +671 -0
- kailash/nodes/rag/optimized.py +933 -0
- kailash/nodes/rag/privacy.py +1059 -0
- kailash/nodes/rag/query_processing.py +1335 -0
- kailash/nodes/rag/realtime.py +764 -0
- kailash/nodes/rag/registry.py +547 -0
- kailash/nodes/rag/router.py +837 -0
- kailash/nodes/rag/similarity.py +1854 -0
- kailash/nodes/rag/strategies.py +566 -0
- kailash/nodes/rag/workflows.py +575 -0
- kailash/nodes/security/__init__.py +19 -0
- kailash/nodes/security/abac_evaluator.py +1411 -0
- kailash/nodes/security/audit_log.py +91 -0
- kailash/nodes/security/behavior_analysis.py +1893 -0
- kailash/nodes/security/credential_manager.py +401 -0
- kailash/nodes/security/rotating_credentials.py +760 -0
- kailash/nodes/security/security_event.py +132 -0
- kailash/nodes/security/threat_detection.py +1103 -0
- kailash/nodes/testing/__init__.py +9 -0
- kailash/nodes/testing/credential_testing.py +499 -0
- kailash/nodes/transform/__init__.py +10 -2
- kailash/nodes/transform/chunkers.py +592 -1
- kailash/nodes/transform/processors.py +484 -14
- kailash/nodes/validation.py +321 -0
- kailash/runtime/access_controlled.py +1 -1
- kailash/runtime/async_local.py +41 -7
- kailash/runtime/docker.py +1 -1
- kailash/runtime/local.py +474 -55
- kailash/runtime/parallel.py +1 -1
- kailash/runtime/parallel_cyclic.py +1 -1
- kailash/runtime/testing.py +210 -2
- kailash/utils/migrations/__init__.py +25 -0
- kailash/utils/migrations/generator.py +433 -0
- kailash/utils/migrations/models.py +231 -0
- kailash/utils/migrations/runner.py +489 -0
- kailash/utils/secure_logging.py +342 -0
- kailash/workflow/__init__.py +16 -0
- kailash/workflow/cyclic_runner.py +3 -4
- kailash/workflow/graph.py +70 -2
- kailash/workflow/resilience.py +249 -0
- kailash/workflow/templates.py +726 -0
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/METADATA +253 -20
- kailash-0.4.0.dist-info/RECORD +223 -0
- kailash/api/__init__.py +0 -17
- kailash/api/__main__.py +0 -6
- kailash/api/studio_secure.py +0 -893
- kailash/mcp/__main__.py +0 -13
- kailash/mcp/server_new.py +0 -336
- kailash/mcp/servers/__init__.py +0 -12
- kailash-0.3.1.dist-info/RECORD +0 -136
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/WHEEL +0 -0
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,249 @@
|
|
1
|
+
"""Resilience features for Kailash workflows.
|
2
|
+
|
3
|
+
This module provides resilience patterns that can be applied to any workflow,
|
4
|
+
including retry policies, fallback nodes, and circuit breakers.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import asyncio
|
8
|
+
import time
|
9
|
+
from dataclasses import dataclass, field
|
10
|
+
from datetime import datetime
|
11
|
+
from enum import Enum
|
12
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
13
|
+
|
14
|
+
|
15
|
+
class RetryStrategy(Enum):
|
16
|
+
"""Retry strategies for failed nodes."""
|
17
|
+
|
18
|
+
IMMEDIATE = "immediate"
|
19
|
+
LINEAR = "linear"
|
20
|
+
EXPONENTIAL = "exponential"
|
21
|
+
FIBONACCI = "fibonacci"
|
22
|
+
|
23
|
+
|
24
|
+
@dataclass
|
25
|
+
class RetryPolicy:
|
26
|
+
"""Configuration for node retry behavior."""
|
27
|
+
|
28
|
+
max_retries: int = 3
|
29
|
+
strategy: RetryStrategy = RetryStrategy.EXPONENTIAL
|
30
|
+
base_delay: float = 1.0
|
31
|
+
max_delay: float = 60.0
|
32
|
+
retry_on: List[type] = field(default_factory=lambda: [Exception])
|
33
|
+
|
34
|
+
def calculate_delay(self, attempt: int) -> float:
|
35
|
+
"""Calculate delay based on retry strategy."""
|
36
|
+
if self.strategy == RetryStrategy.IMMEDIATE:
|
37
|
+
return 0
|
38
|
+
elif self.strategy == RetryStrategy.LINEAR:
|
39
|
+
delay = self.base_delay * attempt
|
40
|
+
elif self.strategy == RetryStrategy.EXPONENTIAL:
|
41
|
+
delay = self.base_delay * (2 ** (attempt - 1))
|
42
|
+
elif self.strategy == RetryStrategy.FIBONACCI:
|
43
|
+
# Fibonacci sequence
|
44
|
+
fib = [1, 1]
|
45
|
+
for _ in range(attempt - 1):
|
46
|
+
fib.append(fib[-1] + fib[-2])
|
47
|
+
delay = self.base_delay * fib[min(attempt - 1, len(fib) - 1)]
|
48
|
+
else:
|
49
|
+
delay = self.base_delay
|
50
|
+
|
51
|
+
return min(delay, self.max_delay)
|
52
|
+
|
53
|
+
|
54
|
+
@dataclass
|
55
|
+
class CircuitBreakerConfig:
|
56
|
+
"""Configuration for circuit breaker pattern."""
|
57
|
+
|
58
|
+
failure_threshold: int = 5
|
59
|
+
success_threshold: int = 2
|
60
|
+
timeout: float = 60.0
|
61
|
+
|
62
|
+
# Runtime state
|
63
|
+
failures: int = 0
|
64
|
+
successes: int = 0
|
65
|
+
state: str = "closed" # closed, open, half-open
|
66
|
+
last_failure_time: Optional[float] = None
|
67
|
+
|
68
|
+
def is_open(self) -> bool:
|
69
|
+
"""Check if circuit breaker is open."""
|
70
|
+
if self.state == "closed":
|
71
|
+
return False
|
72
|
+
|
73
|
+
if self.state == "open":
|
74
|
+
# Check if timeout has passed
|
75
|
+
if self.last_failure_time:
|
76
|
+
elapsed = time.time() - self.last_failure_time
|
77
|
+
if elapsed >= self.timeout:
|
78
|
+
# Move to half-open state
|
79
|
+
self.state = "half-open"
|
80
|
+
self.successes = 0
|
81
|
+
return False
|
82
|
+
return True
|
83
|
+
|
84
|
+
# Half-open state
|
85
|
+
return False
|
86
|
+
|
87
|
+
def record_success(self):
|
88
|
+
"""Record successful execution."""
|
89
|
+
self.failures = 0
|
90
|
+
self.successes += 1
|
91
|
+
|
92
|
+
# Close circuit if enough successes in half-open state
|
93
|
+
if self.state == "half-open" and self.successes >= self.success_threshold:
|
94
|
+
self.state = "closed"
|
95
|
+
|
96
|
+
def record_failure(self):
|
97
|
+
"""Record failed execution."""
|
98
|
+
self.failures += 1
|
99
|
+
self.successes = 0
|
100
|
+
self.last_failure_time = time.time()
|
101
|
+
|
102
|
+
# Open circuit if threshold reached
|
103
|
+
if self.failures >= self.failure_threshold:
|
104
|
+
self.state = "open"
|
105
|
+
|
106
|
+
|
107
|
+
class WorkflowResilience:
|
108
|
+
"""Mixin class to add resilience features to workflows."""
|
109
|
+
|
110
|
+
def __init__(self):
|
111
|
+
"""Initialize resilience features."""
|
112
|
+
self._fallback_nodes: Dict[str, List[Any]] = {}
|
113
|
+
self._retry_policies: Dict[str, RetryPolicy] = {}
|
114
|
+
self._circuit_breakers: Dict[str, CircuitBreakerConfig] = {}
|
115
|
+
self._execution_history: List[Dict[str, Any]] = []
|
116
|
+
self._dead_letter_queue: List[Dict[str, Any]] = []
|
117
|
+
self._node_metrics: Dict[str, Dict[str, Any]] = {}
|
118
|
+
|
119
|
+
def configure_retry(
|
120
|
+
self,
|
121
|
+
node_id: str,
|
122
|
+
max_retries: int = 3,
|
123
|
+
strategy: RetryStrategy = RetryStrategy.EXPONENTIAL,
|
124
|
+
base_delay: float = 1.0,
|
125
|
+
max_delay: float = 60.0,
|
126
|
+
retry_on: Optional[List[type]] = None,
|
127
|
+
):
|
128
|
+
"""
|
129
|
+
Configure retry policy for a specific node.
|
130
|
+
|
131
|
+
Args:
|
132
|
+
node_id: Node to apply policy to
|
133
|
+
max_retries: Maximum retry attempts
|
134
|
+
strategy: Retry delay strategy
|
135
|
+
base_delay: Base delay in seconds
|
136
|
+
max_delay: Maximum delay in seconds
|
137
|
+
retry_on: List of exception types to retry on
|
138
|
+
"""
|
139
|
+
self._retry_policies[node_id] = RetryPolicy(
|
140
|
+
max_retries=max_retries,
|
141
|
+
strategy=strategy,
|
142
|
+
base_delay=base_delay,
|
143
|
+
max_delay=max_delay,
|
144
|
+
retry_on=retry_on or [Exception],
|
145
|
+
)
|
146
|
+
|
147
|
+
def add_fallback(self, primary_node_id: str, fallback_node_id: str):
|
148
|
+
"""
|
149
|
+
Add a fallback node for automatic failover.
|
150
|
+
|
151
|
+
Args:
|
152
|
+
primary_node_id: Primary node that might fail
|
153
|
+
fallback_node_id: Fallback node to use on failure
|
154
|
+
"""
|
155
|
+
if primary_node_id not in self._fallback_nodes:
|
156
|
+
self._fallback_nodes[primary_node_id] = []
|
157
|
+
|
158
|
+
self._fallback_nodes[primary_node_id].append(fallback_node_id)
|
159
|
+
|
160
|
+
def configure_circuit_breaker(
|
161
|
+
self,
|
162
|
+
node_id: str,
|
163
|
+
failure_threshold: int = 5,
|
164
|
+
success_threshold: int = 2,
|
165
|
+
timeout: float = 60.0,
|
166
|
+
):
|
167
|
+
"""
|
168
|
+
Configure circuit breaker for a node.
|
169
|
+
|
170
|
+
Args:
|
171
|
+
node_id: Node to protect
|
172
|
+
failure_threshold: Failures before opening circuit
|
173
|
+
success_threshold: Successes needed to close circuit
|
174
|
+
timeout: Time before attempting to close circuit
|
175
|
+
"""
|
176
|
+
self._circuit_breakers[node_id] = CircuitBreakerConfig(
|
177
|
+
failure_threshold=failure_threshold,
|
178
|
+
success_threshold=success_threshold,
|
179
|
+
timeout=timeout,
|
180
|
+
)
|
181
|
+
|
182
|
+
def get_resilience_metrics(self) -> Dict[str, Any]:
|
183
|
+
"""Get execution metrics for monitoring."""
|
184
|
+
return {
|
185
|
+
"node_metrics": self._node_metrics,
|
186
|
+
"circuit_breakers": {
|
187
|
+
name: {
|
188
|
+
"state": breaker.state,
|
189
|
+
"failures": breaker.failures,
|
190
|
+
"last_failure": breaker.last_failure_time,
|
191
|
+
}
|
192
|
+
for name, breaker in self._circuit_breakers.items()
|
193
|
+
},
|
194
|
+
"dead_letter_queue_size": len(self._dead_letter_queue),
|
195
|
+
"retry_policies": {
|
196
|
+
name: {
|
197
|
+
"max_retries": policy.max_retries,
|
198
|
+
"strategy": policy.strategy.value,
|
199
|
+
}
|
200
|
+
for name, policy in self._retry_policies.items()
|
201
|
+
},
|
202
|
+
}
|
203
|
+
|
204
|
+
def get_dead_letter_queue(self) -> List[Dict[str, Any]]:
|
205
|
+
"""Get failed executions for manual intervention."""
|
206
|
+
return self._dead_letter_queue
|
207
|
+
|
208
|
+
def clear_dead_letter_queue(self):
|
209
|
+
"""Clear the dead letter queue after processing."""
|
210
|
+
self._dead_letter_queue = []
|
211
|
+
|
212
|
+
def reset_circuit_breaker(self, node_id: str):
|
213
|
+
"""Manually reset a circuit breaker."""
|
214
|
+
if node_id in self._circuit_breakers:
|
215
|
+
breaker = self._circuit_breakers[node_id]
|
216
|
+
breaker.state = "closed"
|
217
|
+
breaker.failures = 0
|
218
|
+
breaker.successes = 0
|
219
|
+
breaker.last_failure_time = None
|
220
|
+
|
221
|
+
|
222
|
+
def apply_resilience_to_workflow(workflow_class):
|
223
|
+
"""Decorator to add resilience features to a workflow class."""
|
224
|
+
|
225
|
+
# Store original methods
|
226
|
+
original_init = workflow_class.__init__
|
227
|
+
|
228
|
+
def new_init(self, *args, **kwargs):
|
229
|
+
# Call original init
|
230
|
+
original_init(self, *args, **kwargs)
|
231
|
+
# Initialize resilience attributes directly on instance
|
232
|
+
self._fallback_nodes = {}
|
233
|
+
self._retry_policies = {}
|
234
|
+
self._circuit_breakers = {}
|
235
|
+
self._execution_history = []
|
236
|
+
self._dead_letter_queue = []
|
237
|
+
self._node_metrics = {}
|
238
|
+
|
239
|
+
# Add resilience methods to the class
|
240
|
+
for attr_name in dir(WorkflowResilience):
|
241
|
+
if not attr_name.startswith("_"):
|
242
|
+
attr = getattr(WorkflowResilience, attr_name)
|
243
|
+
if callable(attr) and attr_name not in ["__init__"]:
|
244
|
+
setattr(workflow_class, attr_name, attr)
|
245
|
+
|
246
|
+
# Override __init__
|
247
|
+
workflow_class.__init__ = new_init
|
248
|
+
|
249
|
+
return workflow_class
|