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,567 @@
1
+ """Authentication nodes for API requests in the Kailash SDK.
2
+
3
+ This module provides nodes for handling various authentication methods for API
4
+ requests. These nodes can be used in workflows to authenticate requests to
5
+ external services.
6
+
7
+ Key Components:
8
+ - BasicAuthNode: Basic authentication (username/password)
9
+ - OAuth2Node: OAuth 2.0 authentication flow
10
+ - APIKeyNode: API key authentication
11
+ """
12
+
13
+ import base64
14
+ import time
15
+ from typing import Any, Dict
16
+
17
+ import requests
18
+
19
+ from kailash.nodes.api.http import HTTPRequestNode
20
+ from kailash.nodes.base import Node, NodeParameter, register_node
21
+ from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
22
+
23
+
24
+ @register_node(alias="BasicAuth")
25
+ class BasicAuthNode(Node):
26
+ """Node for adding Basic Authentication to API requests.
27
+
28
+ This node generates HTTP headers for Basic Authentication and can be used
29
+ to authenticate requests to APIs that support this method.
30
+
31
+ Design Purpose:
32
+ - Provide a simple way to add Basic Auth to API requests
33
+ - Abstract away the encoding details
34
+ - Support secure credential handling
35
+
36
+ Upstream Usage:
37
+ - Workflow: Creates and configures with API credentials
38
+ - HTTP/REST nodes: Consume the auth headers
39
+
40
+ Downstream Consumers:
41
+ - HTTPRequestNode: Uses auth headers for requests
42
+ - RESTClientNode: Uses auth headers for API calls
43
+ """
44
+
45
+ def get_parameters(self) -> Dict[str, NodeParameter]:
46
+ """Define the parameters this node accepts.
47
+
48
+ Returns:
49
+ Dictionary of parameter definitions
50
+ """
51
+ return {
52
+ "username": NodeParameter(
53
+ name="username",
54
+ type=str,
55
+ required=True,
56
+ description="Username for Basic Authentication",
57
+ ),
58
+ "password": NodeParameter(
59
+ name="password",
60
+ type=str,
61
+ required=True,
62
+ description="Password for Basic Authentication",
63
+ ),
64
+ }
65
+
66
+ def get_output_schema(self) -> Dict[str, NodeParameter]:
67
+ """Define the output schema for this node.
68
+
69
+ Returns:
70
+ Dictionary of output parameter definitions
71
+ """
72
+ return {
73
+ "headers": NodeParameter(
74
+ name="headers",
75
+ type=dict,
76
+ required=True,
77
+ description="HTTP headers with Basic Authentication",
78
+ ),
79
+ "auth_type": NodeParameter(
80
+ name="auth_type",
81
+ type=str,
82
+ required=True,
83
+ description="Authentication type (always 'basic')",
84
+ ),
85
+ }
86
+
87
+ def run(self, **kwargs) -> Dict[str, Any]:
88
+ """Generate Basic Authentication headers.
89
+
90
+ Args:
91
+ username (str): Username for authentication
92
+ password (str): Password for authentication
93
+
94
+ Returns:
95
+ Dictionary containing:
96
+ headers: HTTP headers with Basic Authentication
97
+ auth_type: Authentication type ('basic')
98
+ """
99
+ username = kwargs.get("username")
100
+ password = kwargs.get("password")
101
+
102
+ # Validate required parameters
103
+ if not username:
104
+ raise NodeValidationError("Username is required for Basic Authentication")
105
+
106
+ if not password:
107
+ raise NodeValidationError("Password is required for Basic Authentication")
108
+
109
+ # Generate auth string
110
+ auth_string = f"{username}:{password}"
111
+ encoded_auth = base64.b64encode(auth_string.encode()).decode()
112
+
113
+ # Create headers
114
+ headers = {"Authorization": f"Basic {encoded_auth}"}
115
+
116
+ return {"headers": headers, "auth_type": "basic"}
117
+
118
+
119
+ @register_node(alias="OAuth2")
120
+ class OAuth2Node(Node):
121
+ """Node for handling OAuth 2.0 authentication flows.
122
+
123
+ This node supports various OAuth 2.0 flows including:
124
+ - Client Credentials
125
+ - Password Grant
126
+ - Authorization Code (with PKCE)
127
+ - Refresh Token
128
+
129
+ It handles token acquisition, storage, and renewal for API requests.
130
+
131
+ Design Purpose:
132
+ - Simplify OAuth 2.0 authentication for API requests
133
+ - Handle token lifecycle (request, store, refresh)
134
+ - Support multiple OAuth flows
135
+ - Abstract away OAuth implementation details
136
+
137
+ Upstream Usage:
138
+ - Workflow: Creates and configures with OAuth credentials
139
+ - HTTP/REST nodes: Consume the auth headers
140
+
141
+ Downstream Consumers:
142
+ - HTTPRequestNode: Uses auth headers for requests
143
+ - RESTClientNode: Uses auth headers for API calls
144
+ """
145
+
146
+ def __init__(self, **kwargs):
147
+ """Initialize the OAuth2 node.
148
+
149
+ Args:
150
+ token_url (str): OAuth token endpoint URL
151
+ client_id (str): OAuth client ID
152
+ client_secret (str, optional): OAuth client secret
153
+ grant_type (str): OAuth grant type
154
+ scope (str, optional): OAuth scopes (space-separated)
155
+ username (str, optional): Username for password grant
156
+ password (str, optional): Password for password grant
157
+ refresh_token (str, optional): Refresh token for refresh flow
158
+ **kwargs: Additional parameters passed to base Node
159
+ """
160
+ super().__init__(**kwargs)
161
+ self.http_node = HTTPRequestNode(**kwargs)
162
+ self.token_data = None # Will store token information
163
+ self.token_expires_at = 0 # Timestamp when token expires
164
+
165
+ def get_parameters(self) -> Dict[str, NodeParameter]:
166
+ """Define the parameters this node accepts.
167
+
168
+ Returns:
169
+ Dictionary of parameter definitions
170
+ """
171
+ return {
172
+ "token_url": NodeParameter(
173
+ name="token_url",
174
+ type=str,
175
+ required=True,
176
+ description="OAuth token endpoint URL",
177
+ ),
178
+ "client_id": NodeParameter(
179
+ name="client_id", type=str, required=True, description="OAuth client ID"
180
+ ),
181
+ "client_secret": NodeParameter(
182
+ name="client_secret",
183
+ type=str,
184
+ required=False,
185
+ default=None,
186
+ description="OAuth client secret",
187
+ ),
188
+ "grant_type": NodeParameter(
189
+ name="grant_type",
190
+ type=str,
191
+ required=True,
192
+ default="client_credentials",
193
+ description="OAuth grant type (client_credentials, password, authorization_code, refresh_token)",
194
+ ),
195
+ "scope": NodeParameter(
196
+ name="scope",
197
+ type=str,
198
+ required=False,
199
+ default=None,
200
+ description="OAuth scopes (space-separated)",
201
+ ),
202
+ "username": NodeParameter(
203
+ name="username",
204
+ type=str,
205
+ required=False,
206
+ default=None,
207
+ description="Username (for password grant)",
208
+ ),
209
+ "password": NodeParameter(
210
+ name="password",
211
+ type=str,
212
+ required=False,
213
+ default=None,
214
+ description="Password (for password grant)",
215
+ ),
216
+ "refresh_token": NodeParameter(
217
+ name="refresh_token",
218
+ type=str,
219
+ required=False,
220
+ default=None,
221
+ description="Refresh token (for refresh_token grant)",
222
+ ),
223
+ "token_storage": NodeParameter(
224
+ name="token_storage",
225
+ type=dict,
226
+ required=False,
227
+ default=None,
228
+ description="Token storage configuration",
229
+ ),
230
+ "auto_refresh": NodeParameter(
231
+ name="auto_refresh",
232
+ type=bool,
233
+ required=False,
234
+ default=True,
235
+ description="Whether to automatically refresh expired tokens",
236
+ ),
237
+ }
238
+
239
+ def get_output_schema(self) -> Dict[str, NodeParameter]:
240
+ """Define the output schema for this node.
241
+
242
+ Returns:
243
+ Dictionary of output parameter definitions
244
+ """
245
+ return {
246
+ "headers": NodeParameter(
247
+ name="headers",
248
+ type=dict,
249
+ required=True,
250
+ description="HTTP headers with OAuth token",
251
+ ),
252
+ "token_data": NodeParameter(
253
+ name="token_data",
254
+ type=dict,
255
+ required=True,
256
+ description="Complete token response data",
257
+ ),
258
+ "auth_type": NodeParameter(
259
+ name="auth_type",
260
+ type=str,
261
+ required=True,
262
+ description="Authentication type (always 'oauth2')",
263
+ ),
264
+ "expires_in": NodeParameter(
265
+ name="expires_in",
266
+ type=int,
267
+ required=False,
268
+ description="Seconds until token expiration",
269
+ ),
270
+ }
271
+
272
+ def _get_token(self, **kwargs) -> Dict[str, Any]:
273
+ """Get an OAuth token using the configured grant type.
274
+
275
+ This method handles different grant types with appropriate parameters.
276
+
277
+ Args:
278
+ kwargs: Parameters from run method
279
+
280
+ Returns:
281
+ Dictionary containing token response data
282
+
283
+ Raises:
284
+ NodeExecutionError: If token acquisition fails
285
+ """
286
+ token_url = kwargs.get("token_url")
287
+ client_id = kwargs.get("client_id")
288
+ client_secret = kwargs.get("client_secret")
289
+ grant_type = kwargs.get("grant_type", "client_credentials")
290
+ scope = kwargs.get("scope")
291
+ username = kwargs.get("username")
292
+ password = kwargs.get("password")
293
+ refresh_token = (
294
+ kwargs.get("refresh_token")
295
+ or self.token_data
296
+ and self.token_data.get("refresh_token")
297
+ )
298
+
299
+ # Build request data based on grant type
300
+ data = {"grant_type": grant_type, "client_id": client_id}
301
+
302
+ if client_secret:
303
+ data["client_secret"] = client_secret
304
+
305
+ if scope:
306
+ data["scope"] = scope
307
+
308
+ # Add grant-specific parameters
309
+ if grant_type == "password":
310
+ if not username or not password:
311
+ raise NodeValidationError(
312
+ "Username and password are required for password grant type"
313
+ )
314
+ data["username"] = username
315
+ data["password"] = password
316
+
317
+ elif grant_type == "refresh_token":
318
+ if not refresh_token:
319
+ raise NodeValidationError(
320
+ "Refresh token is required for refresh_token grant type"
321
+ )
322
+ data["refresh_token"] = refresh_token
323
+
324
+ # Make token request
325
+ self.logger.info(
326
+ f"Requesting OAuth token from {token_url} using {grant_type} grant"
327
+ )
328
+
329
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
330
+
331
+ # Some OAuth servers require Basic auth with client credentials
332
+ if client_id and client_secret:
333
+ auth_string = f"{client_id}:{client_secret}"
334
+ encoded_auth = base64.b64encode(auth_string.encode()).decode()
335
+ headers["Authorization"] = f"Basic {encoded_auth}"
336
+
337
+ # Remove from form body if using auth header
338
+ if "client_secret" in data:
339
+ del data["client_secret"]
340
+
341
+ try:
342
+ response = requests.post(token_url, data=data, headers=headers, timeout=30)
343
+
344
+ response.raise_for_status()
345
+ token_data = response.json()
346
+
347
+ # Check for required fields in response
348
+ if "access_token" not in token_data:
349
+ raise NodeExecutionError(
350
+ f"Invalid OAuth response: missing access_token. Response: {token_data}"
351
+ )
352
+
353
+ return token_data
354
+
355
+ except requests.RequestException as e:
356
+ raise NodeExecutionError(f"Failed to acquire OAuth token: {str(e)}") from e
357
+ except ValueError as e:
358
+ raise NodeExecutionError(
359
+ f"Failed to parse OAuth token response: {str(e)}"
360
+ ) from e
361
+
362
+ def run(self, **kwargs) -> Dict[str, Any]:
363
+ """Get OAuth authentication headers.
364
+
365
+ This method handles token acquisition, caching, and renewal based on
366
+ the configured OAuth flow.
367
+
368
+ Args:
369
+ token_url (str): OAuth token endpoint URL
370
+ client_id (str): OAuth client ID
371
+ client_secret (str, optional): OAuth client secret
372
+ grant_type (str): OAuth grant type
373
+ scope (str, optional): OAuth scopes
374
+ username (str, optional): Username for password grant
375
+ password (str, optional): Password for password grant
376
+ refresh_token (str, optional): Refresh token for refresh flow
377
+ token_storage (dict, optional): Token storage configuration
378
+ auto_refresh (bool, optional): Whether to auto-refresh expired tokens
379
+
380
+ Returns:
381
+ Dictionary containing:
382
+ headers: HTTP headers with OAuth token
383
+ token_data: Complete token response
384
+ auth_type: Authentication type ('oauth2')
385
+ expires_in: Seconds until token expiration
386
+ """
387
+ force_refresh = kwargs.get("force_refresh", False)
388
+ auto_refresh = kwargs.get("auto_refresh", True)
389
+
390
+ current_time = time.time()
391
+
392
+ # Check if we need to refresh the token
393
+ if (
394
+ not self.token_data
395
+ or force_refresh
396
+ or (auto_refresh and current_time >= self.token_expires_at)
397
+ ):
398
+
399
+ # Get new token
400
+ self.token_data = self._get_token(**kwargs)
401
+
402
+ # Calculate expiration time
403
+ expires_in = self.token_data.get("expires_in", 3600) # Default 1 hour
404
+ self.token_expires_at = current_time + expires_in
405
+
406
+ self.logger.info(
407
+ f"Acquired new OAuth token, expires in {expires_in} seconds"
408
+ )
409
+
410
+ # Calculate current expiration time
411
+ current_expires_in = max(0, int(self.token_expires_at - current_time))
412
+
413
+ # Create headers
414
+ headers = {"Authorization": f"Bearer {self.token_data['access_token']}"}
415
+
416
+ return {
417
+ "headers": headers,
418
+ "token_data": self.token_data,
419
+ "auth_type": "oauth2",
420
+ "expires_in": current_expires_in,
421
+ }
422
+
423
+
424
+ @register_node(alias="APIKey")
425
+ class APIKeyNode(Node):
426
+ """Node for API key authentication.
427
+
428
+ This node handles API key authentication in various formats:
429
+ - Header-based (e.g., "X-API-Key: key123")
430
+ - Query parameter (e.g., "?api_key=key123")
431
+ - Request body parameter
432
+
433
+ Design Purpose:
434
+ - Simplify API key authentication for API requests
435
+ - Support different API key placement formats
436
+ - Abstract away implementation details
437
+
438
+ Upstream Usage:
439
+ - Workflow: Creates and configures with API key
440
+ - HTTP/REST nodes: Consume the auth data
441
+
442
+ Downstream Consumers:
443
+ - HTTPRequestNode: Uses auth data for requests
444
+ - RESTClientNode: Uses auth data for API calls
445
+ """
446
+
447
+ def get_parameters(self) -> Dict[str, NodeParameter]:
448
+ """Define the parameters this node accepts.
449
+
450
+ Returns:
451
+ Dictionary of parameter definitions
452
+ """
453
+ return {
454
+ "api_key": NodeParameter(
455
+ name="api_key", type=str, required=True, description="API key value"
456
+ ),
457
+ "location": NodeParameter(
458
+ name="location",
459
+ type=str,
460
+ required=True,
461
+ default="header",
462
+ description="Where to place the API key (header, query, body)",
463
+ ),
464
+ "param_name": NodeParameter(
465
+ name="param_name",
466
+ type=str,
467
+ required=True,
468
+ default="X-API-Key",
469
+ description="Parameter name for the API key",
470
+ ),
471
+ "prefix": NodeParameter(
472
+ name="prefix",
473
+ type=str,
474
+ required=False,
475
+ default=None,
476
+ description="Prefix for the API key value (e.g., 'Bearer')",
477
+ ),
478
+ }
479
+
480
+ def get_output_schema(self) -> Dict[str, NodeParameter]:
481
+ """Define the output schema for this node.
482
+
483
+ Returns:
484
+ Dictionary of output parameter definitions
485
+ """
486
+ return {
487
+ "headers": NodeParameter(
488
+ name="headers",
489
+ type=dict,
490
+ required=False,
491
+ description="HTTP headers with API key (if location=header)",
492
+ ),
493
+ "query_params": NodeParameter(
494
+ name="query_params",
495
+ type=dict,
496
+ required=False,
497
+ description="Query parameters with API key (if location=query)",
498
+ ),
499
+ "body_params": NodeParameter(
500
+ name="body_params",
501
+ type=dict,
502
+ required=False,
503
+ description="Body parameters with API key (if location=body)",
504
+ ),
505
+ "auth_type": NodeParameter(
506
+ name="auth_type",
507
+ type=str,
508
+ required=True,
509
+ description="Authentication type (always 'api_key')",
510
+ ),
511
+ }
512
+
513
+ def run(self, **kwargs) -> Dict[str, Any]:
514
+ """Generate API key authentication data.
515
+
516
+ Args:
517
+ api_key (str): API key value
518
+ location (str): Where to place the API key (header, query, body)
519
+ param_name (str): Parameter name for the API key
520
+ prefix (str, optional): Prefix for the API key value
521
+
522
+ Returns:
523
+ Dictionary containing:
524
+ headers: HTTP headers with API key (if location=header)
525
+ query_params: Query parameters with API key (if location=query)
526
+ body_params: Body parameters with API key (if location=body)
527
+ auth_type: Authentication type ('api_key')
528
+ """
529
+ api_key = kwargs.get("api_key")
530
+ location = kwargs.get("location", "header").lower()
531
+ param_name = kwargs.get("param_name", "X-API-Key")
532
+ prefix = kwargs.get("prefix")
533
+
534
+ # Validate required parameters
535
+ if not api_key:
536
+ raise NodeValidationError("API key is required")
537
+
538
+ if location not in ("header", "query", "body"):
539
+ raise NodeValidationError(
540
+ f"Invalid API key location: {location}. "
541
+ "Must be one of: header, query, body"
542
+ )
543
+
544
+ # Format API key value
545
+ key_value = api_key
546
+ if prefix:
547
+ key_value = f"{prefix} {api_key}"
548
+
549
+ # Create result based on location
550
+ result = {"auth_type": "api_key"}
551
+
552
+ if location == "header":
553
+ result["headers"] = {param_name: key_value}
554
+ result["query_params"] = {}
555
+ result["body_params"] = {}
556
+
557
+ elif location == "query":
558
+ result["headers"] = {}
559
+ result["query_params"] = {param_name: key_value}
560
+ result["body_params"] = {}
561
+
562
+ elif location == "body":
563
+ result["headers"] = {}
564
+ result["query_params"] = {}
565
+ result["body_params"] = {param_name: key_value}
566
+
567
+ return result