kailash 0.3.2__py3-none-any.whl → 0.4.1__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 +27 -3
- kailash/nodes/admin/__init__.py +42 -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 +1523 -0
- kailash/nodes/admin/user_management.py +944 -0
- kailash/nodes/ai/a2a.py +24 -7
- kailash/nodes/ai/ai_providers.py +248 -40
- kailash/nodes/ai/embedding_generator.py +11 -11
- kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
- kailash/nodes/ai/llm_agent.py +436 -5
- kailash/nodes/ai/self_organizing.py +85 -10
- kailash/nodes/ai/vision_utils.py +148 -0
- kailash/nodes/alerts/__init__.py +26 -0
- kailash/nodes/alerts/base.py +234 -0
- kailash/nodes/alerts/discord.py +499 -0
- 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 +283 -10
- 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 +103 -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 +133 -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/security.py +1 -1
- 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.2.dist-info → kailash-0.4.1.dist-info}/METADATA +256 -20
- kailash-0.4.1.dist-info/RECORD +227 -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.2.dist-info/RECORD +0 -136
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/WHEEL +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/entry_points.txt +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
"""Vision utilities for AI providers - lazy loaded to avoid overhead."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Optional, Tuple
|
5
|
+
|
6
|
+
|
7
|
+
def encode_image(image_path: str) -> str:
|
8
|
+
"""
|
9
|
+
Encode image file to base64 string.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
image_path: Path to the image file
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
Base64 encoded string of the image
|
16
|
+
|
17
|
+
Raises:
|
18
|
+
FileNotFoundError: If image file doesn't exist
|
19
|
+
IOError: If unable to read the image file
|
20
|
+
"""
|
21
|
+
# Lazy import to avoid overhead when not using vision
|
22
|
+
import base64
|
23
|
+
|
24
|
+
image_path = Path(image_path).resolve()
|
25
|
+
if not image_path.exists():
|
26
|
+
raise FileNotFoundError(f"Image file not found: {image_path}")
|
27
|
+
|
28
|
+
try:
|
29
|
+
with open(image_path, "rb") as image_file:
|
30
|
+
return base64.b64encode(image_file.read()).decode("utf-8")
|
31
|
+
except Exception as e:
|
32
|
+
raise IOError(f"Failed to read image file: {e}")
|
33
|
+
|
34
|
+
|
35
|
+
def get_media_type(image_path: str) -> str:
|
36
|
+
"""
|
37
|
+
Get media type from file extension.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
image_path: Path to the image file
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
Media type string (e.g., "image/jpeg")
|
44
|
+
"""
|
45
|
+
ext = Path(image_path).suffix.lower()
|
46
|
+
media_types = {
|
47
|
+
".jpg": "image/jpeg",
|
48
|
+
".jpeg": "image/jpeg",
|
49
|
+
".png": "image/png",
|
50
|
+
".gif": "image/gif",
|
51
|
+
".webp": "image/webp",
|
52
|
+
".bmp": "image/bmp",
|
53
|
+
".tiff": "image/tiff",
|
54
|
+
".tif": "image/tiff",
|
55
|
+
}
|
56
|
+
return media_types.get(ext, "image/jpeg")
|
57
|
+
|
58
|
+
|
59
|
+
def validate_image_size(
|
60
|
+
image_path: str, max_size_mb: float = 20.0
|
61
|
+
) -> Tuple[bool, Optional[str]]:
|
62
|
+
"""
|
63
|
+
Validate image file size.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
image_path: Path to the image file
|
67
|
+
max_size_mb: Maximum allowed size in megabytes
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
Tuple of (is_valid, error_message)
|
71
|
+
"""
|
72
|
+
import os
|
73
|
+
|
74
|
+
try:
|
75
|
+
size_bytes = os.path.getsize(image_path)
|
76
|
+
size_mb = size_bytes / (1024 * 1024)
|
77
|
+
|
78
|
+
if size_mb > max_size_mb:
|
79
|
+
return False, f"Image size {size_mb:.1f}MB exceeds maximum {max_size_mb}MB"
|
80
|
+
|
81
|
+
return True, None
|
82
|
+
except Exception as e:
|
83
|
+
return False, f"Failed to check image size: {e}"
|
84
|
+
|
85
|
+
|
86
|
+
def resize_image_if_needed(
|
87
|
+
image_path: str, max_size_mb: float = 20.0, max_dimension: int = 4096
|
88
|
+
) -> Optional[str]:
|
89
|
+
"""
|
90
|
+
Resize image if it exceeds size or dimension limits.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
image_path: Path to the image file
|
94
|
+
max_size_mb: Maximum file size in MB
|
95
|
+
max_dimension: Maximum width or height in pixels
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
Base64 encoded resized image, or None if no resize needed
|
99
|
+
"""
|
100
|
+
try:
|
101
|
+
# Lazy import to avoid PIL dependency when not using vision
|
102
|
+
import base64
|
103
|
+
import io
|
104
|
+
|
105
|
+
from PIL import Image
|
106
|
+
|
107
|
+
# Check if resize is needed
|
108
|
+
is_valid, _ = validate_image_size(image_path, max_size_mb)
|
109
|
+
|
110
|
+
with Image.open(image_path) as img:
|
111
|
+
# Check dimensions
|
112
|
+
needs_resize = (
|
113
|
+
not is_valid or img.width > max_dimension or img.height > max_dimension
|
114
|
+
)
|
115
|
+
|
116
|
+
if not needs_resize:
|
117
|
+
return None
|
118
|
+
|
119
|
+
# Calculate new size maintaining aspect ratio
|
120
|
+
ratio = min(max_dimension / img.width, max_dimension / img.height, 1.0)
|
121
|
+
new_size = (int(img.width * ratio), int(img.height * ratio))
|
122
|
+
|
123
|
+
# Resize image
|
124
|
+
img = img.resize(new_size, Image.Resampling.LANCZOS)
|
125
|
+
|
126
|
+
# Convert to RGB if necessary (for JPEG)
|
127
|
+
if img.mode not in ("RGB", "L"):
|
128
|
+
img = img.convert("RGB")
|
129
|
+
|
130
|
+
# Save to bytes
|
131
|
+
output = io.BytesIO()
|
132
|
+
img_format = (
|
133
|
+
"JPEG"
|
134
|
+
if Path(image_path).suffix.lower() in [".jpg", ".jpeg"]
|
135
|
+
else "PNG"
|
136
|
+
)
|
137
|
+
img.save(output, format=img_format, optimize=True, quality=85)
|
138
|
+
|
139
|
+
# Encode to base64
|
140
|
+
output.seek(0)
|
141
|
+
return base64.b64encode(output.read()).decode("utf-8")
|
142
|
+
|
143
|
+
except ImportError:
|
144
|
+
# PIL not available, skip resizing
|
145
|
+
return None
|
146
|
+
except Exception:
|
147
|
+
# Any error in resizing, return None to use original
|
148
|
+
return None
|
@@ -0,0 +1,26 @@
|
|
1
|
+
"""Alert and notification nodes for the Kailash SDK.
|
2
|
+
|
3
|
+
This module provides specialized nodes for sending alerts and notifications
|
4
|
+
through various channels. Each alert node follows a consistent interface while
|
5
|
+
providing channel-specific features and optimizations.
|
6
|
+
|
7
|
+
The module includes:
|
8
|
+
- Base alert node infrastructure
|
9
|
+
- Discord webhook integration
|
10
|
+
- (Future) Slack, email, webhook, and other integrations
|
11
|
+
|
12
|
+
Design Philosophy:
|
13
|
+
- Provide purpose-built nodes for common alert patterns
|
14
|
+
- Abstract channel-specific complexity
|
15
|
+
- Support both simple and advanced use cases
|
16
|
+
- Enable consistent alert formatting across channels
|
17
|
+
"""
|
18
|
+
|
19
|
+
from .base import AlertNode, AlertSeverity
|
20
|
+
from .discord import DiscordAlertNode
|
21
|
+
|
22
|
+
__all__ = [
|
23
|
+
"AlertNode",
|
24
|
+
"AlertSeverity",
|
25
|
+
"DiscordAlertNode",
|
26
|
+
]
|
@@ -0,0 +1,234 @@
|
|
1
|
+
"""Base alert node class for the Kailash SDK.
|
2
|
+
|
3
|
+
This module provides the foundation for all alert and notification nodes in the system.
|
4
|
+
It defines common parameters, severity levels, and formatting utilities that are shared
|
5
|
+
across different alert implementations.
|
6
|
+
|
7
|
+
The alert system is designed to provide:
|
8
|
+
- Consistent interface across different notification channels
|
9
|
+
- Severity-based formatting and colors
|
10
|
+
- Structured context data support
|
11
|
+
- Easy extensibility for new alert types
|
12
|
+
"""
|
13
|
+
|
14
|
+
from abc import abstractmethod
|
15
|
+
from enum import Enum
|
16
|
+
from typing import Any
|
17
|
+
|
18
|
+
from kailash.nodes.base import Node, NodeParameter
|
19
|
+
|
20
|
+
|
21
|
+
class AlertSeverity(str, Enum):
|
22
|
+
"""Standard alert severity levels with associated colors."""
|
23
|
+
|
24
|
+
SUCCESS = "success"
|
25
|
+
WARNING = "warning"
|
26
|
+
ERROR = "error"
|
27
|
+
CRITICAL = "critical"
|
28
|
+
INFO = "info"
|
29
|
+
|
30
|
+
def get_color(self) -> int:
|
31
|
+
"""Get the color code for this severity level (Discord/Slack compatible)."""
|
32
|
+
colors = {
|
33
|
+
AlertSeverity.SUCCESS: 0x28A745, # Green
|
34
|
+
AlertSeverity.WARNING: 0xFFC107, # Yellow/Amber
|
35
|
+
AlertSeverity.ERROR: 0xDC3545, # Red
|
36
|
+
AlertSeverity.CRITICAL: 0x8B0000, # Dark Red
|
37
|
+
AlertSeverity.INFO: 0x007BFF, # Blue
|
38
|
+
}
|
39
|
+
return colors.get(self, 0x808080) # Default to gray
|
40
|
+
|
41
|
+
|
42
|
+
class AlertNode(Node):
|
43
|
+
"""
|
44
|
+
Base class for all alert and notification nodes in the Kailash SDK.
|
45
|
+
|
46
|
+
This abstract base class provides common functionality for sending alerts
|
47
|
+
through various channels (Discord, Slack, email, webhooks, etc.). It defines
|
48
|
+
standard parameters that all alert nodes should support and provides utilities
|
49
|
+
for formatting messages consistently.
|
50
|
+
|
51
|
+
Design Philosophy:
|
52
|
+
Alert nodes should provide a simple, consistent interface for sending
|
53
|
+
notifications while allowing channel-specific features when needed.
|
54
|
+
The base class handles common concerns like severity levels, titles,
|
55
|
+
and context data, while subclasses implement channel-specific logic.
|
56
|
+
|
57
|
+
Common Parameters:
|
58
|
+
- alert_type: Severity level (success, warning, error, critical, info)
|
59
|
+
- title: Alert title/subject
|
60
|
+
- message: Main alert message body
|
61
|
+
- context: Additional structured data
|
62
|
+
|
63
|
+
Subclasses must implement:
|
64
|
+
- get_channel_parameters(): Define channel-specific parameters
|
65
|
+
- send_alert(): Implement the actual alert sending logic
|
66
|
+
"""
|
67
|
+
|
68
|
+
category = "alerts"
|
69
|
+
|
70
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
71
|
+
"""Define common parameters for all alert nodes.
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
Dictionary of common alert parameters merged with channel-specific ones
|
75
|
+
"""
|
76
|
+
common_params = {
|
77
|
+
"alert_type": NodeParameter(
|
78
|
+
name="alert_type",
|
79
|
+
type=str,
|
80
|
+
required=False,
|
81
|
+
default="info",
|
82
|
+
description="Alert severity level: success, warning, error, critical, info",
|
83
|
+
),
|
84
|
+
"title": NodeParameter(
|
85
|
+
name="title",
|
86
|
+
type=str,
|
87
|
+
required=True,
|
88
|
+
description="Alert title or subject",
|
89
|
+
),
|
90
|
+
"message": NodeParameter(
|
91
|
+
name="message",
|
92
|
+
type=str,
|
93
|
+
required=False,
|
94
|
+
default="",
|
95
|
+
description="Main alert message body",
|
96
|
+
),
|
97
|
+
"context": NodeParameter(
|
98
|
+
name="context",
|
99
|
+
type=dict,
|
100
|
+
required=False,
|
101
|
+
default={},
|
102
|
+
description="Additional context data to include in the alert",
|
103
|
+
),
|
104
|
+
}
|
105
|
+
|
106
|
+
# Merge with channel-specific parameters
|
107
|
+
channel_params = self.get_channel_parameters()
|
108
|
+
return {**common_params, **channel_params}
|
109
|
+
|
110
|
+
@abstractmethod
|
111
|
+
def get_channel_parameters(self) -> dict[str, NodeParameter]:
|
112
|
+
"""Define channel-specific parameters.
|
113
|
+
|
114
|
+
Subclasses must implement this to add their specific parameters
|
115
|
+
like webhook URLs, authentication tokens, formatting options, etc.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
Dictionary of channel-specific parameters
|
119
|
+
"""
|
120
|
+
pass
|
121
|
+
|
122
|
+
def validate_alert_type(self, alert_type: str) -> AlertSeverity:
|
123
|
+
"""Validate and normalize the alert type.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
alert_type: String representation of alert severity
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
Normalized AlertSeverity enum value
|
130
|
+
|
131
|
+
Raises:
|
132
|
+
ValueError: If alert_type is not valid
|
133
|
+
"""
|
134
|
+
try:
|
135
|
+
return AlertSeverity(alert_type.lower())
|
136
|
+
except ValueError:
|
137
|
+
valid_types = [s.value for s in AlertSeverity]
|
138
|
+
raise ValueError(
|
139
|
+
f"Invalid alert_type '{alert_type}'. Must be one of: {', '.join(valid_types)}"
|
140
|
+
)
|
141
|
+
|
142
|
+
def format_context(self, context: dict[str, Any]) -> str:
|
143
|
+
"""Format context dictionary for display.
|
144
|
+
|
145
|
+
Args:
|
146
|
+
context: Dictionary of context data
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
Formatted string representation of context
|
150
|
+
"""
|
151
|
+
if not context:
|
152
|
+
return ""
|
153
|
+
|
154
|
+
lines = []
|
155
|
+
for key, value in context.items():
|
156
|
+
# Handle nested dictionaries and lists
|
157
|
+
if isinstance(value, (dict, list)):
|
158
|
+
import json
|
159
|
+
|
160
|
+
value_str = json.dumps(value) # No indent for single-line format
|
161
|
+
else:
|
162
|
+
value_str = str(value)
|
163
|
+
|
164
|
+
lines.append(f"**{key}**: {value_str}")
|
165
|
+
|
166
|
+
return "\n".join(lines)
|
167
|
+
|
168
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
169
|
+
"""Execute the alert node.
|
170
|
+
|
171
|
+
This method validates common parameters, normalizes the alert type,
|
172
|
+
and delegates to the subclass's send_alert method.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
**kwargs: Alert parameters including common and channel-specific ones
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
Dictionary with alert execution results
|
179
|
+
"""
|
180
|
+
# Validate and normalize alert type
|
181
|
+
alert_type_str = kwargs.get("alert_type", "info")
|
182
|
+
alert_severity = self.validate_alert_type(alert_type_str)
|
183
|
+
|
184
|
+
# Extract common parameters
|
185
|
+
title = kwargs["title"]
|
186
|
+
message = kwargs.get("message", "")
|
187
|
+
context = kwargs.get("context", {})
|
188
|
+
|
189
|
+
# Extract channel-specific parameters only
|
190
|
+
channel_params = {
|
191
|
+
k: v
|
192
|
+
for k, v in kwargs.items()
|
193
|
+
if k not in ["alert_type", "title", "message", "context"]
|
194
|
+
}
|
195
|
+
|
196
|
+
# Call subclass implementation
|
197
|
+
result = self.send_alert(
|
198
|
+
severity=alert_severity,
|
199
|
+
title=title,
|
200
|
+
message=message,
|
201
|
+
context=context,
|
202
|
+
**channel_params, # Pass only channel-specific params
|
203
|
+
)
|
204
|
+
|
205
|
+
# Add standard metadata to result
|
206
|
+
result["alert_type"] = alert_severity.value
|
207
|
+
result["title"] = title
|
208
|
+
|
209
|
+
return result
|
210
|
+
|
211
|
+
@abstractmethod
|
212
|
+
def send_alert(
|
213
|
+
self,
|
214
|
+
severity: AlertSeverity,
|
215
|
+
title: str,
|
216
|
+
message: str,
|
217
|
+
context: dict[str, Any],
|
218
|
+
**kwargs,
|
219
|
+
) -> dict[str, Any]:
|
220
|
+
"""Send the alert through the specific channel.
|
221
|
+
|
222
|
+
Subclasses must implement this method to handle the actual alert delivery.
|
223
|
+
|
224
|
+
Args:
|
225
|
+
severity: Normalized alert severity
|
226
|
+
title: Alert title
|
227
|
+
message: Alert message body
|
228
|
+
context: Additional context data
|
229
|
+
**kwargs: Channel-specific parameters
|
230
|
+
|
231
|
+
Returns:
|
232
|
+
Dictionary with channel-specific response data
|
233
|
+
"""
|
234
|
+
pass
|