kailash 0.3.2__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.
Files changed (146) hide show
  1. kailash/__init__.py +33 -1
  2. kailash/access_control/__init__.py +129 -0
  3. kailash/access_control/managers.py +461 -0
  4. kailash/access_control/rule_evaluators.py +467 -0
  5. kailash/access_control_abac.py +825 -0
  6. kailash/config/__init__.py +27 -0
  7. kailash/config/database_config.py +359 -0
  8. kailash/database/__init__.py +28 -0
  9. kailash/database/execution_pipeline.py +499 -0
  10. kailash/middleware/__init__.py +306 -0
  11. kailash/middleware/auth/__init__.py +33 -0
  12. kailash/middleware/auth/access_control.py +436 -0
  13. kailash/middleware/auth/auth_manager.py +422 -0
  14. kailash/middleware/auth/jwt_auth.py +477 -0
  15. kailash/middleware/auth/kailash_jwt_auth.py +616 -0
  16. kailash/middleware/communication/__init__.py +37 -0
  17. kailash/middleware/communication/ai_chat.py +989 -0
  18. kailash/middleware/communication/api_gateway.py +802 -0
  19. kailash/middleware/communication/events.py +470 -0
  20. kailash/middleware/communication/realtime.py +710 -0
  21. kailash/middleware/core/__init__.py +21 -0
  22. kailash/middleware/core/agent_ui.py +890 -0
  23. kailash/middleware/core/schema.py +643 -0
  24. kailash/middleware/core/workflows.py +396 -0
  25. kailash/middleware/database/__init__.py +63 -0
  26. kailash/middleware/database/base.py +113 -0
  27. kailash/middleware/database/base_models.py +525 -0
  28. kailash/middleware/database/enums.py +106 -0
  29. kailash/middleware/database/migrations.py +12 -0
  30. kailash/{api/database.py → middleware/database/models.py} +183 -291
  31. kailash/middleware/database/repositories.py +685 -0
  32. kailash/middleware/database/session_manager.py +19 -0
  33. kailash/middleware/mcp/__init__.py +38 -0
  34. kailash/middleware/mcp/client_integration.py +585 -0
  35. kailash/middleware/mcp/enhanced_server.py +576 -0
  36. kailash/nodes/__init__.py +25 -3
  37. kailash/nodes/admin/__init__.py +35 -0
  38. kailash/nodes/admin/audit_log.py +794 -0
  39. kailash/nodes/admin/permission_check.py +864 -0
  40. kailash/nodes/admin/role_management.py +823 -0
  41. kailash/nodes/admin/security_event.py +1519 -0
  42. kailash/nodes/admin/user_management.py +944 -0
  43. kailash/nodes/ai/a2a.py +24 -7
  44. kailash/nodes/ai/ai_providers.py +1 -0
  45. kailash/nodes/ai/embedding_generator.py +11 -11
  46. kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
  47. kailash/nodes/ai/llm_agent.py +407 -2
  48. kailash/nodes/ai/self_organizing.py +85 -10
  49. kailash/nodes/api/auth.py +287 -6
  50. kailash/nodes/api/rest.py +151 -0
  51. kailash/nodes/auth/__init__.py +17 -0
  52. kailash/nodes/auth/directory_integration.py +1228 -0
  53. kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
  54. kailash/nodes/auth/mfa.py +2338 -0
  55. kailash/nodes/auth/risk_assessment.py +872 -0
  56. kailash/nodes/auth/session_management.py +1093 -0
  57. kailash/nodes/auth/sso.py +1040 -0
  58. kailash/nodes/base.py +344 -13
  59. kailash/nodes/base_cycle_aware.py +4 -2
  60. kailash/nodes/base_with_acl.py +1 -1
  61. kailash/nodes/code/python.py +283 -10
  62. kailash/nodes/compliance/__init__.py +9 -0
  63. kailash/nodes/compliance/data_retention.py +1888 -0
  64. kailash/nodes/compliance/gdpr.py +2004 -0
  65. kailash/nodes/data/__init__.py +22 -2
  66. kailash/nodes/data/async_connection.py +469 -0
  67. kailash/nodes/data/async_sql.py +757 -0
  68. kailash/nodes/data/async_vector.py +598 -0
  69. kailash/nodes/data/readers.py +767 -0
  70. kailash/nodes/data/retrieval.py +360 -1
  71. kailash/nodes/data/sharepoint_graph.py +397 -21
  72. kailash/nodes/data/sql.py +94 -5
  73. kailash/nodes/data/streaming.py +68 -8
  74. kailash/nodes/data/vector_db.py +54 -4
  75. kailash/nodes/enterprise/__init__.py +13 -0
  76. kailash/nodes/enterprise/batch_processor.py +741 -0
  77. kailash/nodes/enterprise/data_lineage.py +497 -0
  78. kailash/nodes/logic/convergence.py +31 -9
  79. kailash/nodes/logic/operations.py +14 -3
  80. kailash/nodes/mixins/__init__.py +8 -0
  81. kailash/nodes/mixins/event_emitter.py +201 -0
  82. kailash/nodes/mixins/mcp.py +9 -4
  83. kailash/nodes/mixins/security.py +165 -0
  84. kailash/nodes/monitoring/__init__.py +7 -0
  85. kailash/nodes/monitoring/performance_benchmark.py +2497 -0
  86. kailash/nodes/rag/__init__.py +284 -0
  87. kailash/nodes/rag/advanced.py +1615 -0
  88. kailash/nodes/rag/agentic.py +773 -0
  89. kailash/nodes/rag/conversational.py +999 -0
  90. kailash/nodes/rag/evaluation.py +875 -0
  91. kailash/nodes/rag/federated.py +1188 -0
  92. kailash/nodes/rag/graph.py +721 -0
  93. kailash/nodes/rag/multimodal.py +671 -0
  94. kailash/nodes/rag/optimized.py +933 -0
  95. kailash/nodes/rag/privacy.py +1059 -0
  96. kailash/nodes/rag/query_processing.py +1335 -0
  97. kailash/nodes/rag/realtime.py +764 -0
  98. kailash/nodes/rag/registry.py +547 -0
  99. kailash/nodes/rag/router.py +837 -0
  100. kailash/nodes/rag/similarity.py +1854 -0
  101. kailash/nodes/rag/strategies.py +566 -0
  102. kailash/nodes/rag/workflows.py +575 -0
  103. kailash/nodes/security/__init__.py +19 -0
  104. kailash/nodes/security/abac_evaluator.py +1411 -0
  105. kailash/nodes/security/audit_log.py +91 -0
  106. kailash/nodes/security/behavior_analysis.py +1893 -0
  107. kailash/nodes/security/credential_manager.py +401 -0
  108. kailash/nodes/security/rotating_credentials.py +760 -0
  109. kailash/nodes/security/security_event.py +132 -0
  110. kailash/nodes/security/threat_detection.py +1103 -0
  111. kailash/nodes/testing/__init__.py +9 -0
  112. kailash/nodes/testing/credential_testing.py +499 -0
  113. kailash/nodes/transform/__init__.py +10 -2
  114. kailash/nodes/transform/chunkers.py +592 -1
  115. kailash/nodes/transform/processors.py +484 -14
  116. kailash/nodes/validation.py +321 -0
  117. kailash/runtime/access_controlled.py +1 -1
  118. kailash/runtime/async_local.py +41 -7
  119. kailash/runtime/docker.py +1 -1
  120. kailash/runtime/local.py +474 -55
  121. kailash/runtime/parallel.py +1 -1
  122. kailash/runtime/parallel_cyclic.py +1 -1
  123. kailash/runtime/testing.py +210 -2
  124. kailash/utils/migrations/__init__.py +25 -0
  125. kailash/utils/migrations/generator.py +433 -0
  126. kailash/utils/migrations/models.py +231 -0
  127. kailash/utils/migrations/runner.py +489 -0
  128. kailash/utils/secure_logging.py +342 -0
  129. kailash/workflow/__init__.py +16 -0
  130. kailash/workflow/cyclic_runner.py +3 -4
  131. kailash/workflow/graph.py +70 -2
  132. kailash/workflow/resilience.py +249 -0
  133. kailash/workflow/templates.py +726 -0
  134. {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/METADATA +253 -20
  135. kailash-0.4.0.dist-info/RECORD +223 -0
  136. kailash/api/__init__.py +0 -17
  137. kailash/api/__main__.py +0 -6
  138. kailash/api/studio_secure.py +0 -893
  139. kailash/mcp/__main__.py +0 -13
  140. kailash/mcp/server_new.py +0 -336
  141. kailash/mcp/servers/__init__.py +0 -12
  142. kailash-0.3.2.dist-info/RECORD +0 -136
  143. {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/WHEEL +0 -0
  144. {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/entry_points.txt +0 -0
  145. {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/licenses/LICENSE +0 -0
  146. {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,499 @@
1
+ """Database execution pipeline for clean separation of concerns.
2
+
3
+ This module provides a pipeline-based approach to database operations,
4
+ separating permission checking, query execution, and data masking into
5
+ clear, testable stages.
6
+ """
7
+
8
+ import logging
9
+ import time
10
+ from abc import ABC, abstractmethod
11
+ from dataclasses import dataclass
12
+ from typing import Any, Dict, List, Optional, Union
13
+
14
+ from kailash.access_control import NodePermission, UserContext
15
+ from kailash.sdk_exceptions import NodeExecutionError
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class ExecutionContext:
22
+ """Context for database execution pipeline."""
23
+
24
+ query: str
25
+ parameters: Optional[Union[Dict[str, Any], List[Any]]] = None
26
+ user_context: Optional[UserContext] = None
27
+ node_name: str = "unknown_node"
28
+ result_format: str = "dict"
29
+ runtime_context: Optional[Dict[str, Any]] = None
30
+
31
+
32
+ @dataclass
33
+ class ExecutionResult:
34
+ """Result from database execution pipeline."""
35
+
36
+ data: Any
37
+ row_count: int
38
+ columns: List[str]
39
+ execution_time: float
40
+ metadata: Optional[Dict[str, Any]] = None
41
+
42
+
43
+ class PipelineStage(ABC):
44
+ """Abstract base class for pipeline stages."""
45
+
46
+ @abstractmethod
47
+ async def process(
48
+ self, context: ExecutionContext, result: Optional[ExecutionResult] = None
49
+ ) -> Optional[ExecutionResult]:
50
+ """Process this stage of the pipeline.
51
+
52
+ Args:
53
+ context: Execution context
54
+ result: Result from previous stage (None for first stage)
55
+
56
+ Returns:
57
+ Result to pass to next stage, or None to stop pipeline
58
+ """
59
+ pass
60
+
61
+ @abstractmethod
62
+ def get_stage_name(self) -> str:
63
+ """Get the name of this pipeline stage."""
64
+ pass
65
+
66
+
67
+ class PermissionCheckStage(PipelineStage):
68
+ """Pipeline stage for checking user permissions."""
69
+
70
+ def __init__(self, access_control_manager=None):
71
+ """Initialize permission check stage.
72
+
73
+ Args:
74
+ access_control_manager: Access control manager for permission checks
75
+ """
76
+ self.access_control_manager = access_control_manager
77
+ self.logger = logging.getLogger(f"{__name__}.PermissionCheckStage")
78
+
79
+ async def process(
80
+ self, context: ExecutionContext, result: Optional[ExecutionResult] = None
81
+ ) -> Optional[ExecutionResult]:
82
+ """Check user permissions before query execution."""
83
+ # Skip if no access control or no user context
84
+ if not self.access_control_manager or not context.user_context:
85
+ self.logger.debug(
86
+ "Skipping permission check - no access control or user context"
87
+ )
88
+ return result
89
+
90
+ # Check execute permission
91
+ decision = self.access_control_manager.check_node_access(
92
+ context.user_context,
93
+ context.node_name,
94
+ NodePermission.EXECUTE,
95
+ context.runtime_context,
96
+ )
97
+
98
+ if not decision.allowed:
99
+ raise NodeExecutionError(f"Access denied: {decision.reason}")
100
+
101
+ self.logger.debug(
102
+ f"Permission granted for {context.node_name}: {decision.reason}"
103
+ )
104
+ return result
105
+
106
+ def get_stage_name(self) -> str:
107
+ """Get stage name."""
108
+ return "permission_check"
109
+
110
+
111
+ class QueryValidationStage(PipelineStage):
112
+ """Pipeline stage for validating SQL queries."""
113
+
114
+ def __init__(self, validation_rules: Optional[Dict[str, Any]] = None):
115
+ """Initialize query validation stage.
116
+
117
+ Args:
118
+ validation_rules: Custom validation rules
119
+ """
120
+ self.validation_rules = validation_rules or {}
121
+ self.logger = logging.getLogger(f"{__name__}.QueryValidationStage")
122
+
123
+ async def process(
124
+ self, context: ExecutionContext, result: Optional[ExecutionResult] = None
125
+ ) -> Optional[ExecutionResult]:
126
+ """Validate query for security and safety."""
127
+ if not context.query:
128
+ raise NodeExecutionError("Query cannot be empty")
129
+
130
+ # Basic SQL injection checks
131
+ self._validate_query_safety(context.query)
132
+
133
+ self.logger.debug(f"Query validation passed for: {context.query[:100]}...")
134
+ return result
135
+
136
+ def _validate_query_safety(self, query: str) -> None:
137
+ """Validate query for potential security issues."""
138
+ if not query:
139
+ return
140
+
141
+ query_upper = query.upper().strip()
142
+
143
+ # Check for dangerous operations
144
+ dangerous_keywords = [
145
+ "DROP",
146
+ "DELETE",
147
+ "TRUNCATE",
148
+ "ALTER",
149
+ "CREATE",
150
+ "GRANT",
151
+ "REVOKE",
152
+ "EXEC",
153
+ "EXECUTE",
154
+ "SHUTDOWN",
155
+ "BACKUP",
156
+ "RESTORE",
157
+ ]
158
+
159
+ import re
160
+
161
+ for keyword in dangerous_keywords:
162
+ pattern = r"\b" + re.escape(keyword) + r"\b"
163
+ if re.search(pattern, query_upper):
164
+ self.logger.warning(
165
+ f"Query contains potentially dangerous keyword: {keyword}"
166
+ )
167
+ # In production, you might want to block these entirely
168
+ # raise NodeExecutionError(f"Query contains forbidden keyword: {keyword}")
169
+
170
+ def get_stage_name(self) -> str:
171
+ """Get stage name."""
172
+ return "query_validation"
173
+
174
+
175
+ class QueryExecutionStage(PipelineStage):
176
+ """Pipeline stage for executing SQL queries."""
177
+
178
+ def __init__(self, query_executor):
179
+ """Initialize query execution stage.
180
+
181
+ Args:
182
+ query_executor: Object that can execute queries (engine, connection, etc.)
183
+ """
184
+ self.query_executor = query_executor
185
+ self.logger = logging.getLogger(f"{__name__}.QueryExecutionStage")
186
+
187
+ async def process(
188
+ self, context: ExecutionContext, result: Optional[ExecutionResult] = None
189
+ ) -> Optional[ExecutionResult]:
190
+ """Execute the SQL query."""
191
+ start_time = time.time()
192
+
193
+ try:
194
+ # This is where the actual query execution happens
195
+ # The implementation depends on whether it's sync or async
196
+ if hasattr(self.query_executor, "execute_query"):
197
+ # Custom executor interface
198
+ query_result = await self.query_executor.execute_query(
199
+ context.query, context.parameters, context.result_format
200
+ )
201
+ else:
202
+ # Fallback - assume it's a callable
203
+ query_result = await self.query_executor(
204
+ context.query, context.parameters
205
+ )
206
+
207
+ execution_time = time.time() - start_time
208
+
209
+ # Format the result
210
+ if isinstance(query_result, dict):
211
+ # Structured result
212
+ return ExecutionResult(
213
+ data=query_result.get("data", []),
214
+ row_count=query_result.get("row_count", 0),
215
+ columns=query_result.get("columns", []),
216
+ execution_time=execution_time,
217
+ metadata=query_result.get("metadata", {}),
218
+ )
219
+ else:
220
+ # Raw result - format it
221
+ return ExecutionResult(
222
+ data=query_result,
223
+ row_count=(
224
+ len(query_result) if isinstance(query_result, list) else 1
225
+ ),
226
+ columns=[],
227
+ execution_time=execution_time,
228
+ )
229
+
230
+ except Exception as e:
231
+ execution_time = time.time() - start_time
232
+ self.logger.error(
233
+ f"Query execution failed after {execution_time:.3f}s: {e}"
234
+ )
235
+ raise NodeExecutionError(f"Database query failed: {e}") from e
236
+
237
+ def get_stage_name(self) -> str:
238
+ """Get stage name."""
239
+ return "query_execution"
240
+
241
+
242
+ class DataMaskingStage(PipelineStage):
243
+ """Pipeline stage for applying data masking based on user attributes."""
244
+
245
+ def __init__(self, access_control_manager=None):
246
+ """Initialize data masking stage.
247
+
248
+ Args:
249
+ access_control_manager: Access control manager with masking capabilities
250
+ """
251
+ self.access_control_manager = access_control_manager
252
+ self.logger = logging.getLogger(f"{__name__}.DataMaskingStage")
253
+
254
+ async def process(
255
+ self, context: ExecutionContext, result: Optional[ExecutionResult] = None
256
+ ) -> Optional[ExecutionResult]:
257
+ """Apply data masking based on user attributes."""
258
+ if not result or not result.data:
259
+ return result
260
+
261
+ # Skip if no access control or no user context
262
+ if not self.access_control_manager or not context.user_context:
263
+ self.logger.debug(
264
+ "Skipping data masking - no access control or user context"
265
+ )
266
+ return result
267
+
268
+ # Skip if not dict format (masking only works on structured data)
269
+ if context.result_format != "dict" or not isinstance(result.data, list):
270
+ self.logger.debug("Skipping data masking - data format not supported")
271
+ return result
272
+
273
+ # Apply masking to each row
274
+ masked_data = []
275
+ for row in result.data:
276
+ if isinstance(row, dict):
277
+ # Apply masking if access control manager supports it
278
+ if hasattr(self.access_control_manager, "apply_data_masking"):
279
+ masked_row = self.access_control_manager.apply_data_masking(
280
+ context.user_context, context.node_name, row
281
+ )
282
+ masked_data.append(masked_row)
283
+ else:
284
+ masked_data.append(row)
285
+ else:
286
+ masked_data.append(row)
287
+
288
+ # Return result with masked data
289
+ return ExecutionResult(
290
+ data=masked_data,
291
+ row_count=result.row_count,
292
+ columns=result.columns,
293
+ execution_time=result.execution_time,
294
+ metadata=result.metadata,
295
+ )
296
+
297
+ def get_stage_name(self) -> str:
298
+ """Get stage name."""
299
+ return "data_masking"
300
+
301
+
302
+ class DatabaseExecutionPipeline:
303
+ """Pipeline for executing database operations with clean separation of concerns.
304
+
305
+ This pipeline provides:
306
+ - Permission checking
307
+ - Query validation
308
+ - Query execution
309
+ - Data masking
310
+
311
+ Example:
312
+ >>> pipeline = DatabaseExecutionPipeline(
313
+ ... access_control_manager=access_manager,
314
+ ... query_executor=my_executor
315
+ ... )
316
+ >>>
317
+ >>> context = ExecutionContext(
318
+ ... query="SELECT * FROM users",
319
+ ... user_context=user,
320
+ ... node_name="user_query"
321
+ ... )
322
+ >>>
323
+ >>> result = await pipeline.execute(context)
324
+ """
325
+
326
+ def __init__(
327
+ self,
328
+ access_control_manager=None,
329
+ query_executor=None,
330
+ validation_rules: Optional[Dict[str, Any]] = None,
331
+ custom_stages: Optional[List[PipelineStage]] = None,
332
+ ):
333
+ """Initialize database execution pipeline.
334
+
335
+ Args:
336
+ access_control_manager: Access control manager for permissions and masking
337
+ query_executor: Object that can execute database queries
338
+ validation_rules: Custom validation rules for queries
339
+ custom_stages: Additional custom pipeline stages
340
+ """
341
+ self.access_control_manager = access_control_manager
342
+ self.query_executor = query_executor
343
+ self.logger = logging.getLogger(f"{__name__}.DatabaseExecutionPipeline")
344
+
345
+ # Build pipeline stages
346
+ self.stages: List[PipelineStage] = []
347
+
348
+ # 1. Permission check
349
+ self.stages.append(PermissionCheckStage(access_control_manager))
350
+
351
+ # 2. Query validation
352
+ self.stages.append(QueryValidationStage(validation_rules))
353
+
354
+ # 3. Custom stages (before execution)
355
+ if custom_stages:
356
+ for stage in custom_stages:
357
+ if stage.get_stage_name() != "query_execution":
358
+ self.stages.append(stage)
359
+
360
+ # 4. Query execution
361
+ if query_executor:
362
+ self.stages.append(QueryExecutionStage(query_executor))
363
+
364
+ # 5. Data masking
365
+ self.stages.append(DataMaskingStage(access_control_manager))
366
+
367
+ # 6. Custom stages (after execution)
368
+ if custom_stages:
369
+ for stage in custom_stages:
370
+ if stage.get_stage_name() == "post_processing":
371
+ self.stages.append(stage)
372
+
373
+ self.logger.info(f"Initialized pipeline with {len(self.stages)} stages")
374
+
375
+ async def execute(self, context: ExecutionContext) -> ExecutionResult:
376
+ """Execute the full database pipeline.
377
+
378
+ Args:
379
+ context: Execution context with query, user, etc.
380
+
381
+ Returns:
382
+ Execution result with data, timing, etc.
383
+
384
+ Raises:
385
+ NodeExecutionError: If any stage fails
386
+ """
387
+ self.logger.debug(f"Starting pipeline execution for {context.node_name}")
388
+
389
+ result = None
390
+ pipeline_start = time.time()
391
+
392
+ try:
393
+ # Execute each stage in sequence
394
+ for i, stage in enumerate(self.stages):
395
+ stage_start = time.time()
396
+
397
+ try:
398
+ result = await stage.process(context, result)
399
+ stage_time = time.time() - stage_start
400
+
401
+ self.logger.debug(
402
+ f"Stage {i+1}/{len(self.stages)} ({stage.get_stage_name()}) "
403
+ f"completed in {stage_time:.3f}s"
404
+ )
405
+
406
+ # Allow stages to stop the pipeline
407
+ if result is None and stage.get_stage_name() != "permission_check":
408
+ self.logger.warning(
409
+ f"Pipeline stopped at stage: {stage.get_stage_name()}"
410
+ )
411
+ break
412
+
413
+ except Exception as e:
414
+ self.logger.error(
415
+ f"Pipeline failed at stage {stage.get_stage_name()}: {e}"
416
+ )
417
+ raise
418
+
419
+ pipeline_time = time.time() - pipeline_start
420
+ self.logger.info(f"Pipeline execution completed in {pipeline_time:.3f}s")
421
+
422
+ # Ensure we have a result
423
+ if result is None:
424
+ result = ExecutionResult(
425
+ data=[],
426
+ row_count=0,
427
+ columns=[],
428
+ execution_time=pipeline_time,
429
+ )
430
+
431
+ return result
432
+
433
+ except Exception as e:
434
+ pipeline_time = time.time() - pipeline_start
435
+ self.logger.error(
436
+ f"Pipeline execution failed after {pipeline_time:.3f}s: {e}"
437
+ )
438
+ raise
439
+
440
+ def add_stage(self, stage: PipelineStage, position: Optional[int] = None) -> None:
441
+ """Add a custom stage to the pipeline.
442
+
443
+ Args:
444
+ stage: Pipeline stage to add
445
+ position: Position to insert at (None = append)
446
+ """
447
+ if position is None:
448
+ self.stages.append(stage)
449
+ else:
450
+ self.stages.insert(position, stage)
451
+
452
+ self.logger.info(
453
+ f"Added stage {stage.get_stage_name()} at position {position or len(self.stages)}"
454
+ )
455
+
456
+ def remove_stage(self, stage_name: str) -> bool:
457
+ """Remove a stage from the pipeline.
458
+
459
+ Args:
460
+ stage_name: Name of stage to remove
461
+
462
+ Returns:
463
+ True if stage was found and removed
464
+ """
465
+ initial_count = len(self.stages)
466
+ self.stages = [s for s in self.stages if s.get_stage_name() != stage_name]
467
+ removed = len(self.stages) < initial_count
468
+
469
+ if removed:
470
+ self.logger.info(f"Removed stage {stage_name}")
471
+
472
+ return removed
473
+
474
+ def get_stage_info(self) -> List[Dict[str, str]]:
475
+ """Get information about all pipeline stages.
476
+
477
+ Returns:
478
+ List of stage information dictionaries
479
+ """
480
+ return [
481
+ {
482
+ "name": stage.get_stage_name(),
483
+ "type": type(stage).__name__,
484
+ }
485
+ for stage in self.stages
486
+ ]
487
+
488
+
489
+ # Export components
490
+ __all__ = [
491
+ "ExecutionContext",
492
+ "ExecutionResult",
493
+ "PipelineStage",
494
+ "PermissionCheckStage",
495
+ "QueryValidationStage",
496
+ "QueryExecutionStage",
497
+ "DataMaskingStage",
498
+ "DatabaseExecutionPipeline",
499
+ ]