adamops 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 (42) hide show
  1. adamops/__init__.py +40 -0
  2. adamops/cli.py +163 -0
  3. adamops/data/__init__.py +24 -0
  4. adamops/data/feature_engineering.py +284 -0
  5. adamops/data/loaders.py +922 -0
  6. adamops/data/preprocessors.py +227 -0
  7. adamops/data/splitters.py +218 -0
  8. adamops/data/validators.py +148 -0
  9. adamops/deployment/__init__.py +21 -0
  10. adamops/deployment/api.py +237 -0
  11. adamops/deployment/cloud.py +191 -0
  12. adamops/deployment/containerize.py +262 -0
  13. adamops/deployment/exporters.py +148 -0
  14. adamops/evaluation/__init__.py +24 -0
  15. adamops/evaluation/comparison.py +133 -0
  16. adamops/evaluation/explainability.py +143 -0
  17. adamops/evaluation/metrics.py +233 -0
  18. adamops/evaluation/reports.py +165 -0
  19. adamops/evaluation/visualization.py +238 -0
  20. adamops/models/__init__.py +21 -0
  21. adamops/models/automl.py +277 -0
  22. adamops/models/ensembles.py +228 -0
  23. adamops/models/modelops.py +308 -0
  24. adamops/models/registry.py +250 -0
  25. adamops/monitoring/__init__.py +21 -0
  26. adamops/monitoring/alerts.py +200 -0
  27. adamops/monitoring/dashboard.py +117 -0
  28. adamops/monitoring/drift.py +212 -0
  29. adamops/monitoring/performance.py +195 -0
  30. adamops/pipelines/__init__.py +15 -0
  31. adamops/pipelines/orchestrators.py +183 -0
  32. adamops/pipelines/workflows.py +212 -0
  33. adamops/utils/__init__.py +18 -0
  34. adamops/utils/config.py +457 -0
  35. adamops/utils/helpers.py +663 -0
  36. adamops/utils/logging.py +412 -0
  37. adamops-0.1.0.dist-info/METADATA +310 -0
  38. adamops-0.1.0.dist-info/RECORD +42 -0
  39. adamops-0.1.0.dist-info/WHEEL +5 -0
  40. adamops-0.1.0.dist-info/entry_points.txt +2 -0
  41. adamops-0.1.0.dist-info/licenses/LICENSE +21 -0
  42. adamops-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,250 @@
1
+ """
2
+ AdamOps Model Registry Module
3
+
4
+ Provides model versioning, metadata tracking, and comparison.
5
+ """
6
+
7
+ import json
8
+ import sqlite3
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List, Optional
12
+ import joblib
13
+
14
+ from adamops.utils.logging import get_logger
15
+ from adamops.utils.config import get_config
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ class ModelVersion:
21
+ """Represents a model version."""
22
+
23
+ def __init__(self, name: str, version: str, path: str, metadata: Dict):
24
+ self.name = name
25
+ self.version = version
26
+ self.path = path
27
+ self.metadata = metadata
28
+ self.created_at = metadata.get("created_at", datetime.now().isoformat())
29
+
30
+ def to_dict(self) -> Dict:
31
+ return {
32
+ "name": self.name, "version": self.version, "path": self.path,
33
+ "metadata": self.metadata, "created_at": self.created_at
34
+ }
35
+
36
+ @classmethod
37
+ def from_dict(cls, data: Dict) -> "ModelVersion":
38
+ return cls(data["name"], data["version"], data["path"], data["metadata"])
39
+
40
+
41
+ class ModelRegistry:
42
+ """Model registry for versioning and tracking."""
43
+
44
+ def __init__(self, path: Optional[str] = None, backend: str = "json"):
45
+ """
46
+ Initialize registry.
47
+
48
+ Args:
49
+ path: Registry storage path.
50
+ backend: 'json' or 'sqlite'.
51
+ """
52
+ config = get_config()
53
+ self.path = Path(path or config.registry_path)
54
+ self.backend = backend
55
+ self.path.mkdir(parents=True, exist_ok=True)
56
+
57
+ if backend == "sqlite":
58
+ self._init_sqlite()
59
+ else:
60
+ self._init_json()
61
+
62
+ def _init_json(self):
63
+ self.registry_file = self.path / "registry.json"
64
+ if not self.registry_file.exists():
65
+ self._save_json({})
66
+
67
+ def _init_sqlite(self):
68
+ self.db_file = self.path / "registry.db"
69
+ conn = sqlite3.connect(self.db_file)
70
+ conn.execute("""
71
+ CREATE TABLE IF NOT EXISTS models (
72
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
73
+ name TEXT, version TEXT, path TEXT,
74
+ metadata TEXT, created_at TEXT,
75
+ UNIQUE(name, version)
76
+ )
77
+ """)
78
+ conn.commit()
79
+ conn.close()
80
+
81
+ def _load_json(self) -> Dict:
82
+ with open(self.registry_file) as f:
83
+ return json.load(f)
84
+
85
+ def _save_json(self, data: Dict):
86
+ with open(self.registry_file, "w") as f:
87
+ json.dump(data, f, indent=2)
88
+
89
+ def register(self, name: str, model: Any, metadata: Optional[Dict] = None,
90
+ version: Optional[str] = None) -> ModelVersion:
91
+ """
92
+ Register a model.
93
+
94
+ Args:
95
+ name: Model name.
96
+ model: Model object to save.
97
+ metadata: Model metadata (algorithm, metrics, etc).
98
+ version: Version string (auto-incremented if None).
99
+
100
+ Returns:
101
+ ModelVersion: Registered model version.
102
+ """
103
+ # Get next version
104
+ if version is None:
105
+ existing = self.list_versions(name)
106
+ version = f"v{len(existing) + 1}"
107
+
108
+ # Save model
109
+ model_path = self.path / "models" / name / f"{version}.joblib"
110
+ model_path.parent.mkdir(parents=True, exist_ok=True)
111
+ joblib.dump(model, model_path)
112
+
113
+ # Create version record
114
+ meta = metadata or {}
115
+ meta["created_at"] = datetime.now().isoformat()
116
+ model_version = ModelVersion(name, version, str(model_path), meta)
117
+
118
+ # Store in registry
119
+ if self.backend == "json":
120
+ data = self._load_json()
121
+ if name not in data:
122
+ data[name] = {}
123
+ data[name][version] = model_version.to_dict()
124
+ self._save_json(data)
125
+ else:
126
+ conn = sqlite3.connect(self.db_file)
127
+ conn.execute(
128
+ "INSERT OR REPLACE INTO models (name, version, path, metadata, created_at) VALUES (?, ?, ?, ?, ?)",
129
+ (name, version, str(model_path), json.dumps(meta), meta["created_at"])
130
+ )
131
+ conn.commit()
132
+ conn.close()
133
+
134
+ logger.info(f"Registered model {name} version {version}")
135
+ return model_version
136
+
137
+ def get(self, name: str, version: str = "latest") -> Optional[ModelVersion]:
138
+ """Get a model version."""
139
+ if version == "latest":
140
+ versions = self.list_versions(name)
141
+ if not versions:
142
+ return None
143
+ version = versions[-1].version
144
+
145
+ if self.backend == "json":
146
+ data = self._load_json()
147
+ if name in data and version in data[name]:
148
+ return ModelVersion.from_dict(data[name][version])
149
+ else:
150
+ conn = sqlite3.connect(self.db_file)
151
+ cur = conn.execute(
152
+ "SELECT name, version, path, metadata FROM models WHERE name=? AND version=?",
153
+ (name, version)
154
+ )
155
+ row = cur.fetchone()
156
+ conn.close()
157
+ if row:
158
+ return ModelVersion(row[0], row[1], row[2], json.loads(row[3]))
159
+
160
+ return None
161
+
162
+ def load(self, name: str, version: str = "latest") -> Any:
163
+ """Load a model."""
164
+ model_version = self.get(name, version)
165
+ if model_version is None:
166
+ raise ValueError(f"Model {name} version {version} not found")
167
+ return joblib.load(model_version.path)
168
+
169
+ def list_versions(self, name: str) -> List[ModelVersion]:
170
+ """List all versions of a model."""
171
+ if self.backend == "json":
172
+ data = self._load_json()
173
+ if name not in data:
174
+ return []
175
+ return [ModelVersion.from_dict(v) for v in data[name].values()]
176
+ else:
177
+ conn = sqlite3.connect(self.db_file)
178
+ cur = conn.execute(
179
+ "SELECT name, version, path, metadata FROM models WHERE name=? ORDER BY created_at",
180
+ (name,)
181
+ )
182
+ versions = [ModelVersion(r[0], r[1], r[2], json.loads(r[3])) for r in cur.fetchall()]
183
+ conn.close()
184
+ return versions
185
+
186
+ def list_models(self) -> List[str]:
187
+ """List all registered model names."""
188
+ if self.backend == "json":
189
+ return list(self._load_json().keys())
190
+ else:
191
+ conn = sqlite3.connect(self.db_file)
192
+ cur = conn.execute("SELECT DISTINCT name FROM models")
193
+ names = [r[0] for r in cur.fetchall()]
194
+ conn.close()
195
+ return names
196
+
197
+ def compare(self, name: str, metric: str = "accuracy") -> Dict:
198
+ """Compare all versions of a model by a metric."""
199
+ versions = self.list_versions(name)
200
+ comparison = []
201
+ for v in versions:
202
+ metrics = v.metadata.get("metrics", {})
203
+ comparison.append({
204
+ "version": v.version,
205
+ "created_at": v.created_at,
206
+ metric: metrics.get(metric),
207
+ **{k: val for k, val in metrics.items() if k != metric}
208
+ })
209
+ return sorted(comparison, key=lambda x: x.get(metric, 0) or 0, reverse=True)
210
+
211
+ def delete(self, name: str, version: Optional[str] = None):
212
+ """Delete a model or version."""
213
+ if version:
214
+ model_version = self.get(name, version)
215
+ if model_version and Path(model_version.path).exists():
216
+ Path(model_version.path).unlink()
217
+
218
+ if self.backend == "json":
219
+ data = self._load_json()
220
+ if name in data and version in data[name]:
221
+ del data[name][version]
222
+ self._save_json(data)
223
+ else:
224
+ conn = sqlite3.connect(self.db_file)
225
+ conn.execute("DELETE FROM models WHERE name=? AND version=?", (name, version))
226
+ conn.commit()
227
+ conn.close()
228
+ else:
229
+ # Delete all versions
230
+ for v in self.list_versions(name):
231
+ self.delete(name, v.version)
232
+
233
+
234
+ # Global registry instance
235
+ _registry: Optional[ModelRegistry] = None
236
+
237
+ def get_registry() -> ModelRegistry:
238
+ """Get global registry instance."""
239
+ global _registry
240
+ if _registry is None:
241
+ _registry = ModelRegistry()
242
+ return _registry
243
+
244
+ def register_model(name: str, model: Any, **kwargs) -> ModelVersion:
245
+ """Register a model in the global registry."""
246
+ return get_registry().register(name, model, **kwargs)
247
+
248
+ def load_model(name: str, version: str = "latest") -> Any:
249
+ """Load a model from the global registry."""
250
+ return get_registry().load(name, version)
@@ -0,0 +1,21 @@
1
+ """
2
+ AdamOps Monitoring Module
3
+
4
+ Provides model monitoring capabilities:
5
+ - drift: Detect data and concept drift
6
+ - performance: Track model performance metrics
7
+ - alerts: Set up alerting for performance degradation
8
+ - dashboard: Create monitoring dashboards
9
+ """
10
+
11
+ from adamops.monitoring import drift
12
+ from adamops.monitoring import performance
13
+ from adamops.monitoring import alerts
14
+ from adamops.monitoring import dashboard
15
+
16
+ __all__ = [
17
+ "drift",
18
+ "performance",
19
+ "alerts",
20
+ "dashboard",
21
+ ]
@@ -0,0 +1,200 @@
1
+ """
2
+ AdamOps Alerts Module
3
+
4
+ Alerting for model performance degradation.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from typing import Any, Callable, Dict, List, Optional
9
+ from enum import Enum
10
+
11
+ from adamops.utils.logging import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
15
+
16
+ class AlertSeverity(Enum):
17
+ INFO = "info"
18
+ WARNING = "warning"
19
+ CRITICAL = "critical"
20
+
21
+
22
+ class Alert:
23
+ """Represents an alert."""
24
+
25
+ def __init__(self, name: str, message: str, severity: AlertSeverity,
26
+ metadata: Optional[Dict] = None):
27
+ self.name = name
28
+ self.message = message
29
+ self.severity = severity
30
+ self.metadata = metadata or {}
31
+ self.timestamp = datetime.now().isoformat()
32
+
33
+ def to_dict(self) -> Dict:
34
+ return {
35
+ "name": self.name,
36
+ "message": self.message,
37
+ "severity": self.severity.value,
38
+ "timestamp": self.timestamp,
39
+ "metadata": self.metadata,
40
+ }
41
+
42
+
43
+ class AlertRule:
44
+ """Defines an alert rule."""
45
+
46
+ def __init__(self, name: str, condition: Callable[[Dict], bool],
47
+ message_template: str, severity: AlertSeverity = AlertSeverity.WARNING):
48
+ self.name = name
49
+ self.condition = condition
50
+ self.message_template = message_template
51
+ self.severity = severity
52
+
53
+ def check(self, data: Dict) -> Optional[Alert]:
54
+ """Check if alert should be triggered."""
55
+ if self.condition(data):
56
+ message = self.message_template.format(**data)
57
+ return Alert(self.name, message, self.severity, data)
58
+ return None
59
+
60
+
61
+ class AlertManager:
62
+ """Manage alerts and notifications."""
63
+
64
+ def __init__(self):
65
+ self.rules: List[AlertRule] = []
66
+ self.handlers: List[Callable[[Alert], None]] = []
67
+ self.alert_history: List[Alert] = []
68
+
69
+ def add_rule(self, rule: AlertRule):
70
+ """Add an alert rule."""
71
+ self.rules.append(rule)
72
+ logger.info(f"Added alert rule: {rule.name}")
73
+
74
+ def add_handler(self, handler: Callable[[Alert], None]):
75
+ """Add an alert handler (callback)."""
76
+ self.handlers.append(handler)
77
+
78
+ def check(self, data: Dict) -> List[Alert]:
79
+ """Check all rules against data."""
80
+ triggered = []
81
+
82
+ for rule in self.rules:
83
+ alert = rule.check(data)
84
+ if alert:
85
+ triggered.append(alert)
86
+ self._handle_alert(alert)
87
+
88
+ return triggered
89
+
90
+ def _handle_alert(self, alert: Alert):
91
+ """Handle triggered alert."""
92
+ self.alert_history.append(alert)
93
+
94
+ # Log alert
95
+ log_method = logger.warning if alert.severity == AlertSeverity.WARNING else \
96
+ logger.critical if alert.severity == AlertSeverity.CRITICAL else logger.info
97
+ log_method(f"[{alert.severity.value.upper()}] {alert.name}: {alert.message}")
98
+
99
+ # Call handlers
100
+ for handler in self.handlers:
101
+ try:
102
+ handler(alert)
103
+ except Exception as e:
104
+ logger.error(f"Alert handler error: {e}")
105
+
106
+ def get_history(self, severity: Optional[AlertSeverity] = None) -> List[Alert]:
107
+ """Get alert history."""
108
+ if severity:
109
+ return [a for a in self.alert_history if a.severity == severity]
110
+ return self.alert_history
111
+
112
+
113
+ # Default alert rules
114
+ def accuracy_drop_rule(threshold: float = 0.1) -> AlertRule:
115
+ """Alert rule for accuracy drop."""
116
+ return AlertRule(
117
+ name="accuracy_drop",
118
+ condition=lambda d: d.get("accuracy_change", 0) < -threshold,
119
+ message_template="Accuracy dropped by {accuracy_change:.1%}",
120
+ severity=AlertSeverity.WARNING,
121
+ )
122
+
123
+
124
+ def drift_detected_rule() -> AlertRule:
125
+ """Alert rule for drift detection."""
126
+ return AlertRule(
127
+ name="drift_detected",
128
+ condition=lambda d: d.get("drift_detected", False),
129
+ message_template="Data drift detected in {drifted_columns} columns",
130
+ severity=AlertSeverity.WARNING,
131
+ )
132
+
133
+
134
+ def high_latency_rule(threshold_ms: float = 1000) -> AlertRule:
135
+ """Alert rule for high latency."""
136
+ return AlertRule(
137
+ name="high_latency",
138
+ condition=lambda d: d.get("latency_ms", 0) > threshold_ms,
139
+ message_template="High latency detected: {latency_ms:.0f}ms",
140
+ severity=AlertSeverity.WARNING,
141
+ )
142
+
143
+
144
+ # Notification handlers
145
+ def console_handler(alert: Alert):
146
+ """Print alert to console."""
147
+ print(f"[ALERT] {alert.severity.value.upper()}: {alert.message}")
148
+
149
+
150
+ def email_handler(recipients: List[str], smtp_config: Dict):
151
+ """Create email alert handler."""
152
+ def handler(alert: Alert):
153
+ try:
154
+ import smtplib
155
+ from email.message import EmailMessage
156
+
157
+ msg = EmailMessage()
158
+ msg['Subject'] = f"[{alert.severity.value.upper()}] {alert.name}"
159
+ msg['From'] = smtp_config.get('from', 'alerts@adamops.local')
160
+ msg['To'] = ', '.join(recipients)
161
+ msg.set_content(f"{alert.message}\n\nTimestamp: {alert.timestamp}")
162
+
163
+ with smtplib.SMTP(smtp_config['host'], smtp_config.get('port', 587)) as s:
164
+ if smtp_config.get('use_tls'):
165
+ s.starttls()
166
+ if 'username' in smtp_config:
167
+ s.login(smtp_config['username'], smtp_config['password'])
168
+ s.send_message(msg)
169
+
170
+ logger.info(f"Sent email alert to {recipients}")
171
+ except Exception as e:
172
+ logger.error(f"Failed to send email: {e}")
173
+
174
+ return handler
175
+
176
+
177
+ def create_alert_manager(
178
+ rules: Optional[List[str]] = None,
179
+ handlers: Optional[List[str]] = None
180
+ ) -> AlertManager:
181
+ """Create alert manager with default rules."""
182
+ manager = AlertManager()
183
+
184
+ default_rules = {
185
+ "accuracy": accuracy_drop_rule(),
186
+ "drift": drift_detected_rule(),
187
+ "latency": high_latency_rule(),
188
+ }
189
+
190
+ rules = rules or list(default_rules.keys())
191
+ for rule_name in rules:
192
+ if rule_name in default_rules:
193
+ manager.add_rule(default_rules[rule_name])
194
+
195
+ handlers = handlers or ["console"]
196
+ for handler_name in handlers:
197
+ if handler_name == "console":
198
+ manager.add_handler(console_handler)
199
+
200
+ return manager
@@ -0,0 +1,117 @@
1
+ """
2
+ AdamOps Dashboard Module
3
+
4
+ Simple monitoring dashboard.
5
+ """
6
+
7
+ from typing import Any, Dict, List, Optional
8
+ from datetime import datetime
9
+
10
+ from adamops.utils.logging import get_logger
11
+ from adamops.monitoring.performance import PerformanceMonitor
12
+
13
+ logger = get_logger(__name__)
14
+
15
+
16
+ def generate_dashboard_html(
17
+ monitors: Dict[str, PerformanceMonitor],
18
+ title: str = "AdamOps Monitoring Dashboard"
19
+ ) -> str:
20
+ """
21
+ Generate HTML dashboard for monitoring.
22
+
23
+ Args:
24
+ monitors: Dict of model names to monitors.
25
+ title: Dashboard title.
26
+
27
+ Returns:
28
+ HTML string.
29
+ """
30
+
31
+ model_cards = ""
32
+ for name, monitor in monitors.items():
33
+ summary = monitor.summary()
34
+ metrics = summary.get("latest_metrics", {})
35
+
36
+ metrics_html = ""
37
+ for metric, value in metrics.items():
38
+ if isinstance(value, float):
39
+ metrics_html += f'<div class="metric"><span class="label">{metric}</span><span class="value">{value:.4f}</span></div>'
40
+
41
+ model_cards += f'''
42
+ <div class="card">
43
+ <h3>{name}</h3>
44
+ <p>Entries: {summary.get("entries", 0)}</p>
45
+ <p>Last update: {summary.get("latest_timestamp", "N/A")}</p>
46
+ <div class="metrics">{metrics_html}</div>
47
+ </div>
48
+ '''
49
+
50
+ html = f'''<!DOCTYPE html>
51
+ <html>
52
+ <head>
53
+ <title>{title}</title>
54
+ <style>
55
+ body {{ font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #1a1a2e; color: #eee; }}
56
+ h1 {{ color: #4a90d9; }}
57
+ .container {{ max-width: 1200px; margin: 0 auto; }}
58
+ .grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; }}
59
+ .card {{ background: #16213e; padding: 20px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }}
60
+ .card h3 {{ color: #4a90d9; margin-top: 0; }}
61
+ .metrics {{ margin-top: 15px; }}
62
+ .metric {{ display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #333; }}
63
+ .label {{ color: #888; }}
64
+ .value {{ font-weight: bold; color: #4ecdc4; }}
65
+ .timestamp {{ color: #666; font-size: 12px; margin-top: 20px; }}
66
+ </style>
67
+ </head>
68
+ <body>
69
+ <div class="container">
70
+ <h1>{title}</h1>
71
+ <div class="grid">{model_cards}</div>
72
+ <p class="timestamp">Generated: {datetime.now().isoformat()}</p>
73
+ </div>
74
+ </body>
75
+ </html>'''
76
+
77
+ return html
78
+
79
+
80
+ def save_dashboard(
81
+ monitors: Dict[str, PerformanceMonitor],
82
+ output_path: str,
83
+ title: str = "AdamOps Monitoring Dashboard"
84
+ ):
85
+ """Save dashboard to HTML file."""
86
+ html = generate_dashboard_html(monitors, title)
87
+ with open(output_path, 'w') as f:
88
+ f.write(html)
89
+ logger.info(f"Dashboard saved to {output_path}")
90
+
91
+
92
+ def create_streamlit_dashboard(monitors: Dict[str, PerformanceMonitor]) -> str:
93
+ """Generate Streamlit dashboard code."""
94
+ return '''"""AdamOps Monitoring Dashboard
95
+ Run with: streamlit run dashboard.py
96
+ """
97
+ import streamlit as st
98
+ import pandas as pd
99
+ import plotly.express as px
100
+ from adamops.monitoring.performance import PerformanceMonitor
101
+
102
+ st.set_page_config(page_title="AdamOps Monitor", layout="wide")
103
+ st.title("AdamOps Monitoring Dashboard")
104
+
105
+ # Load monitors (customize paths)
106
+ # monitor = PerformanceMonitor("model_name")
107
+
108
+ # Placeholder content
109
+ st.subheader("Model Performance")
110
+ st.info("Configure monitors to see metrics")
111
+
112
+ # Example metrics display
113
+ col1, col2, col3 = st.columns(3)
114
+ col1.metric("Accuracy", "0.95", "+0.02")
115
+ col2.metric("Latency", "45ms", "-5ms")
116
+ col3.metric("Predictions", "1,234", "+100")
117
+ '''