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.
- adamops/__init__.py +40 -0
- adamops/cli.py +163 -0
- adamops/data/__init__.py +24 -0
- adamops/data/feature_engineering.py +284 -0
- adamops/data/loaders.py +922 -0
- adamops/data/preprocessors.py +227 -0
- adamops/data/splitters.py +218 -0
- adamops/data/validators.py +148 -0
- adamops/deployment/__init__.py +21 -0
- adamops/deployment/api.py +237 -0
- adamops/deployment/cloud.py +191 -0
- adamops/deployment/containerize.py +262 -0
- adamops/deployment/exporters.py +148 -0
- adamops/evaluation/__init__.py +24 -0
- adamops/evaluation/comparison.py +133 -0
- adamops/evaluation/explainability.py +143 -0
- adamops/evaluation/metrics.py +233 -0
- adamops/evaluation/reports.py +165 -0
- adamops/evaluation/visualization.py +238 -0
- adamops/models/__init__.py +21 -0
- adamops/models/automl.py +277 -0
- adamops/models/ensembles.py +228 -0
- adamops/models/modelops.py +308 -0
- adamops/models/registry.py +250 -0
- adamops/monitoring/__init__.py +21 -0
- adamops/monitoring/alerts.py +200 -0
- adamops/monitoring/dashboard.py +117 -0
- adamops/monitoring/drift.py +212 -0
- adamops/monitoring/performance.py +195 -0
- adamops/pipelines/__init__.py +15 -0
- adamops/pipelines/orchestrators.py +183 -0
- adamops/pipelines/workflows.py +212 -0
- adamops/utils/__init__.py +18 -0
- adamops/utils/config.py +457 -0
- adamops/utils/helpers.py +663 -0
- adamops/utils/logging.py +412 -0
- adamops-0.1.0.dist-info/METADATA +310 -0
- adamops-0.1.0.dist-info/RECORD +42 -0
- adamops-0.1.0.dist-info/WHEEL +5 -0
- adamops-0.1.0.dist-info/entry_points.txt +2 -0
- adamops-0.1.0.dist-info/licenses/LICENSE +21 -0
- adamops-0.1.0.dist-info/top_level.txt +1 -0
adamops/utils/logging.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AdamOps Logging Module
|
|
3
|
+
|
|
4
|
+
Provides centralized logging functionality for the entire library.
|
|
5
|
+
Supports console and file logging with configurable levels and formats.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional, Union
|
|
12
|
+
from logging.handlers import RotatingFileHandler
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
# Custom log levels
|
|
16
|
+
TRACE = 5
|
|
17
|
+
logging.addLevelName(TRACE, "TRACE")
|
|
18
|
+
|
|
19
|
+
# Module-level logger cache
|
|
20
|
+
_loggers: dict = {}
|
|
21
|
+
|
|
22
|
+
# Default format
|
|
23
|
+
DEFAULT_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
24
|
+
DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ColoredFormatter(logging.Formatter):
|
|
28
|
+
"""
|
|
29
|
+
Colored formatter for console output.
|
|
30
|
+
|
|
31
|
+
Adds color codes to log messages based on log level.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# ANSI color codes
|
|
35
|
+
COLORS = {
|
|
36
|
+
"TRACE": "\033[37m", # White
|
|
37
|
+
"DEBUG": "\033[36m", # Cyan
|
|
38
|
+
"INFO": "\033[32m", # Green
|
|
39
|
+
"WARNING": "\033[33m", # Yellow
|
|
40
|
+
"ERROR": "\033[31m", # Red
|
|
41
|
+
"CRITICAL": "\033[35m", # Magenta
|
|
42
|
+
}
|
|
43
|
+
RESET = "\033[0m"
|
|
44
|
+
BOLD = "\033[1m"
|
|
45
|
+
|
|
46
|
+
def __init__(self, fmt: Optional[str] = None, datefmt: Optional[str] = None, use_colors: bool = True):
|
|
47
|
+
super().__init__(fmt or DEFAULT_FORMAT, datefmt or DEFAULT_DATE_FORMAT)
|
|
48
|
+
self.use_colors = use_colors
|
|
49
|
+
|
|
50
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
51
|
+
"""Format log record with optional colors."""
|
|
52
|
+
# Save original levelname
|
|
53
|
+
original_levelname = record.levelname
|
|
54
|
+
|
|
55
|
+
if self.use_colors and record.levelname in self.COLORS:
|
|
56
|
+
color = self.COLORS[record.levelname]
|
|
57
|
+
record.levelname = f"{color}{self.BOLD}{record.levelname}{self.RESET}"
|
|
58
|
+
record.msg = f"{color}{record.msg}{self.RESET}"
|
|
59
|
+
|
|
60
|
+
result = super().format(record)
|
|
61
|
+
|
|
62
|
+
# Restore original levelname
|
|
63
|
+
record.levelname = original_levelname
|
|
64
|
+
|
|
65
|
+
return result
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AdamOpsLogger:
|
|
69
|
+
"""
|
|
70
|
+
Custom logger class for AdamOps.
|
|
71
|
+
|
|
72
|
+
Provides a unified logging interface with features like:
|
|
73
|
+
- Console and file logging
|
|
74
|
+
- Colored output
|
|
75
|
+
- Automatic log rotation
|
|
76
|
+
- Context managers for temporary log level changes
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
>>> logger = AdamOpsLogger("my_module")
|
|
80
|
+
>>> logger.info("This is an info message")
|
|
81
|
+
>>> logger.debug("This is a debug message")
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
name: str,
|
|
87
|
+
level: str = "INFO",
|
|
88
|
+
log_file: Optional[str] = None,
|
|
89
|
+
console: bool = True,
|
|
90
|
+
use_colors: bool = True,
|
|
91
|
+
format_string: Optional[str] = None,
|
|
92
|
+
max_bytes: int = 10485760, # 10MB
|
|
93
|
+
backup_count: int = 5,
|
|
94
|
+
):
|
|
95
|
+
"""
|
|
96
|
+
Initialize the logger.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
name: Logger name (usually module name).
|
|
100
|
+
level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL).
|
|
101
|
+
log_file: Optional path to log file.
|
|
102
|
+
console: Whether to log to console.
|
|
103
|
+
use_colors: Whether to use colored output.
|
|
104
|
+
format_string: Custom format string.
|
|
105
|
+
max_bytes: Maximum log file size before rotation.
|
|
106
|
+
backup_count: Number of backup files to keep.
|
|
107
|
+
"""
|
|
108
|
+
self.name = name
|
|
109
|
+
self._logger = logging.getLogger(name)
|
|
110
|
+
self._logger.setLevel(getattr(logging, level.upper(), logging.INFO))
|
|
111
|
+
self._logger.handlers = [] # Clear existing handlers
|
|
112
|
+
|
|
113
|
+
format_str = format_string or DEFAULT_FORMAT
|
|
114
|
+
|
|
115
|
+
# Console handler
|
|
116
|
+
if console:
|
|
117
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
118
|
+
console_handler.setFormatter(ColoredFormatter(format_str, use_colors=use_colors))
|
|
119
|
+
self._logger.addHandler(console_handler)
|
|
120
|
+
|
|
121
|
+
# File handler
|
|
122
|
+
if log_file:
|
|
123
|
+
log_path = Path(log_file)
|
|
124
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
|
|
126
|
+
file_handler = RotatingFileHandler(
|
|
127
|
+
log_file,
|
|
128
|
+
maxBytes=max_bytes,
|
|
129
|
+
backupCount=backup_count,
|
|
130
|
+
encoding="utf-8",
|
|
131
|
+
)
|
|
132
|
+
file_handler.setFormatter(logging.Formatter(format_str, DEFAULT_DATE_FORMAT))
|
|
133
|
+
self._logger.addHandler(file_handler)
|
|
134
|
+
|
|
135
|
+
# Prevent propagation to root logger
|
|
136
|
+
self._logger.propagate = False
|
|
137
|
+
|
|
138
|
+
def set_level(self, level: str) -> None:
|
|
139
|
+
"""Set the log level."""
|
|
140
|
+
self._logger.setLevel(getattr(logging, level.upper(), logging.INFO))
|
|
141
|
+
|
|
142
|
+
def trace(self, msg: str, *args, **kwargs) -> None:
|
|
143
|
+
"""Log a TRACE level message."""
|
|
144
|
+
self._logger.log(TRACE, msg, *args, **kwargs)
|
|
145
|
+
|
|
146
|
+
def debug(self, msg: str, *args, **kwargs) -> None:
|
|
147
|
+
"""Log a DEBUG level message."""
|
|
148
|
+
self._logger.debug(msg, *args, **kwargs)
|
|
149
|
+
|
|
150
|
+
def info(self, msg: str, *args, **kwargs) -> None:
|
|
151
|
+
"""Log an INFO level message."""
|
|
152
|
+
self._logger.info(msg, *args, **kwargs)
|
|
153
|
+
|
|
154
|
+
def warning(self, msg: str, *args, **kwargs) -> None:
|
|
155
|
+
"""Log a WARNING level message."""
|
|
156
|
+
self._logger.warning(msg, *args, **kwargs)
|
|
157
|
+
|
|
158
|
+
def warn(self, msg: str, *args, **kwargs) -> None:
|
|
159
|
+
"""Alias for warning."""
|
|
160
|
+
self.warning(msg, *args, **kwargs)
|
|
161
|
+
|
|
162
|
+
def error(self, msg: str, *args, **kwargs) -> None:
|
|
163
|
+
"""Log an ERROR level message."""
|
|
164
|
+
self._logger.error(msg, *args, **kwargs)
|
|
165
|
+
|
|
166
|
+
def critical(self, msg: str, *args, **kwargs) -> None:
|
|
167
|
+
"""Log a CRITICAL level message."""
|
|
168
|
+
self._logger.critical(msg, *args, **kwargs)
|
|
169
|
+
|
|
170
|
+
def exception(self, msg: str, *args, **kwargs) -> None:
|
|
171
|
+
"""Log an exception with traceback."""
|
|
172
|
+
self._logger.exception(msg, *args, **kwargs)
|
|
173
|
+
|
|
174
|
+
def log(self, level: int, msg: str, *args, **kwargs) -> None:
|
|
175
|
+
"""Log a message at the specified level."""
|
|
176
|
+
self._logger.log(level, msg, *args, **kwargs)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def get_logger(
|
|
180
|
+
name: str = "adamops",
|
|
181
|
+
level: Optional[str] = None,
|
|
182
|
+
log_file: Optional[str] = None,
|
|
183
|
+
console: bool = True,
|
|
184
|
+
use_colors: bool = True,
|
|
185
|
+
) -> AdamOpsLogger:
|
|
186
|
+
"""
|
|
187
|
+
Get or create a logger with the specified name.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
name: Logger name.
|
|
191
|
+
level: Log level (default: INFO).
|
|
192
|
+
log_file: Optional path to log file.
|
|
193
|
+
console: Whether to log to console.
|
|
194
|
+
use_colors: Whether to use colored output.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
AdamOpsLogger: Logger instance.
|
|
198
|
+
|
|
199
|
+
Example:
|
|
200
|
+
>>> logger = get_logger("my_module")
|
|
201
|
+
>>> logger.info("Processing data...")
|
|
202
|
+
"""
|
|
203
|
+
# Use default level from config if not specified
|
|
204
|
+
if level is None:
|
|
205
|
+
try:
|
|
206
|
+
from adamops.utils.config import get_config
|
|
207
|
+
config = get_config()
|
|
208
|
+
level = config.logging.level
|
|
209
|
+
if log_file is None:
|
|
210
|
+
log_file = config.logging.file
|
|
211
|
+
except ImportError:
|
|
212
|
+
level = "INFO"
|
|
213
|
+
|
|
214
|
+
# Check cache
|
|
215
|
+
cache_key = f"{name}:{level}:{log_file}:{console}:{use_colors}"
|
|
216
|
+
if cache_key not in _loggers:
|
|
217
|
+
_loggers[cache_key] = AdamOpsLogger(
|
|
218
|
+
name=name,
|
|
219
|
+
level=level,
|
|
220
|
+
log_file=log_file,
|
|
221
|
+
console=console,
|
|
222
|
+
use_colors=use_colors,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return _loggers[cache_key]
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def setup_logging(
|
|
229
|
+
level: str = "INFO",
|
|
230
|
+
log_file: Optional[str] = None,
|
|
231
|
+
console: bool = True,
|
|
232
|
+
use_colors: bool = True,
|
|
233
|
+
format_string: Optional[str] = None,
|
|
234
|
+
) -> None:
|
|
235
|
+
"""
|
|
236
|
+
Set up global logging configuration for AdamOps.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
level: Global log level.
|
|
240
|
+
log_file: Path to log file.
|
|
241
|
+
console: Whether to log to console.
|
|
242
|
+
use_colors: Whether to use colored output.
|
|
243
|
+
format_string: Custom format string.
|
|
244
|
+
|
|
245
|
+
Example:
|
|
246
|
+
>>> setup_logging(level="DEBUG", log_file="adamops.log")
|
|
247
|
+
"""
|
|
248
|
+
# Configure root adamops logger
|
|
249
|
+
root_logger = logging.getLogger("adamops")
|
|
250
|
+
root_logger.setLevel(getattr(logging, level.upper(), logging.INFO))
|
|
251
|
+
root_logger.handlers = []
|
|
252
|
+
|
|
253
|
+
format_str = format_string or DEFAULT_FORMAT
|
|
254
|
+
|
|
255
|
+
if console:
|
|
256
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
257
|
+
console_handler.setFormatter(ColoredFormatter(format_str, use_colors=use_colors))
|
|
258
|
+
root_logger.addHandler(console_handler)
|
|
259
|
+
|
|
260
|
+
if log_file:
|
|
261
|
+
log_path = Path(log_file)
|
|
262
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
263
|
+
|
|
264
|
+
file_handler = RotatingFileHandler(
|
|
265
|
+
log_file,
|
|
266
|
+
maxBytes=10485760,
|
|
267
|
+
backupCount=5,
|
|
268
|
+
encoding="utf-8",
|
|
269
|
+
)
|
|
270
|
+
file_handler.setFormatter(logging.Formatter(format_str, DEFAULT_DATE_FORMAT))
|
|
271
|
+
root_logger.addHandler(file_handler)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class LogContext:
|
|
275
|
+
"""
|
|
276
|
+
Context manager for temporary log level changes.
|
|
277
|
+
|
|
278
|
+
Example:
|
|
279
|
+
>>> logger = get_logger("my_module")
|
|
280
|
+
>>> with LogContext(logger, "DEBUG"):
|
|
281
|
+
... logger.debug("This will be logged")
|
|
282
|
+
>>> logger.debug("This might not be logged")
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
def __init__(self, logger: Union[AdamOpsLogger, logging.Logger], level: str):
|
|
286
|
+
"""
|
|
287
|
+
Initialize context manager.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
logger: Logger to modify.
|
|
291
|
+
level: Temporary log level.
|
|
292
|
+
"""
|
|
293
|
+
self.logger = logger
|
|
294
|
+
self.new_level = getattr(logging, level.upper(), logging.INFO)
|
|
295
|
+
self.old_level = None
|
|
296
|
+
|
|
297
|
+
def __enter__(self) -> None:
|
|
298
|
+
"""Enter context and set new level."""
|
|
299
|
+
if isinstance(self.logger, AdamOpsLogger):
|
|
300
|
+
self.old_level = self.logger._logger.level
|
|
301
|
+
self.logger._logger.setLevel(self.new_level)
|
|
302
|
+
else:
|
|
303
|
+
self.old_level = self.logger.level
|
|
304
|
+
self.logger.setLevel(self.new_level)
|
|
305
|
+
|
|
306
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
307
|
+
"""Exit context and restore old level."""
|
|
308
|
+
if isinstance(self.logger, AdamOpsLogger):
|
|
309
|
+
self.logger._logger.setLevel(self.old_level)
|
|
310
|
+
else:
|
|
311
|
+
self.logger.setLevel(self.old_level)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class Timer:
|
|
315
|
+
"""
|
|
316
|
+
Context manager for timing operations with optional logging.
|
|
317
|
+
|
|
318
|
+
Example:
|
|
319
|
+
>>> logger = get_logger("my_module")
|
|
320
|
+
>>> with Timer("Data loading", logger):
|
|
321
|
+
... load_data()
|
|
322
|
+
[INFO] Data loading completed in 2.34s
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
def __init__(self, operation: str, logger: Optional[AdamOpsLogger] = None, level: str = "INFO"):
|
|
326
|
+
"""
|
|
327
|
+
Initialize timer.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
operation: Name of the operation being timed.
|
|
331
|
+
logger: Optional logger for timing output.
|
|
332
|
+
level: Log level for timing message.
|
|
333
|
+
"""
|
|
334
|
+
self.operation = operation
|
|
335
|
+
self.logger = logger
|
|
336
|
+
self.level = level.upper()
|
|
337
|
+
self.start_time: Optional[datetime] = None
|
|
338
|
+
self.end_time: Optional[datetime] = None
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def elapsed(self) -> float:
|
|
342
|
+
"""Get elapsed time in seconds."""
|
|
343
|
+
if self.start_time is None:
|
|
344
|
+
return 0.0
|
|
345
|
+
end = self.end_time or datetime.now()
|
|
346
|
+
return (end - self.start_time).total_seconds()
|
|
347
|
+
|
|
348
|
+
def __enter__(self) -> "Timer":
|
|
349
|
+
"""Start timer."""
|
|
350
|
+
self.start_time = datetime.now()
|
|
351
|
+
if self.logger:
|
|
352
|
+
self.logger.log(
|
|
353
|
+
getattr(logging, self.level, logging.INFO),
|
|
354
|
+
f"Starting: {self.operation}..."
|
|
355
|
+
)
|
|
356
|
+
return self
|
|
357
|
+
|
|
358
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
359
|
+
"""Stop timer and log result."""
|
|
360
|
+
self.end_time = datetime.now()
|
|
361
|
+
if self.logger:
|
|
362
|
+
status = "completed" if exc_type is None else "failed"
|
|
363
|
+
self.logger.log(
|
|
364
|
+
getattr(logging, self.level, logging.INFO),
|
|
365
|
+
f"{self.operation} {status} in {self.elapsed:.2f}s"
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def log_function_call(logger: Optional[AdamOpsLogger] = None, level: str = "DEBUG"):
|
|
370
|
+
"""
|
|
371
|
+
Decorator to log function calls and their results.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
logger: Logger to use (creates one if not provided).
|
|
375
|
+
level: Log level for function call messages.
|
|
376
|
+
|
|
377
|
+
Example:
|
|
378
|
+
>>> @log_function_call()
|
|
379
|
+
... def process_data(df):
|
|
380
|
+
... return df.dropna()
|
|
381
|
+
"""
|
|
382
|
+
def decorator(func):
|
|
383
|
+
def wrapper(*args, **kwargs):
|
|
384
|
+
nonlocal logger
|
|
385
|
+
if logger is None:
|
|
386
|
+
logger = get_logger(func.__module__)
|
|
387
|
+
|
|
388
|
+
log_level = getattr(logging, level.upper(), logging.DEBUG)
|
|
389
|
+
|
|
390
|
+
# Log function call
|
|
391
|
+
args_str = ", ".join([repr(a) for a in args[:3]]) # Limit args
|
|
392
|
+
if len(args) > 3:
|
|
393
|
+
args_str += ", ..."
|
|
394
|
+
kwargs_str = ", ".join([f"{k}={repr(v)}" for k, v in list(kwargs.items())[:3]])
|
|
395
|
+
if len(kwargs) > 3:
|
|
396
|
+
kwargs_str += ", ..."
|
|
397
|
+
|
|
398
|
+
call_str = f"{func.__name__}({args_str}{', ' if args_str and kwargs_str else ''}{kwargs_str})"
|
|
399
|
+
logger.log(log_level, f"Calling: {call_str}")
|
|
400
|
+
|
|
401
|
+
try:
|
|
402
|
+
result = func(*args, **kwargs)
|
|
403
|
+
logger.log(log_level, f"{func.__name__} returned successfully")
|
|
404
|
+
return result
|
|
405
|
+
except Exception as e:
|
|
406
|
+
logger.error(f"{func.__name__} raised {type(e).__name__}: {e}")
|
|
407
|
+
raise
|
|
408
|
+
|
|
409
|
+
wrapper.__name__ = func.__name__
|
|
410
|
+
wrapper.__doc__ = func.__doc__
|
|
411
|
+
return wrapper
|
|
412
|
+
return decorator
|