kailash 0.3.0__py3-none-any.whl → 0.3.2__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/access_control.py +40 -39
- kailash/api/auth.py +26 -32
- kailash/api/custom_nodes.py +29 -29
- kailash/api/custom_nodes_secure.py +35 -35
- kailash/api/database.py +17 -17
- kailash/api/gateway.py +19 -19
- kailash/api/mcp_integration.py +24 -23
- kailash/api/studio.py +45 -45
- kailash/api/workflow_api.py +8 -8
- kailash/cli/commands.py +5 -8
- kailash/manifest.py +42 -42
- kailash/mcp/__init__.py +1 -1
- kailash/mcp/ai_registry_server.py +20 -20
- kailash/mcp/client.py +9 -11
- kailash/mcp/client_new.py +10 -10
- kailash/mcp/server.py +1 -2
- kailash/mcp/server_enhanced.py +449 -0
- kailash/mcp/servers/ai_registry.py +6 -6
- kailash/mcp/utils/__init__.py +31 -0
- kailash/mcp/utils/cache.py +267 -0
- kailash/mcp/utils/config.py +263 -0
- kailash/mcp/utils/formatters.py +293 -0
- kailash/mcp/utils/metrics.py +418 -0
- kailash/nodes/ai/agents.py +9 -9
- kailash/nodes/ai/ai_providers.py +33 -34
- kailash/nodes/ai/embedding_generator.py +31 -32
- kailash/nodes/ai/intelligent_agent_orchestrator.py +62 -66
- kailash/nodes/ai/iterative_llm_agent.py +48 -48
- kailash/nodes/ai/llm_agent.py +32 -33
- kailash/nodes/ai/models.py +13 -13
- kailash/nodes/ai/self_organizing.py +44 -44
- kailash/nodes/api/auth.py +11 -11
- kailash/nodes/api/graphql.py +13 -13
- kailash/nodes/api/http.py +19 -19
- kailash/nodes/api/monitoring.py +20 -20
- kailash/nodes/api/rate_limiting.py +9 -13
- kailash/nodes/api/rest.py +29 -29
- kailash/nodes/api/security.py +44 -47
- kailash/nodes/base.py +21 -23
- kailash/nodes/base_async.py +7 -7
- kailash/nodes/base_cycle_aware.py +12 -12
- kailash/nodes/base_with_acl.py +5 -5
- kailash/nodes/code/python.py +66 -57
- kailash/nodes/data/directory.py +6 -6
- kailash/nodes/data/event_generation.py +10 -10
- kailash/nodes/data/file_discovery.py +28 -31
- kailash/nodes/data/readers.py +8 -8
- kailash/nodes/data/retrieval.py +10 -10
- kailash/nodes/data/sharepoint_graph.py +17 -17
- kailash/nodes/data/sources.py +5 -5
- kailash/nodes/data/sql.py +13 -13
- kailash/nodes/data/streaming.py +25 -25
- kailash/nodes/data/vector_db.py +22 -22
- kailash/nodes/data/writers.py +7 -7
- kailash/nodes/logic/async_operations.py +17 -17
- kailash/nodes/logic/convergence.py +11 -11
- kailash/nodes/logic/loop.py +4 -4
- kailash/nodes/logic/operations.py +11 -11
- kailash/nodes/logic/workflow.py +8 -9
- kailash/nodes/mixins/mcp.py +17 -17
- kailash/nodes/mixins.py +8 -10
- kailash/nodes/transform/chunkers.py +3 -3
- kailash/nodes/transform/formatters.py +7 -7
- kailash/nodes/transform/processors.py +10 -10
- kailash/runtime/access_controlled.py +18 -18
- kailash/runtime/async_local.py +17 -19
- kailash/runtime/docker.py +20 -22
- kailash/runtime/local.py +16 -16
- kailash/runtime/parallel.py +23 -23
- kailash/runtime/parallel_cyclic.py +27 -27
- kailash/runtime/runner.py +6 -6
- kailash/runtime/testing.py +20 -20
- kailash/sdk_exceptions.py +0 -58
- kailash/security.py +14 -26
- kailash/tracking/manager.py +38 -38
- kailash/tracking/metrics_collector.py +15 -14
- kailash/tracking/models.py +53 -53
- kailash/tracking/storage/base.py +7 -17
- kailash/tracking/storage/database.py +22 -23
- kailash/tracking/storage/filesystem.py +38 -40
- kailash/utils/export.py +21 -21
- kailash/utils/templates.py +2 -3
- kailash/visualization/api.py +30 -34
- kailash/visualization/dashboard.py +17 -17
- kailash/visualization/performance.py +16 -16
- kailash/visualization/reports.py +25 -27
- kailash/workflow/builder.py +8 -8
- kailash/workflow/convergence.py +13 -12
- kailash/workflow/cycle_analyzer.py +30 -32
- kailash/workflow/cycle_builder.py +12 -12
- kailash/workflow/cycle_config.py +16 -15
- kailash/workflow/cycle_debugger.py +40 -40
- kailash/workflow/cycle_exceptions.py +29 -29
- kailash/workflow/cycle_profiler.py +21 -21
- kailash/workflow/cycle_state.py +20 -22
- kailash/workflow/cyclic_runner.py +44 -44
- kailash/workflow/graph.py +40 -40
- kailash/workflow/mermaid_visualizer.py +9 -11
- kailash/workflow/migration.py +22 -22
- kailash/workflow/mock_registry.py +6 -6
- kailash/workflow/runner.py +9 -9
- kailash/workflow/safety.py +12 -13
- kailash/workflow/state.py +8 -11
- kailash/workflow/templates.py +19 -19
- kailash/workflow/validation.py +14 -14
- kailash/workflow/visualization.py +22 -22
- {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/METADATA +53 -5
- kailash-0.3.2.dist-info/RECORD +136 -0
- kailash-0.3.0.dist-info/RECORD +0 -130
- {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/WHEEL +0 -0
- {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/entry_points.txt +0 -0
- {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/top_level.txt +0 -0
kailash/nodes/api/auth.py
CHANGED
@@ -12,7 +12,7 @@ Key Components:
|
|
12
12
|
|
13
13
|
import base64
|
14
14
|
import time
|
15
|
-
from typing import Any
|
15
|
+
from typing import Any
|
16
16
|
|
17
17
|
import requests
|
18
18
|
|
@@ -42,7 +42,7 @@ class BasicAuthNode(Node):
|
|
42
42
|
- RESTClientNode: Uses auth headers for API calls
|
43
43
|
"""
|
44
44
|
|
45
|
-
def get_parameters(self) ->
|
45
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
46
46
|
"""Define the parameters this node accepts.
|
47
47
|
|
48
48
|
Returns:
|
@@ -63,7 +63,7 @@ class BasicAuthNode(Node):
|
|
63
63
|
),
|
64
64
|
}
|
65
65
|
|
66
|
-
def get_output_schema(self) ->
|
66
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
67
67
|
"""Define the output schema for this node.
|
68
68
|
|
69
69
|
Returns:
|
@@ -84,7 +84,7 @@ class BasicAuthNode(Node):
|
|
84
84
|
),
|
85
85
|
}
|
86
86
|
|
87
|
-
def run(self, **kwargs) ->
|
87
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
88
88
|
"""Generate Basic Authentication headers.
|
89
89
|
|
90
90
|
Args:
|
@@ -162,7 +162,7 @@ class OAuth2Node(Node):
|
|
162
162
|
self.token_data = None # Will store token information
|
163
163
|
self.token_expires_at = 0 # Timestamp when token expires
|
164
164
|
|
165
|
-
def get_parameters(self) ->
|
165
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
166
166
|
"""Define the parameters this node accepts.
|
167
167
|
|
168
168
|
Returns:
|
@@ -236,7 +236,7 @@ class OAuth2Node(Node):
|
|
236
236
|
),
|
237
237
|
}
|
238
238
|
|
239
|
-
def get_output_schema(self) ->
|
239
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
240
240
|
"""Define the output schema for this node.
|
241
241
|
|
242
242
|
Returns:
|
@@ -269,7 +269,7 @@ class OAuth2Node(Node):
|
|
269
269
|
),
|
270
270
|
}
|
271
271
|
|
272
|
-
def _get_token(self, **kwargs) ->
|
272
|
+
def _get_token(self, **kwargs) -> dict[str, Any]:
|
273
273
|
"""Get an OAuth token using the configured grant type.
|
274
274
|
|
275
275
|
This method handles different grant types with appropriate parameters.
|
@@ -359,7 +359,7 @@ class OAuth2Node(Node):
|
|
359
359
|
f"Failed to parse OAuth token response: {str(e)}"
|
360
360
|
) from e
|
361
361
|
|
362
|
-
def run(self, **kwargs) ->
|
362
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
363
363
|
"""Get OAuth authentication headers.
|
364
364
|
|
365
365
|
This method handles token acquisition, caching, and renewal based on
|
@@ -444,7 +444,7 @@ class APIKeyNode(Node):
|
|
444
444
|
- RESTClientNode: Uses auth data for API calls
|
445
445
|
"""
|
446
446
|
|
447
|
-
def get_parameters(self) ->
|
447
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
448
448
|
"""Define the parameters this node accepts.
|
449
449
|
|
450
450
|
Returns:
|
@@ -477,7 +477,7 @@ class APIKeyNode(Node):
|
|
477
477
|
),
|
478
478
|
}
|
479
479
|
|
480
|
-
def get_output_schema(self) ->
|
480
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
481
481
|
"""Define the output schema for this node.
|
482
482
|
|
483
483
|
Returns:
|
@@ -510,7 +510,7 @@ class APIKeyNode(Node):
|
|
510
510
|
),
|
511
511
|
}
|
512
512
|
|
513
|
-
def run(self, **kwargs) ->
|
513
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
514
514
|
"""Generate API key authentication data.
|
515
515
|
|
516
516
|
Args:
|
kailash/nodes/api/graphql.py
CHANGED
@@ -10,7 +10,7 @@ Key Components:
|
|
10
10
|
- GraphQL query building and response handling utilities
|
11
11
|
"""
|
12
12
|
|
13
|
-
from typing import Any
|
13
|
+
from typing import Any
|
14
14
|
|
15
15
|
from kailash.nodes.api.http import AsyncHTTPRequestNode, HTTPRequestNode
|
16
16
|
from kailash.nodes.base import Node, NodeParameter, register_node
|
@@ -60,7 +60,7 @@ class GraphQLClientNode(Node):
|
|
60
60
|
super().__init__(**kwargs)
|
61
61
|
self.http_node = HTTPRequestNode(**kwargs)
|
62
62
|
|
63
|
-
def get_parameters(self) ->
|
63
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
64
64
|
"""Define the parameters this node accepts.
|
65
65
|
|
66
66
|
Returns:
|
@@ -137,7 +137,7 @@ class GraphQLClientNode(Node):
|
|
137
137
|
),
|
138
138
|
}
|
139
139
|
|
140
|
-
def get_output_schema(self) ->
|
140
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
141
141
|
"""Define the output schema for this node.
|
142
142
|
|
143
143
|
Returns:
|
@@ -173,9 +173,9 @@ class GraphQLClientNode(Node):
|
|
173
173
|
def _build_graphql_payload(
|
174
174
|
self,
|
175
175
|
query: str,
|
176
|
-
variables:
|
177
|
-
operation_name:
|
178
|
-
) ->
|
176
|
+
variables: dict[str, Any] = None,
|
177
|
+
operation_name: str | None = None,
|
178
|
+
) -> dict[str, Any]:
|
179
179
|
"""Build a GraphQL request payload.
|
180
180
|
|
181
181
|
Args:
|
@@ -197,8 +197,8 @@ class GraphQLClientNode(Node):
|
|
197
197
|
return payload
|
198
198
|
|
199
199
|
def _process_graphql_response(
|
200
|
-
self, response:
|
201
|
-
) ->
|
200
|
+
self, response: dict[str, Any], flatten_response: bool = False
|
201
|
+
) -> dict[str, Any]:
|
202
202
|
"""Process a GraphQL response.
|
203
203
|
|
204
204
|
Args:
|
@@ -241,7 +241,7 @@ class GraphQLClientNode(Node):
|
|
241
241
|
|
242
242
|
return {"data": data, "errors": errors, "success": success}
|
243
243
|
|
244
|
-
def run(self, **kwargs) ->
|
244
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
245
245
|
"""Execute a GraphQL query or mutation.
|
246
246
|
|
247
247
|
Args:
|
@@ -362,7 +362,7 @@ class AsyncGraphQLClientNode(AsyncNode):
|
|
362
362
|
self.http_node = AsyncHTTPRequestNode(**kwargs)
|
363
363
|
self.graphql_node = GraphQLClientNode(**kwargs)
|
364
364
|
|
365
|
-
def get_parameters(self) ->
|
365
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
366
366
|
"""Define the parameters this node accepts.
|
367
367
|
|
368
368
|
Returns:
|
@@ -371,7 +371,7 @@ class AsyncGraphQLClientNode(AsyncNode):
|
|
371
371
|
# Same parameters as the synchronous version
|
372
372
|
return self.graphql_node.get_parameters()
|
373
373
|
|
374
|
-
def get_output_schema(self) ->
|
374
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
375
375
|
"""Define the output schema for this node.
|
376
376
|
|
377
377
|
Returns:
|
@@ -380,7 +380,7 @@ class AsyncGraphQLClientNode(AsyncNode):
|
|
380
380
|
# Same output schema as the synchronous version
|
381
381
|
return self.graphql_node.get_output_schema()
|
382
382
|
|
383
|
-
def run(self, **kwargs) ->
|
383
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
384
384
|
"""Synchronous version of the GraphQL request, for compatibility.
|
385
385
|
|
386
386
|
This is implemented for compatibility but users should use the
|
@@ -399,7 +399,7 @@ class AsyncGraphQLClientNode(AsyncNode):
|
|
399
399
|
# Forward to the synchronous GraphQL node
|
400
400
|
return self.graphql_node.run(**kwargs)
|
401
401
|
|
402
|
-
async def async_run(self, **kwargs) ->
|
402
|
+
async def async_run(self, **kwargs) -> dict[str, Any]:
|
403
403
|
"""Execute a GraphQL query or mutation asynchronously.
|
404
404
|
|
405
405
|
Args:
|
kailash/nodes/api/http.py
CHANGED
@@ -8,7 +8,7 @@ import asyncio
|
|
8
8
|
import base64
|
9
9
|
import time
|
10
10
|
from enum import Enum
|
11
|
-
from typing import Any
|
11
|
+
from typing import Any
|
12
12
|
|
13
13
|
import aiohttp
|
14
14
|
import requests
|
@@ -48,8 +48,8 @@ class HTTPResponse(BaseModel):
|
|
48
48
|
"""
|
49
49
|
|
50
50
|
status_code: int
|
51
|
-
headers:
|
52
|
-
content_type:
|
51
|
+
headers: dict[str, str]
|
52
|
+
content_type: str | None = None
|
53
53
|
content: Any # Can be dict, str, bytes depending on response format
|
54
54
|
response_time_ms: float
|
55
55
|
url: str
|
@@ -194,7 +194,7 @@ class HTTPRequestNode(Node):
|
|
194
194
|
super().__init__(**kwargs)
|
195
195
|
self.session = requests.Session()
|
196
196
|
|
197
|
-
def get_parameters(self) ->
|
197
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
198
198
|
"""Define the parameters this node accepts.
|
199
199
|
|
200
200
|
Returns:
|
@@ -328,7 +328,7 @@ class HTTPRequestNode(Node):
|
|
328
328
|
),
|
329
329
|
}
|
330
330
|
|
331
|
-
def get_output_schema(self) ->
|
331
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
332
332
|
"""Define the output schema for this node.
|
333
333
|
|
334
334
|
Returns:
|
@@ -358,10 +358,10 @@ class HTTPRequestNode(Node):
|
|
358
358
|
def _apply_authentication(
|
359
359
|
self,
|
360
360
|
headers: dict,
|
361
|
-
auth_type:
|
362
|
-
auth_token:
|
363
|
-
auth_username:
|
364
|
-
auth_password:
|
361
|
+
auth_type: str | None,
|
362
|
+
auth_token: str | None,
|
363
|
+
auth_username: str | None,
|
364
|
+
auth_password: str | None,
|
365
365
|
api_key_header: str,
|
366
366
|
) -> dict:
|
367
367
|
"""Apply authentication to request headers.
|
@@ -398,7 +398,7 @@ class HTTPRequestNode(Node):
|
|
398
398
|
|
399
399
|
return auth_headers
|
400
400
|
|
401
|
-
def run(self, **kwargs) ->
|
401
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
402
402
|
"""Execute an HTTP request.
|
403
403
|
|
404
404
|
Args:
|
@@ -683,7 +683,7 @@ class AsyncHTTPRequestNode(AsyncNode):
|
|
683
683
|
super().__init__(**kwargs)
|
684
684
|
self._session = None # Will be created when needed
|
685
685
|
|
686
|
-
def get_parameters(self) ->
|
686
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
687
687
|
"""Define the parameters this node accepts.
|
688
688
|
|
689
689
|
Returns:
|
@@ -692,7 +692,7 @@ class AsyncHTTPRequestNode(AsyncNode):
|
|
692
692
|
# Same parameters as the synchronous version
|
693
693
|
return HTTPRequestNode().get_parameters()
|
694
694
|
|
695
|
-
def get_output_schema(self) ->
|
695
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
696
696
|
"""Define the output schema for this node.
|
697
697
|
|
698
698
|
Returns:
|
@@ -704,10 +704,10 @@ class AsyncHTTPRequestNode(AsyncNode):
|
|
704
704
|
def _apply_authentication(
|
705
705
|
self,
|
706
706
|
headers: dict,
|
707
|
-
auth_type:
|
708
|
-
auth_token:
|
709
|
-
auth_username:
|
710
|
-
auth_password:
|
707
|
+
auth_type: str | None,
|
708
|
+
auth_token: str | None,
|
709
|
+
auth_username: str | None,
|
710
|
+
auth_password: str | None,
|
711
711
|
api_key_header: str,
|
712
712
|
) -> dict:
|
713
713
|
"""Apply authentication to request headers.
|
@@ -744,7 +744,7 @@ class AsyncHTTPRequestNode(AsyncNode):
|
|
744
744
|
|
745
745
|
return auth_headers
|
746
746
|
|
747
|
-
def run(self, **kwargs) ->
|
747
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
748
748
|
"""Synchronous version of the request, for compatibility.
|
749
749
|
|
750
750
|
This is implemented for compatibility but users should use the
|
@@ -763,7 +763,7 @@ class AsyncHTTPRequestNode(AsyncNode):
|
|
763
763
|
http_node = HTTPRequestNode(**self.config)
|
764
764
|
return http_node.run(**kwargs)
|
765
765
|
|
766
|
-
async def async_run(self, **kwargs) ->
|
766
|
+
async def async_run(self, **kwargs) -> dict[str, Any]:
|
767
767
|
"""Execute an HTTP request asynchronously.
|
768
768
|
|
769
769
|
Args:
|
@@ -937,7 +937,7 @@ class AsyncHTTPRequestNode(AsyncNode):
|
|
937
937
|
|
938
938
|
return result
|
939
939
|
|
940
|
-
except (aiohttp.ClientError
|
940
|
+
except (TimeoutError, aiohttp.ClientError) as e:
|
941
941
|
self.logger.warning(f"Async request failed: {str(e)}")
|
942
942
|
|
943
943
|
# Last attempt, no more retries
|
kailash/nodes/api/monitoring.py
CHANGED
@@ -4,8 +4,8 @@ import asyncio
|
|
4
4
|
import socket
|
5
5
|
import subprocess
|
6
6
|
import time
|
7
|
-
from datetime import
|
8
|
-
from typing import Any
|
7
|
+
from datetime import UTC, datetime
|
8
|
+
from typing import Any
|
9
9
|
|
10
10
|
import requests
|
11
11
|
|
@@ -92,7 +92,7 @@ class HealthCheckNode(Node):
|
|
92
92
|
>>> assert 'health_results' in result
|
93
93
|
"""
|
94
94
|
|
95
|
-
def get_parameters(self) ->
|
95
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
96
96
|
return {
|
97
97
|
"targets": NodeParameter(
|
98
98
|
name="targets",
|
@@ -130,7 +130,7 @@ class HealthCheckNode(Node):
|
|
130
130
|
),
|
131
131
|
}
|
132
132
|
|
133
|
-
def run(self, **kwargs) ->
|
133
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
134
134
|
targets = kwargs["targets"]
|
135
135
|
timeout = kwargs.get("timeout", 30)
|
136
136
|
retries = kwargs.get("retries", 2)
|
@@ -162,12 +162,12 @@ class HealthCheckNode(Node):
|
|
162
162
|
"healthy_count": len([r for r in results if r["status"] == "healthy"]),
|
163
163
|
"unhealthy_count": len([r for r in results if r["status"] == "unhealthy"]),
|
164
164
|
"execution_time": execution_time,
|
165
|
-
"timestamp": datetime.now(
|
165
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
166
166
|
}
|
167
167
|
|
168
168
|
async def _run_checks_parallel(
|
169
|
-
self, targets:
|
170
|
-
) ->
|
169
|
+
self, targets: list[dict], timeout: int, retries: int, include_metrics: bool
|
170
|
+
) -> list[dict[str, Any]]:
|
171
171
|
"""Run health checks in parallel using asyncio."""
|
172
172
|
|
173
173
|
async def run_single_check(target):
|
@@ -184,8 +184,8 @@ class HealthCheckNode(Node):
|
|
184
184
|
return await asyncio.gather(*tasks, return_exceptions=True)
|
185
185
|
|
186
186
|
def _run_checks_sequential(
|
187
|
-
self, targets:
|
188
|
-
) ->
|
187
|
+
self, targets: list[dict], timeout: int, retries: int, include_metrics: bool
|
188
|
+
) -> list[dict[str, Any]]:
|
189
189
|
"""Run health checks sequentially."""
|
190
190
|
return [
|
191
191
|
self._perform_health_check(target, timeout, retries, include_metrics)
|
@@ -193,8 +193,8 @@ class HealthCheckNode(Node):
|
|
193
193
|
]
|
194
194
|
|
195
195
|
def _perform_health_check(
|
196
|
-
self, target:
|
197
|
-
) ->
|
196
|
+
self, target: dict, timeout: int, retries: int, include_metrics: bool
|
197
|
+
) -> dict[str, Any]:
|
198
198
|
"""Perform a single health check with retry logic."""
|
199
199
|
|
200
200
|
check_type = target.get("type", "unknown")
|
@@ -228,7 +228,7 @@ class HealthCheckNode(Node):
|
|
228
228
|
result["check_id"] = check_id
|
229
229
|
result["check_type"] = check_type
|
230
230
|
result["target"] = target
|
231
|
-
result["timestamp"] = datetime.now(
|
231
|
+
result["timestamp"] = datetime.now(UTC).isoformat() + "Z"
|
232
232
|
|
233
233
|
# If successful, return immediately
|
234
234
|
if result["status"] == "healthy":
|
@@ -245,7 +245,7 @@ class HealthCheckNode(Node):
|
|
245
245
|
"details": {"error": str(e), "error_type": type(e).__name__},
|
246
246
|
"response_time": time.time() - start_time,
|
247
247
|
"attempt": attempt + 1,
|
248
|
-
"timestamp": datetime.now(
|
248
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
249
249
|
}
|
250
250
|
|
251
251
|
# Wait before retry (exponential backoff)
|
@@ -253,7 +253,7 @@ class HealthCheckNode(Node):
|
|
253
253
|
|
254
254
|
return result
|
255
255
|
|
256
|
-
def _check_http(self, target:
|
256
|
+
def _check_http(self, target: dict, timeout: int) -> dict[str, Any]:
|
257
257
|
"""Perform HTTP health check."""
|
258
258
|
url = target["url"]
|
259
259
|
expected_status = target.get("expected_status", 200)
|
@@ -296,7 +296,7 @@ class HealthCheckNode(Node):
|
|
296
296
|
},
|
297
297
|
}
|
298
298
|
|
299
|
-
def _check_tcp(self, target:
|
299
|
+
def _check_tcp(self, target: dict, timeout: int) -> dict[str, Any]:
|
300
300
|
"""Perform TCP port connectivity check."""
|
301
301
|
host = target["host"]
|
302
302
|
port = target["port"]
|
@@ -321,7 +321,7 @@ class HealthCheckNode(Node):
|
|
321
321
|
finally:
|
322
322
|
sock.close()
|
323
323
|
|
324
|
-
def _check_disk(self, target:
|
324
|
+
def _check_disk(self, target: dict) -> dict[str, Any]:
|
325
325
|
"""Perform disk space check."""
|
326
326
|
import shutil
|
327
327
|
|
@@ -365,7 +365,7 @@ class HealthCheckNode(Node):
|
|
365
365
|
"details": {"path": path, "error": str(e)},
|
366
366
|
}
|
367
367
|
|
368
|
-
def _check_command(self, target:
|
368
|
+
def _check_command(self, target: dict, timeout: int) -> dict[str, Any]:
|
369
369
|
"""Perform custom command health check."""
|
370
370
|
command = target["command"]
|
371
371
|
expected_exit_code = target.get("expected_exit_code", 0)
|
@@ -409,7 +409,7 @@ class HealthCheckNode(Node):
|
|
409
409
|
"details": {"command": command, "timeout": timeout},
|
410
410
|
}
|
411
411
|
|
412
|
-
def _check_database(self, target:
|
412
|
+
def _check_database(self, target: dict, timeout: int) -> dict[str, Any]:
|
413
413
|
"""Perform database connectivity check."""
|
414
414
|
# This is a simplified example - in production, you'd use actual database drivers
|
415
415
|
db_type = target.get("db_type", "postgresql")
|
@@ -421,8 +421,8 @@ class HealthCheckNode(Node):
|
|
421
421
|
return self._check_tcp({"host": host, "port": port}, timeout)
|
422
422
|
|
423
423
|
def _generate_summary(
|
424
|
-
self, results:
|
425
|
-
) ->
|
424
|
+
self, results: list[dict], execution_time: float
|
425
|
+
) -> dict[str, Any]:
|
426
426
|
"""Generate summary statistics from health check results."""
|
427
427
|
total_checks = len(results)
|
428
428
|
healthy_checks = len([r for r in results if r.get("status") == "healthy"])
|
@@ -17,7 +17,7 @@ import time
|
|
17
17
|
from abc import ABC, abstractmethod
|
18
18
|
from collections import deque
|
19
19
|
from dataclasses import dataclass
|
20
|
-
from typing import Any
|
20
|
+
from typing import Any
|
21
21
|
|
22
22
|
from kailash.nodes.base import Node, NodeParameter, register_node
|
23
23
|
from kailash.nodes.base_async import AsyncNode
|
@@ -34,7 +34,7 @@ class RateLimitConfig:
|
|
34
34
|
max_requests: int = 100 # Maximum requests allowed
|
35
35
|
time_window: float = 60.0 # Time window in seconds
|
36
36
|
strategy: str = "token_bucket" # Rate limiting strategy
|
37
|
-
burst_limit:
|
37
|
+
burst_limit: int | None = None # Maximum burst requests (for token bucket)
|
38
38
|
backoff_factor: float = 1.0 # Backoff factor when rate limited
|
39
39
|
max_backoff: float = 300.0 # Maximum backoff time in seconds
|
40
40
|
|
@@ -61,7 +61,6 @@ class RateLimiter(ABC):
|
|
61
61
|
Returns:
|
62
62
|
True if request can proceed, False if rate limited
|
63
63
|
"""
|
64
|
-
pass
|
65
64
|
|
66
65
|
@abstractmethod
|
67
66
|
def wait_time(self) -> float:
|
@@ -70,7 +69,6 @@ class RateLimiter(ABC):
|
|
70
69
|
Returns:
|
71
70
|
Wait time in seconds (0 if can proceed immediately)
|
72
71
|
"""
|
73
|
-
pass
|
74
72
|
|
75
73
|
@abstractmethod
|
76
74
|
def consume(self) -> bool:
|
@@ -79,12 +77,10 @@ class RateLimiter(ABC):
|
|
79
77
|
Returns:
|
80
78
|
True if token was consumed, False if rate limited
|
81
79
|
"""
|
82
|
-
pass
|
83
80
|
|
84
81
|
@abstractmethod
|
85
82
|
def reset(self) -> None:
|
86
83
|
"""Reset the rate limiter state."""
|
87
|
-
pass
|
88
84
|
|
89
85
|
|
90
86
|
class TokenBucketRateLimiter(RateLimiter):
|
@@ -295,7 +291,7 @@ class RateLimitedAPINode(Node):
|
|
295
291
|
self.rate_limiter = create_rate_limiter(rate_limit_config)
|
296
292
|
self.config = rate_limit_config
|
297
293
|
|
298
|
-
def get_parameters(self) ->
|
294
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
299
295
|
"""Define the parameters this node accepts.
|
300
296
|
|
301
297
|
Returns:
|
@@ -326,7 +322,7 @@ class RateLimitedAPINode(Node):
|
|
326
322
|
|
327
323
|
return params
|
328
324
|
|
329
|
-
def get_output_schema(self) ->
|
325
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
330
326
|
"""Define the output schema for this node.
|
331
327
|
|
332
328
|
Returns:
|
@@ -345,7 +341,7 @@ class RateLimitedAPINode(Node):
|
|
345
341
|
|
346
342
|
return schema
|
347
343
|
|
348
|
-
def run(self, **kwargs) ->
|
344
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
349
345
|
"""Execute the wrapped node with rate limiting.
|
350
346
|
|
351
347
|
Args:
|
@@ -463,7 +459,7 @@ class AsyncRateLimitedAPINode(AsyncNode):
|
|
463
459
|
self.config = rate_limit_config
|
464
460
|
self.sync_node = RateLimitedAPINode(wrapped_node, rate_limit_config, **kwargs)
|
465
461
|
|
466
|
-
def get_parameters(self) ->
|
462
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
467
463
|
"""Define the parameters this node accepts.
|
468
464
|
|
469
465
|
Returns:
|
@@ -471,7 +467,7 @@ class AsyncRateLimitedAPINode(AsyncNode):
|
|
471
467
|
"""
|
472
468
|
return self.sync_node.get_parameters()
|
473
469
|
|
474
|
-
def get_output_schema(self) ->
|
470
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
475
471
|
"""Define the output schema for this node.
|
476
472
|
|
477
473
|
Returns:
|
@@ -479,7 +475,7 @@ class AsyncRateLimitedAPINode(AsyncNode):
|
|
479
475
|
"""
|
480
476
|
return self.sync_node.get_output_schema()
|
481
477
|
|
482
|
-
def run(self, **kwargs) ->
|
478
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
483
479
|
"""Synchronous version for compatibility.
|
484
480
|
|
485
481
|
Args:
|
@@ -490,7 +486,7 @@ class AsyncRateLimitedAPINode(AsyncNode):
|
|
490
486
|
"""
|
491
487
|
return self.sync_node.run(**kwargs)
|
492
488
|
|
493
|
-
async def async_run(self, **kwargs) ->
|
489
|
+
async def async_run(self, **kwargs) -> dict[str, Any]:
|
494
490
|
"""Execute the wrapped async node with rate limiting.
|
495
491
|
|
496
492
|
Args:
|