kailash 0.1.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.
Files changed (69) hide show
  1. kailash/__init__.py +31 -0
  2. kailash/__main__.py +11 -0
  3. kailash/cli/__init__.py +5 -0
  4. kailash/cli/commands.py +563 -0
  5. kailash/manifest.py +778 -0
  6. kailash/nodes/__init__.py +23 -0
  7. kailash/nodes/ai/__init__.py +26 -0
  8. kailash/nodes/ai/agents.py +417 -0
  9. kailash/nodes/ai/models.py +488 -0
  10. kailash/nodes/api/__init__.py +52 -0
  11. kailash/nodes/api/auth.py +567 -0
  12. kailash/nodes/api/graphql.py +480 -0
  13. kailash/nodes/api/http.py +598 -0
  14. kailash/nodes/api/rate_limiting.py +572 -0
  15. kailash/nodes/api/rest.py +665 -0
  16. kailash/nodes/base.py +1032 -0
  17. kailash/nodes/base_async.py +128 -0
  18. kailash/nodes/code/__init__.py +32 -0
  19. kailash/nodes/code/python.py +1021 -0
  20. kailash/nodes/data/__init__.py +125 -0
  21. kailash/nodes/data/readers.py +496 -0
  22. kailash/nodes/data/sharepoint_graph.py +623 -0
  23. kailash/nodes/data/sql.py +380 -0
  24. kailash/nodes/data/streaming.py +1168 -0
  25. kailash/nodes/data/vector_db.py +964 -0
  26. kailash/nodes/data/writers.py +529 -0
  27. kailash/nodes/logic/__init__.py +6 -0
  28. kailash/nodes/logic/async_operations.py +702 -0
  29. kailash/nodes/logic/operations.py +551 -0
  30. kailash/nodes/transform/__init__.py +5 -0
  31. kailash/nodes/transform/processors.py +379 -0
  32. kailash/runtime/__init__.py +6 -0
  33. kailash/runtime/async_local.py +356 -0
  34. kailash/runtime/docker.py +697 -0
  35. kailash/runtime/local.py +434 -0
  36. kailash/runtime/parallel.py +557 -0
  37. kailash/runtime/runner.py +110 -0
  38. kailash/runtime/testing.py +347 -0
  39. kailash/sdk_exceptions.py +307 -0
  40. kailash/tracking/__init__.py +7 -0
  41. kailash/tracking/manager.py +885 -0
  42. kailash/tracking/metrics_collector.py +342 -0
  43. kailash/tracking/models.py +535 -0
  44. kailash/tracking/storage/__init__.py +0 -0
  45. kailash/tracking/storage/base.py +113 -0
  46. kailash/tracking/storage/database.py +619 -0
  47. kailash/tracking/storage/filesystem.py +543 -0
  48. kailash/utils/__init__.py +0 -0
  49. kailash/utils/export.py +924 -0
  50. kailash/utils/templates.py +680 -0
  51. kailash/visualization/__init__.py +62 -0
  52. kailash/visualization/api.py +732 -0
  53. kailash/visualization/dashboard.py +951 -0
  54. kailash/visualization/performance.py +808 -0
  55. kailash/visualization/reports.py +1471 -0
  56. kailash/workflow/__init__.py +15 -0
  57. kailash/workflow/builder.py +245 -0
  58. kailash/workflow/graph.py +827 -0
  59. kailash/workflow/mermaid_visualizer.py +628 -0
  60. kailash/workflow/mock_registry.py +63 -0
  61. kailash/workflow/runner.py +302 -0
  62. kailash/workflow/state.py +238 -0
  63. kailash/workflow/visualization.py +588 -0
  64. kailash-0.1.0.dist-info/METADATA +710 -0
  65. kailash-0.1.0.dist-info/RECORD +69 -0
  66. kailash-0.1.0.dist-info/WHEEL +5 -0
  67. kailash-0.1.0.dist-info/entry_points.txt +2 -0
  68. kailash-0.1.0.dist-info/licenses/LICENSE +21 -0
  69. kailash-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,480 @@
1
+ """GraphQL client nodes for the Kailash SDK.
2
+
3
+ This module provides specialized nodes for interacting with GraphQL APIs in both
4
+ synchronous and asynchronous modes. These nodes provide a higher-level interface
5
+ for constructing and executing GraphQL queries.
6
+
7
+ Key Components:
8
+ - GraphQLClientNode: Synchronous GraphQL API client
9
+ - AsyncGraphQLClientNode: Asynchronous GraphQL API client
10
+ - GraphQL query building and response handling utilities
11
+ """
12
+
13
+ from typing import Any, Dict, Optional
14
+
15
+ from kailash.nodes.api.http import AsyncHTTPRequestNode, HTTPRequestNode
16
+ from kailash.nodes.base import Node, NodeParameter, register_node
17
+ from kailash.nodes.base_async import AsyncNode
18
+ from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
19
+
20
+
21
+ @register_node(alias="GraphQLClient")
22
+ class GraphQLClientNode(Node):
23
+ """Node for interacting with GraphQL APIs.
24
+
25
+ This node provides a specialized interface for executing GraphQL queries
26
+ and mutations, with support for:
27
+ - Query and mutation operations
28
+ - Variables and fragments
29
+ - Response selection and formatting
30
+ - Error handling for GraphQL-specific error formats
31
+
32
+ Design Purpose:
33
+ - Simplify GraphQL API integration in workflows
34
+ - Abstract away GraphQL-specific protocol details
35
+ - Provide type-safe variable handling
36
+ - Support all GraphQL operations and features
37
+
38
+ Upstream Usage:
39
+ - Workflow: Creates and configures for specific GraphQL APIs
40
+ - API integration workflows: Uses for external service integration
41
+
42
+ Downstream Consumers:
43
+ - Data processing nodes: Consume API response data
44
+ - Custom nodes: Process API-specific data formats
45
+ """
46
+
47
+ def __init__(self, **kwargs):
48
+ """Initialize the GraphQL client node.
49
+
50
+ Args:
51
+ endpoint (str): GraphQL endpoint URL
52
+ headers (dict, optional): Default headers for all requests
53
+ auth (dict, optional): Authentication configuration
54
+ timeout (int, optional): Default request timeout in seconds
55
+ verify_ssl (bool, optional): Whether to verify SSL certificates
56
+ retry_count (int, optional): Number of times to retry failed requests
57
+ retry_backoff (float, optional): Backoff factor for retries
58
+ **kwargs: Additional parameters passed to base Node
59
+ """
60
+ super().__init__(**kwargs)
61
+ self.http_node = HTTPRequestNode(**kwargs)
62
+
63
+ def get_parameters(self) -> Dict[str, NodeParameter]:
64
+ """Define the parameters this node accepts.
65
+
66
+ Returns:
67
+ Dictionary of parameter definitions
68
+ """
69
+ return {
70
+ "endpoint": NodeParameter(
71
+ name="endpoint",
72
+ type=str,
73
+ required=True,
74
+ description="GraphQL endpoint URL",
75
+ ),
76
+ "query": NodeParameter(
77
+ name="query",
78
+ type=str,
79
+ required=True,
80
+ description="GraphQL query or mutation string",
81
+ ),
82
+ "variables": NodeParameter(
83
+ name="variables",
84
+ type=dict,
85
+ required=False,
86
+ default={},
87
+ description="Variables for the GraphQL query",
88
+ ),
89
+ "operation_name": NodeParameter(
90
+ name="operation_name",
91
+ type=str,
92
+ required=False,
93
+ default=None,
94
+ description="Name of the operation to execute (if query contains multiple)",
95
+ ),
96
+ "headers": NodeParameter(
97
+ name="headers",
98
+ type=dict,
99
+ required=False,
100
+ default={},
101
+ description="HTTP headers to include in the request",
102
+ ),
103
+ "timeout": NodeParameter(
104
+ name="timeout",
105
+ type=int,
106
+ required=False,
107
+ default=30,
108
+ description="Request timeout in seconds",
109
+ ),
110
+ "verify_ssl": NodeParameter(
111
+ name="verify_ssl",
112
+ type=bool,
113
+ required=False,
114
+ default=True,
115
+ description="Whether to verify SSL certificates",
116
+ ),
117
+ "retry_count": NodeParameter(
118
+ name="retry_count",
119
+ type=int,
120
+ required=False,
121
+ default=0,
122
+ description="Number of times to retry failed requests",
123
+ ),
124
+ "retry_backoff": NodeParameter(
125
+ name="retry_backoff",
126
+ type=float,
127
+ required=False,
128
+ default=0.5,
129
+ description="Backoff factor for retries",
130
+ ),
131
+ "flatten_response": NodeParameter(
132
+ name="flatten_response",
133
+ type=bool,
134
+ required=False,
135
+ default=False,
136
+ description="Whether to flatten the response data structure",
137
+ ),
138
+ }
139
+
140
+ def get_output_schema(self) -> Dict[str, NodeParameter]:
141
+ """Define the output schema for this node.
142
+
143
+ Returns:
144
+ Dictionary of output parameter definitions
145
+ """
146
+ return {
147
+ "data": NodeParameter(
148
+ name="data",
149
+ type=Any,
150
+ required=True,
151
+ description="GraphQL response data",
152
+ ),
153
+ "errors": NodeParameter(
154
+ name="errors",
155
+ type=list,
156
+ required=False,
157
+ description="GraphQL errors (if any)",
158
+ ),
159
+ "success": NodeParameter(
160
+ name="success",
161
+ type=bool,
162
+ required=True,
163
+ description="Whether the request was successful (no errors)",
164
+ ),
165
+ "metadata": NodeParameter(
166
+ name="metadata",
167
+ type=dict,
168
+ required=True,
169
+ description="Additional metadata about the request and response",
170
+ ),
171
+ }
172
+
173
+ def _build_graphql_payload(
174
+ self,
175
+ query: str,
176
+ variables: Dict[str, Any] = None,
177
+ operation_name: Optional[str] = None,
178
+ ) -> Dict[str, Any]:
179
+ """Build a GraphQL request payload.
180
+
181
+ Args:
182
+ query: GraphQL query or mutation string
183
+ variables: Variables for the query
184
+ operation_name: Name of the operation to execute
185
+
186
+ Returns:
187
+ Dictionary containing the GraphQL request payload
188
+ """
189
+ payload = {"query": query}
190
+
191
+ if variables:
192
+ payload["variables"] = variables
193
+
194
+ if operation_name:
195
+ payload["operationName"] = operation_name
196
+
197
+ return payload
198
+
199
+ def _process_graphql_response(
200
+ self, response: Dict[str, Any], flatten_response: bool = False
201
+ ) -> Dict[str, Any]:
202
+ """Process a GraphQL response.
203
+
204
+ Args:
205
+ response: Raw HTTP response from GraphQL API
206
+ flatten_response: Whether to flatten the response data structure
207
+
208
+ Returns:
209
+ Processed GraphQL response with data and errors
210
+
211
+ Raises:
212
+ NodeExecutionError: If the response has an invalid format
213
+ """
214
+ content = response.get("content", {})
215
+
216
+ # Validate GraphQL response format
217
+ if not isinstance(content, dict):
218
+ raise NodeExecutionError(
219
+ f"Invalid GraphQL response format: expected dict, got {type(content).__name__}"
220
+ )
221
+
222
+ # Check for transport-level errors (non-200 responses)
223
+ if response.get("status_code", 200) >= 400:
224
+ error_msg = "GraphQL request failed with status code " + str(
225
+ response.get("status_code")
226
+ )
227
+ if isinstance(content, dict) and "errors" not in content:
228
+ # Add transport error to GraphQL error format
229
+ content = {"data": None, "errors": [{"message": error_msg}]}
230
+
231
+ # Extract data and errors
232
+ data = content.get("data")
233
+ errors = content.get("errors", [])
234
+ success = response.get("status_code", 200) < 400 and not errors
235
+
236
+ # Optional: Flatten data structure
237
+ if flatten_response and data and isinstance(data, dict):
238
+ # Get the first key in data (usually operation name) and return its value
239
+ if len(data) == 1:
240
+ data = next(iter(data.values()))
241
+
242
+ return {"data": data, "errors": errors, "success": success}
243
+
244
+ def run(self, **kwargs) -> Dict[str, Any]:
245
+ """Execute a GraphQL query or mutation.
246
+
247
+ Args:
248
+ endpoint (str): GraphQL endpoint URL
249
+ query (str): GraphQL query or mutation string
250
+ variables (dict, optional): Variables for the query
251
+ operation_name (str, optional): Name of the operation to execute
252
+ headers (dict, optional): HTTP headers
253
+ timeout (int, optional): Request timeout in seconds
254
+ verify_ssl (bool, optional): Whether to verify SSL certificates
255
+ retry_count (int, optional): Number of times to retry failed requests
256
+ retry_backoff (float, optional): Backoff factor for retries
257
+ flatten_response (bool, optional): Whether to flatten the response
258
+
259
+ Returns:
260
+ Dictionary containing:
261
+ data: GraphQL response data
262
+ errors: List of GraphQL errors (if any)
263
+ success: Boolean indicating request success
264
+ metadata: Additional request/response metadata
265
+
266
+ Raises:
267
+ NodeValidationError: If required parameters are missing or invalid
268
+ NodeExecutionError: If the request fails
269
+ """
270
+ endpoint = kwargs.get("endpoint")
271
+ query = kwargs.get("query")
272
+ variables = kwargs.get("variables", {})
273
+ operation_name = kwargs.get("operation_name")
274
+ headers = kwargs.get("headers", {})
275
+ timeout = kwargs.get("timeout", 30)
276
+ verify_ssl = kwargs.get("verify_ssl", True)
277
+ retry_count = kwargs.get("retry_count", 0)
278
+ retry_backoff = kwargs.get("retry_backoff", 0.5)
279
+ flatten_response = kwargs.get("flatten_response", False)
280
+
281
+ # Validate required parameters
282
+ if not endpoint:
283
+ raise NodeValidationError("GraphQL endpoint URL is required")
284
+
285
+ if not query:
286
+ raise NodeValidationError("GraphQL query is required")
287
+
288
+ # Set content type for GraphQL requests
289
+ if "Content-Type" not in headers:
290
+ headers["Content-Type"] = "application/json"
291
+
292
+ # Build GraphQL payload
293
+ payload = self._build_graphql_payload(query, variables, operation_name)
294
+
295
+ # Execute HTTP request
296
+ self.logger.info(f"Executing GraphQL request to {endpoint}")
297
+ if variables:
298
+ self.logger.debug(f"With variables: {variables}")
299
+
300
+ http_params = {
301
+ "url": endpoint,
302
+ "method": "POST",
303
+ "headers": headers,
304
+ "json_data": payload,
305
+ "response_format": "json",
306
+ "timeout": timeout,
307
+ "verify_ssl": verify_ssl,
308
+ "retry_count": retry_count,
309
+ "retry_backoff": retry_backoff,
310
+ }
311
+
312
+ http_result = self.http_node.run(**http_params)
313
+
314
+ # Process GraphQL-specific response
315
+ response = http_result["response"]
316
+ graphql_result = self._process_graphql_response(response, flatten_response)
317
+
318
+ # Construct metadata
319
+ metadata = {
320
+ "endpoint": endpoint,
321
+ "operation_name": operation_name,
322
+ "response_time_ms": response["response_time_ms"],
323
+ "status_code": response["status_code"],
324
+ }
325
+
326
+ return {
327
+ "data": graphql_result["data"],
328
+ "errors": graphql_result["errors"],
329
+ "success": graphql_result["success"],
330
+ "metadata": metadata,
331
+ }
332
+
333
+
334
+ @register_node(alias="AsyncGraphQLClient")
335
+ class AsyncGraphQLClientNode(AsyncNode):
336
+ """Asynchronous node for interacting with GraphQL APIs.
337
+
338
+ This node provides the same functionality as GraphQLClientNode but uses
339
+ asynchronous I/O for better performance, especially for concurrent requests.
340
+
341
+ Design Purpose:
342
+ - Enable efficient, non-blocking GraphQL operations in workflows
343
+ - Provide the same interface as GraphQLClientNode but with async execution
344
+ - Support high-throughput API integrations with minimal overhead
345
+
346
+ Upstream Usage:
347
+ - AsyncLocalRuntime: Executes workflow with async support
348
+ - Specialized async API nodes: May extend this node
349
+
350
+ Downstream Consumers:
351
+ - Data processing nodes: Consume API response data
352
+ - Decision nodes: Route workflow based on API responses
353
+ """
354
+
355
+ def __init__(self, **kwargs):
356
+ """Initialize the async GraphQL client node.
357
+
358
+ Args:
359
+ Same as GraphQLClientNode
360
+ """
361
+ super().__init__(**kwargs)
362
+ self.http_node = AsyncHTTPRequestNode(**kwargs)
363
+ self.graphql_node = GraphQLClientNode(**kwargs)
364
+
365
+ def get_parameters(self) -> Dict[str, NodeParameter]:
366
+ """Define the parameters this node accepts.
367
+
368
+ Returns:
369
+ Dictionary of parameter definitions
370
+ """
371
+ # Same parameters as the synchronous version
372
+ return self.graphql_node.get_parameters()
373
+
374
+ def get_output_schema(self) -> Dict[str, NodeParameter]:
375
+ """Define the output schema for this node.
376
+
377
+ Returns:
378
+ Dictionary of output parameter definitions
379
+ """
380
+ # Same output schema as the synchronous version
381
+ return self.graphql_node.get_output_schema()
382
+
383
+ def run(self, **kwargs) -> Dict[str, Any]:
384
+ """Synchronous version of the GraphQL request, for compatibility.
385
+
386
+ This is implemented for compatibility but users should use the
387
+ async_run method for better performance.
388
+
389
+ Args:
390
+ Same as GraphQLClientNode.run()
391
+
392
+ Returns:
393
+ Same as GraphQLClientNode.run()
394
+
395
+ Raises:
396
+ NodeValidationError: If required parameters are missing or invalid
397
+ NodeExecutionError: If the request fails
398
+ """
399
+ # Forward to the synchronous GraphQL node
400
+ return self.graphql_node.run(**kwargs)
401
+
402
+ async def async_run(self, **kwargs) -> Dict[str, Any]:
403
+ """Execute a GraphQL query or mutation asynchronously.
404
+
405
+ Args:
406
+ Same as GraphQLClientNode.run()
407
+
408
+ Returns:
409
+ Same as GraphQLClientNode.run()
410
+
411
+ Raises:
412
+ NodeValidationError: If required parameters are missing or invalid
413
+ NodeExecutionError: If the request fails
414
+ """
415
+ endpoint = kwargs.get("endpoint")
416
+ query = kwargs.get("query")
417
+ variables = kwargs.get("variables", {})
418
+ operation_name = kwargs.get("operation_name")
419
+ headers = kwargs.get("headers", {})
420
+ timeout = kwargs.get("timeout", 30)
421
+ verify_ssl = kwargs.get("verify_ssl", True)
422
+ retry_count = kwargs.get("retry_count", 0)
423
+ retry_backoff = kwargs.get("retry_backoff", 0.5)
424
+ flatten_response = kwargs.get("flatten_response", False)
425
+
426
+ # Validate required parameters
427
+ if not endpoint:
428
+ raise NodeValidationError("GraphQL endpoint URL is required")
429
+
430
+ if not query:
431
+ raise NodeValidationError("GraphQL query is required")
432
+
433
+ # Set content type for GraphQL requests
434
+ if "Content-Type" not in headers:
435
+ headers["Content-Type"] = "application/json"
436
+
437
+ # Build GraphQL payload
438
+ payload = self.graphql_node._build_graphql_payload(
439
+ query, variables, operation_name
440
+ )
441
+
442
+ # Execute HTTP request asynchronously
443
+ self.logger.info(f"Executing async GraphQL request to {endpoint}")
444
+ if variables:
445
+ self.logger.debug(f"With variables: {variables}")
446
+
447
+ http_params = {
448
+ "url": endpoint,
449
+ "method": "POST",
450
+ "headers": headers,
451
+ "json_data": payload,
452
+ "response_format": "json",
453
+ "timeout": timeout,
454
+ "verify_ssl": verify_ssl,
455
+ "retry_count": retry_count,
456
+ "retry_backoff": retry_backoff,
457
+ }
458
+
459
+ http_result = await self.http_node.async_run(**http_params)
460
+
461
+ # Process GraphQL-specific response
462
+ response = http_result["response"]
463
+ graphql_result = self.graphql_node._process_graphql_response(
464
+ response, flatten_response
465
+ )
466
+
467
+ # Construct metadata
468
+ metadata = {
469
+ "endpoint": endpoint,
470
+ "operation_name": operation_name,
471
+ "response_time_ms": response["response_time_ms"],
472
+ "status_code": response["status_code"],
473
+ }
474
+
475
+ return {
476
+ "data": graphql_result["data"],
477
+ "errors": graphql_result["errors"],
478
+ "success": graphql_result["success"],
479
+ "metadata": metadata,
480
+ }