meerschaum.utils.warnings
Handle all things warnings and errors.
1#! /usr/bin/env python 2# -*- coding: utf-8 -*- 3# vim:fenc=utf-8 4 5""" 6Handle all things warnings and errors. 7""" 8 9from __future__ import annotations 10from meerschaum.utils.typing import Any, Union 11from meerschaum.utils.debug import dprint 12 13import sys 14import warnings 15 16#: Set (process-wide, like `warnings.catch_warnings`) while warnings are being recorded. 17_RECORDING: bool = False 18 19__all__ = ( 20 'warn', 21 'info', 22 'error', 23 'dprint', 24) 25 26warnings.filterwarnings( 27 "always", 28 category = UserWarning 29) 30warnings.filterwarnings( 31 "always", 32 category = ImportWarning 33) 34warnings.filterwarnings( 35 "ignore", 36 category = RuntimeWarning 37) 38 39 40def enable_depreciation_warnings(name) -> None: 41 """Enable depreciation warnings in the warnings module.""" 42 import meerschaum.actions 43 warnings.filterwarnings( 44 "always", 45 category = DeprecationWarning, 46 module = name 47 ) 48 49def warn(*args, stacklevel=2, stack=True, color: bool = True, **kw) -> None: 50 """ 51 Print a stylized warning message. 52 May be captured by `warnings.filterwarnings()`. 53 """ 54 if stacklevel is None: 55 stacklevel = 1 56 stack = False 57 _old_sw = warnings.showwarning 58 59 get_config = None 60 if color: 61 try: 62 from meerschaum.utils.formatting import ( 63 CHARSET, colored, fill_ansi, highlight_pipes, _init, stderr_ansi 64 ) 65 ### Warnings go to stderr: styled by stderr's own terminal check. 66 ANSI = stderr_ansi() 67 except ImportError: 68 CHARSET = 'ascii' 69 ANSI = False 70 try: 71 from meerschaum.config import get_config as _get_config 72 from meerschaum.config import _config 73 cf = _config() 74 get_config = _get_config 75 except ImportError: 76 get_config = None 77 78 if get_config is None and color: 79 try: 80 warn_config = cf['formatting']['warnings'] 81 except KeyError: 82 warn_config = { 83 'ansi' : {'color' : []}, 84 'unicode' : {'icon' : ''}, 85 'ascii' : {'icon' : ''}, 86 } 87 elif color: 88 warn_config = get_config('formatting', 'warnings', patch=True) 89 a = list(args) 90 a[0] = ' ' + (warn_config[CHARSET]['icon'] if color else '') + ' ' + str(a[0]) 91 if color: 92 if ANSI: 93 _init() 94 a[0] = fill_ansi(highlight_pipes(a[0]), ansi=True, **warn_config['ansi']['rich']) 95 96 ### Optionally omit the warning location. 97 def _no_stack_sw(message, category, filename, lineno, file=None, line=None): 98 sys.stderr.write(str(message) + '\n') 99 100 ### While a caller records warnings (`_RECORDING`, the worker's quiet connector builds), a 101 ### stackless warning goes through `warnings` like any other, so it can be recorded instead 102 ### of being written straight to stderr. 103 swap = not stack and not _RECORDING 104 if swap: 105 warnings.showwarning = _no_stack_sw 106 warnings.warn(*a, stacklevel=stacklevel, **kw) 107 if swap: 108 warnings.showwarning = _old_sw 109 110 111def exception_with_traceback( 112 message: str, 113 exception_class = Exception, 114 stacklevel = 1, 115 tb_type = 'single' 116): 117 """Traceback construction help found here: 118 https://stackoverflow.com/questions/27138440/how-to-create-a-traceback-object 119 """ 120 import types 121 tb, depth = None, 0 122 while True: 123 try: 124 frame = sys._getframe(depth) 125 depth += 1 126 except ValueError as e: 127 break 128 129 tb = types.TracebackType(tb, frame, frame.f_lasti, frame.f_lineno) 130 131 tbs, _tb = [], tb 132 while True: 133 if _tb is None: 134 break 135 tbs.append(_tb) 136 _tb = _tb.tb_next 137 138 found_main, main_i = False, 0 139 first_mrsm_after_main = None 140 last_mrsm_i = None 141 tbs[(-1 * stacklevel)].tb_next = None 142 for i, _tb in enumerate([_tb for _tb in tbs]): 143 if 'meerschaum' in str(_tb.tb_frame) and '__main__.py' in str(_tb.tb_frame): 144 found_main = True 145 main_i = i 146 continue 147 if i >= (len(tbs) - (stacklevel - 1)): 148 tbs[i] = None 149 elif ( 150 found_main and 'meerschaum' in str(_tb.tb_frame) 151 and first_mrsm_after_main is None 152 and 'Shell' not in str(_tb.tb_frame) 153 ): 154 first_mrsm_after_main = i 155 elif 'meerschaum' in str(_tb.tb_frame): 156 last_mrsm_i = i 157 158 tbs = [_tb for _tb in tbs if tb is not None] 159 160 if tb_type == 'single': 161 return exception_class(message).with_traceback(tbs[-3]) 162 return exception_class(message).with_traceback(tbs[first_mrsm_after_main]) 163 164 165def error( 166 message: str, 167 exception_class = Exception, 168 nopretty: bool = False, 169 silent: bool = True, 170 stack: bool = True, 171 raise_: bool = True, 172): 173 """ 174 Raise an exception with supressed traceback. 175 """ 176 from meerschaum.utils.formatting import ( 177 CHARSET, 178 ANSI, 179 get_console, 180 fill_ansi, 181 highlight_pipes, 182 _init, 183 ) 184 from meerschaum.utils.packages import import_rich, attempt_import 185 from meerschaum.config import get_config 186 _ = import_rich() 187 rich_traceback = attempt_import('rich.traceback') 188 error_config = get_config('formatting', 'errors', patch=True) 189 message = ' ' + error_config[CHARSET]['icon'] + ' ' + str(message) 190 exception = exception_with_traceback(message, exception_class, stacklevel=3) 191 color_message = str(message) 192 color_exception = exception_with_traceback(color_message, exception_class, stacklevel=3) 193 if ANSI and not nopretty: 194 _init() 195 color_message = '\n' + fill_ansi(highlight_pipes(message), **error_config['ansi']['rich']) 196 color_exception = exception_with_traceback(color_message, exception_class, stacklevel=3) 197 try: 198 trace = rich_traceback.Traceback.extract( 199 exception_class, exception, exception.__traceback__ 200 ) 201 rtb = rich_traceback.Traceback(trace) 202 except Exception: 203 trace, rtb = None, None 204 if trace is None or rtb is None: 205 nopretty = True 206 if not nopretty and stack: 207 if get_console() is not None: 208 get_console().print(rtb) 209 if raise_: 210 raise color_exception 211 212 213def info(message: str, icon: bool = True, **kw): 214 """Print an informative message.""" 215 from meerschaum.utils.formatting import ( 216 CHARSET, 217 ANSI, 218 highlight_pipes, 219 fill_ansi, 220 _init, 221 ) 222 from meerschaum.config import get_config 223 info_config = get_config('formatting', 'info', patch=True) 224 if icon: 225 message = ' ' + info_config[CHARSET]['icon'] + ' ' + message 226 if ANSI: 227 _init() 228 message = highlight_pipes(message) 229 lines = message.split('\n') 230 message = fill_ansi(lines[0], **info_config['ansi']['rich']) + ( 231 '\n' + '\n'.join(lines[1:]) if len(lines) > 1 else '' 232 ) 233 print(message, flush=True)
def
warn(*args, stacklevel=2, stack=True, color: bool = True, **kw) -> None:
50def warn(*args, stacklevel=2, stack=True, color: bool = True, **kw) -> None: 51 """ 52 Print a stylized warning message. 53 May be captured by `warnings.filterwarnings()`. 54 """ 55 if stacklevel is None: 56 stacklevel = 1 57 stack = False 58 _old_sw = warnings.showwarning 59 60 get_config = None 61 if color: 62 try: 63 from meerschaum.utils.formatting import ( 64 CHARSET, colored, fill_ansi, highlight_pipes, _init, stderr_ansi 65 ) 66 ### Warnings go to stderr: styled by stderr's own terminal check. 67 ANSI = stderr_ansi() 68 except ImportError: 69 CHARSET = 'ascii' 70 ANSI = False 71 try: 72 from meerschaum.config import get_config as _get_config 73 from meerschaum.config import _config 74 cf = _config() 75 get_config = _get_config 76 except ImportError: 77 get_config = None 78 79 if get_config is None and color: 80 try: 81 warn_config = cf['formatting']['warnings'] 82 except KeyError: 83 warn_config = { 84 'ansi' : {'color' : []}, 85 'unicode' : {'icon' : ''}, 86 'ascii' : {'icon' : ''}, 87 } 88 elif color: 89 warn_config = get_config('formatting', 'warnings', patch=True) 90 a = list(args) 91 a[0] = ' ' + (warn_config[CHARSET]['icon'] if color else '') + ' ' + str(a[0]) 92 if color: 93 if ANSI: 94 _init() 95 a[0] = fill_ansi(highlight_pipes(a[0]), ansi=True, **warn_config['ansi']['rich']) 96 97 ### Optionally omit the warning location. 98 def _no_stack_sw(message, category, filename, lineno, file=None, line=None): 99 sys.stderr.write(str(message) + '\n') 100 101 ### While a caller records warnings (`_RECORDING`, the worker's quiet connector builds), a 102 ### stackless warning goes through `warnings` like any other, so it can be recorded instead 103 ### of being written straight to stderr. 104 swap = not stack and not _RECORDING 105 if swap: 106 warnings.showwarning = _no_stack_sw 107 warnings.warn(*a, stacklevel=stacklevel, **kw) 108 if swap: 109 warnings.showwarning = _old_sw
Print a stylized warning message.
May be captured by warnings.filterwarnings().
def
info(message: str, icon: bool = True, **kw):
214def info(message: str, icon: bool = True, **kw): 215 """Print an informative message.""" 216 from meerschaum.utils.formatting import ( 217 CHARSET, 218 ANSI, 219 highlight_pipes, 220 fill_ansi, 221 _init, 222 ) 223 from meerschaum.config import get_config 224 info_config = get_config('formatting', 'info', patch=True) 225 if icon: 226 message = ' ' + info_config[CHARSET]['icon'] + ' ' + message 227 if ANSI: 228 _init() 229 message = highlight_pipes(message) 230 lines = message.split('\n') 231 message = fill_ansi(lines[0], **info_config['ansi']['rich']) + ( 232 '\n' + '\n'.join(lines[1:]) if len(lines) > 1 else '' 233 ) 234 print(message, flush=True)
Print an informative message.
def
error( message: str, exception_class=<class 'Exception'>, nopretty: bool = False, silent: bool = True, stack: bool = True, raise_: bool = True):
166def error( 167 message: str, 168 exception_class = Exception, 169 nopretty: bool = False, 170 silent: bool = True, 171 stack: bool = True, 172 raise_: bool = True, 173): 174 """ 175 Raise an exception with supressed traceback. 176 """ 177 from meerschaum.utils.formatting import ( 178 CHARSET, 179 ANSI, 180 get_console, 181 fill_ansi, 182 highlight_pipes, 183 _init, 184 ) 185 from meerschaum.utils.packages import import_rich, attempt_import 186 from meerschaum.config import get_config 187 _ = import_rich() 188 rich_traceback = attempt_import('rich.traceback') 189 error_config = get_config('formatting', 'errors', patch=True) 190 message = ' ' + error_config[CHARSET]['icon'] + ' ' + str(message) 191 exception = exception_with_traceback(message, exception_class, stacklevel=3) 192 color_message = str(message) 193 color_exception = exception_with_traceback(color_message, exception_class, stacklevel=3) 194 if ANSI and not nopretty: 195 _init() 196 color_message = '\n' + fill_ansi(highlight_pipes(message), **error_config['ansi']['rich']) 197 color_exception = exception_with_traceback(color_message, exception_class, stacklevel=3) 198 try: 199 trace = rich_traceback.Traceback.extract( 200 exception_class, exception, exception.__traceback__ 201 ) 202 rtb = rich_traceback.Traceback(trace) 203 except Exception: 204 trace, rtb = None, None 205 if trace is None or rtb is None: 206 nopretty = True 207 if not nopretty and stack: 208 if get_console() is not None: 209 get_console().print(rtb) 210 if raise_: 211 raise color_exception
Raise an exception with supressed traceback.
def
dprint( msg: str, leader: bool = True, timestamp: bool = True, package: bool = True, color: 'Optional[Union[str, List[str]], bool]' = None, attrs: List[str] | None = None, nopretty: bool = False, _progress: rich.progress.Progress | None = None, _task: int | None = None, _package: str | None = None, _lineno: int | None = None, **kw) -> None:
32def dprint( 33 msg: str, 34 leader: bool = True, 35 timestamp: bool = True, 36 package: bool = True, 37 color: Optional[Union[str, List[str]], bool] = None, 38 attrs: Optional[List[str]] = None, 39 nopretty: bool = False, 40 _progress: Optional['rich.progress.Progress'] = None, 41 _task: Optional[int] = None, 42 _package: Optional[str] = None, 43 _lineno: Optional[int] = None, 44 **kw 45) -> None: 46 """ 47 Print a debug message to stderr. 48 49 The header names the calling module and line. The Rust core passes its own module path and 50 line as `_package` and `_lineno` (its debug events have no Python caller). 51 """ 52 import meerschaum.config.paths as paths 53 if attrs is None: 54 attrs = [] 55 56 if not isinstance(color, bool) and not nopretty: 57 try: 58 from meerschaum.utils.formatting import CHARSET, ANSI, colored 59 except Exception as e: 60 CHARSET, ANSI, colored = 'ascii', False, None 61 from meerschaum.config import _config 62 cf = _config('formatting') 63 _color = color 64 else: 65 CHARSET, ANSI, colored, _color, cf = 'ascii', False, None, None, None 66 67 if timestamp: 68 from meerschaum.utils.dtypes import get_current_timestamp 69 now = get_current_timestamp('ms').replace(tzinfo=None) 70 else: 71 now = None 72 73 import logging, sys, inspect 74 logging.basicConfig(format='%(message)s') 75 log = logging.getLogger(__name__) 76 77 if _package is not None: 78 parent_package = _package 79 parent_lineno = _lineno 80 else: 81 parent_frame = inspect.stack()[1][0] 82 parent_info = inspect.getframeinfo(parent_frame) 83 parent_lineno = parent_info.lineno 84 parent_globals = parent_frame.f_globals 85 parent_package = parent_globals['__name__'] 86 msg = str(msg) 87 premsg = "" 88 89 if now: 90 premsg = now.isoformat().split('T', maxsplit=1)[-1][:-3] + (' | ' if package else ':') 91 if package: 92 premsg = premsg + parent_package + ( 93 (':' + str(parent_lineno)) if parent_lineno is not None else '' 94 ) 95 if premsg: 96 premsg += "\n" 97 if leader and cf is not None: 98 try: 99 debug_leader = cf['formatting']['debug'][CHARSET]['icon'] if cf is not None else '' 100 except KeyError: 101 print( 102 "Failed to load config. " + 103 "Please delete the following directories and restart Meerschaum:" 104 ) 105 for p in [paths.CONFIG_DIR_PATH, paths.PERMANENT_PATCH_DIR_PATH]: 106 print(' - ' + str(p)) 107 debug_leader = '' 108 premsg = ' ' + debug_leader + ' ' + premsg 109 110 if ANSI: 111 if _color is not None: 112 if isinstance(_color, str): 113 _color = [_color] 114 else: 115 if cf is not None and not nopretty: 116 try: 117 _color = cf['formatting']['debug']['ansi']['rich'] if cf is not None else {} 118 except KeyError: 119 _color = {} 120 else: 121 _color = {} 122 if colored is not None: 123 premsg = colored(premsg, **_color) 124 125 if _progress is not None: 126 rich_text = _import_rich_text_for_dprint() 127 text = rich_text.Text.from_ansi(premsg + msg) 128 _progress.console.log(text) 129 else: 130 ### v5: debug output goes to stderr (v4 printed to stdout), like warnings and the 131 ### Rust core's debug output. Job logs capture both streams. 132 print(premsg + msg, file=sys.stderr)
Print a debug message to stderr.
The header names the calling module and line. The Rust core passes its own module path and
line as _package and _lineno (its debug events have no Python caller).