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__all__ = (
 17    'warn',
 18    'info',
 19    'error',
 20    'dprint',
 21)
 22
 23warnings.filterwarnings(
 24    "always",
 25    category = UserWarning
 26)
 27warnings.filterwarnings(
 28    "always",
 29    category = ImportWarning
 30)
 31warnings.filterwarnings(
 32    "ignore",
 33    category = RuntimeWarning
 34)
 35
 36
 37def enable_depreciation_warnings(name) -> None:
 38    """Enable depreciation warnings in the warnings module."""
 39    import meerschaum.actions
 40    warnings.filterwarnings(
 41        "always",
 42        category = DeprecationWarning,
 43        module = name
 44    )
 45
 46def warn(*args, stacklevel=2, stack=True, color: bool = True, **kw) -> None:
 47    """
 48    Print a stylized warning message.
 49    May be captured by `warnings.filterwarnings()`.
 50    """
 51    if stacklevel is None:
 52        stacklevel = 1
 53        stack = False
 54    _old_sw = warnings.showwarning
 55
 56    get_config = None
 57    if color:
 58        try:
 59            from meerschaum.utils.formatting import (
 60                CHARSET, ANSI, colored, fill_ansi, highlight_pipes, _init
 61            )
 62        except ImportError:
 63            CHARSET = 'ascii'
 64            ANSI = False
 65        try:
 66            from meerschaum.config import get_config as _get_config
 67            from meerschaum.config import _config
 68            cf = _config()
 69            get_config = _get_config
 70        except ImportError:
 71            get_config = None
 72
 73    if get_config is None and color:
 74        try:
 75            warn_config = cf['formatting']['warnings']
 76        except KeyError:
 77            warn_config = {
 78                'ansi' : {'color' : []},
 79                'unicode' : {'icon' : ''},
 80                'ascii' : {'icon' : ''},
 81            }
 82    elif color:
 83        warn_config = get_config('formatting', 'warnings', patch=True)
 84    a = list(args)
 85    a[0] = ' ' + (warn_config[CHARSET]['icon'] if color else '') + ' ' + str(a[0])
 86    if color:
 87        if ANSI:
 88            _init()
 89            a[0] = fill_ansi(highlight_pipes(a[0]), **warn_config['ansi']['rich'])
 90
 91    ### Optionally omit the warning location.
 92    def _no_stack_sw(message, category, filename, lineno, file=None, line=None):
 93        sys.stderr.write(str(message) + '\n')
 94
 95    if not stack:
 96        warnings.showwarning = _no_stack_sw
 97    warnings.warn(*a, stacklevel=stacklevel, **kw)
 98    if not stack:
 99        warnings.showwarning = _old_sw
100
101
102def exception_with_traceback(
103        message: str,
104        exception_class = Exception, 
105        stacklevel = 1,
106        tb_type = 'single'
107    ):
108    """Traceback construction help found here:
109    https://stackoverflow.com/questions/27138440/how-to-create-a-traceback-object
110    """
111    import types
112    tb, depth = None, 0
113    while True:
114        try:
115            frame = sys._getframe(depth)
116            depth += 1
117        except ValueError as e:
118            break
119
120        tb = types.TracebackType(tb, frame, frame.f_lasti, frame.f_lineno)
121
122    tbs, _tb = [], tb
123    while True:
124        if _tb is None:
125            break
126        tbs.append(_tb)
127        _tb = _tb.tb_next
128
129    found_main, main_i = False, 0
130    first_mrsm_after_main = None
131    last_mrsm_i = None
132    tbs[(-1 * stacklevel)].tb_next = None
133    for i, _tb in enumerate([_tb for _tb in tbs]):
134        if 'meerschaum' in str(_tb.tb_frame) and '__main__.py' in str(_tb.tb_frame):
135            found_main = True
136            main_i = i
137            continue
138        if i >= (len(tbs) - (stacklevel - 1)):
139            tbs[i] = None
140        elif (
141                found_main and 'meerschaum' in str(_tb.tb_frame)
142                and first_mrsm_after_main is None
143                and 'Shell' not in str(_tb.tb_frame)
144            ):
145            first_mrsm_after_main = i
146        elif 'meerschaum' in str(_tb.tb_frame):
147            last_mrsm_i = i
148
149    tbs = [_tb for _tb in tbs if tb is not None]
150
151    if tb_type == 'single':
152        return exception_class(message).with_traceback(tbs[-3])
153    return exception_class(message).with_traceback(tbs[first_mrsm_after_main])
154
155
156def error(
157    message: str,
158    exception_class = Exception,
159    nopretty: bool = False,
160    silent: bool = True,
161    stack: bool = True,
162    raise_: bool = True,
163):
164    """
165    Raise an exception with supressed traceback.
166    """
167    from meerschaum.utils.formatting import (
168        CHARSET, ANSI, get_console, fill_ansi, highlight_pipes, _init
169    )
170    from meerschaum.utils.packages import import_rich, attempt_import
171    from meerschaum.config import get_config
172    import types, inspect
173    rich = import_rich()
174    rich_traceback = attempt_import('rich.traceback')
175    error_config = get_config('formatting', 'errors', patch=True)
176    message = ' ' + error_config[CHARSET]['icon'] + ' ' + str(message)
177    exception = exception_with_traceback(message, exception_class, stacklevel=3)
178    color_message = str(message)
179    color_exception = exception_with_traceback(color_message, exception_class, stacklevel=3)
180    if ANSI and not nopretty:
181        _init()
182        color_message = '\n' + fill_ansi(highlight_pipes(message), **error_config['ansi']['rich'])
183        color_exception = exception_with_traceback(color_message, exception_class, stacklevel=3)
184    try:
185        trace = rich_traceback.Traceback.extract(
186            exception_class, exception, exception.__traceback__
187        )
188        rtb = rich_traceback.Traceback(trace)
189    except Exception as e:
190        trace, rtb = None, None
191    if trace is None or rtb is None:
192        nopretty = True
193    if not nopretty and stack:
194        if get_console() is not None:
195            get_console().print(rtb)
196    frame = sys._getframe(len(inspect.stack()) - 1)
197    if raise_:
198        raise color_exception
199
200
201def info(message: str, icon: bool = True, **kw):
202    """Print an informative message."""
203    from meerschaum.utils.packages import import_rich, attempt_import
204    from meerschaum.utils.formatting import (
205        CHARSET, ANSI, highlight_pipes, fill_ansi, _init
206    )
207    from meerschaum.config import get_config
208    info_config = get_config('formatting', 'info', patch=True)
209    if icon:
210        message = ' ' + info_config[CHARSET]['icon'] + ' ' + message
211    if ANSI:
212        _init()
213        message = highlight_pipes(message)
214        lines = message.split('\n')
215        message = fill_ansi(lines[0], **info_config['ansi']['rich']) + (
216            '\n' + '\n'.join(lines[1:]) if len(lines) > 1 else ''
217        )
218    print(message)
def warn(*args, stacklevel=2, stack=True, color: bool = True, **kw) -> None:
 47def warn(*args, stacklevel=2, stack=True, color: bool = True, **kw) -> None:
 48    """
 49    Print a stylized warning message.
 50    May be captured by `warnings.filterwarnings()`.
 51    """
 52    if stacklevel is None:
 53        stacklevel = 1
 54        stack = False
 55    _old_sw = warnings.showwarning
 56
 57    get_config = None
 58    if color:
 59        try:
 60            from meerschaum.utils.formatting import (
 61                CHARSET, ANSI, colored, fill_ansi, highlight_pipes, _init
 62            )
 63        except ImportError:
 64            CHARSET = 'ascii'
 65            ANSI = False
 66        try:
 67            from meerschaum.config import get_config as _get_config
 68            from meerschaum.config import _config
 69            cf = _config()
 70            get_config = _get_config
 71        except ImportError:
 72            get_config = None
 73
 74    if get_config is None and color:
 75        try:
 76            warn_config = cf['formatting']['warnings']
 77        except KeyError:
 78            warn_config = {
 79                'ansi' : {'color' : []},
 80                'unicode' : {'icon' : ''},
 81                'ascii' : {'icon' : ''},
 82            }
 83    elif color:
 84        warn_config = get_config('formatting', 'warnings', patch=True)
 85    a = list(args)
 86    a[0] = ' ' + (warn_config[CHARSET]['icon'] if color else '') + ' ' + str(a[0])
 87    if color:
 88        if ANSI:
 89            _init()
 90            a[0] = fill_ansi(highlight_pipes(a[0]), **warn_config['ansi']['rich'])
 91
 92    ### Optionally omit the warning location.
 93    def _no_stack_sw(message, category, filename, lineno, file=None, line=None):
 94        sys.stderr.write(str(message) + '\n')
 95
 96    if not stack:
 97        warnings.showwarning = _no_stack_sw
 98    warnings.warn(*a, stacklevel=stacklevel, **kw)
 99    if not stack:
100        warnings.showwarning = _old_sw

Print a stylized warning message. May be captured by warnings.filterwarnings().

def info(message: str, icon: bool = True, **kw):
202def info(message: str, icon: bool = True, **kw):
203    """Print an informative message."""
204    from meerschaum.utils.packages import import_rich, attempt_import
205    from meerschaum.utils.formatting import (
206        CHARSET, ANSI, highlight_pipes, fill_ansi, _init
207    )
208    from meerschaum.config import get_config
209    info_config = get_config('formatting', 'info', patch=True)
210    if icon:
211        message = ' ' + info_config[CHARSET]['icon'] + ' ' + message
212    if ANSI:
213        _init()
214        message = highlight_pipes(message)
215        lines = message.split('\n')
216        message = fill_ansi(lines[0], **info_config['ansi']['rich']) + (
217            '\n' + '\n'.join(lines[1:]) if len(lines) > 1 else ''
218        )
219    print(message)

Print an informative message.

def error( message: str, exception_class=<class 'Exception'>, nopretty: bool = False, silent: bool = True, stack: bool = True, raise_: bool = True):
157def error(
158    message: str,
159    exception_class = Exception,
160    nopretty: bool = False,
161    silent: bool = True,
162    stack: bool = True,
163    raise_: bool = True,
164):
165    """
166    Raise an exception with supressed traceback.
167    """
168    from meerschaum.utils.formatting import (
169        CHARSET, ANSI, get_console, fill_ansi, highlight_pipes, _init
170    )
171    from meerschaum.utils.packages import import_rich, attempt_import
172    from meerschaum.config import get_config
173    import types, inspect
174    rich = import_rich()
175    rich_traceback = attempt_import('rich.traceback')
176    error_config = get_config('formatting', 'errors', patch=True)
177    message = ' ' + error_config[CHARSET]['icon'] + ' ' + str(message)
178    exception = exception_with_traceback(message, exception_class, stacklevel=3)
179    color_message = str(message)
180    color_exception = exception_with_traceback(color_message, exception_class, stacklevel=3)
181    if ANSI and not nopretty:
182        _init()
183        color_message = '\n' + fill_ansi(highlight_pipes(message), **error_config['ansi']['rich'])
184        color_exception = exception_with_traceback(color_message, exception_class, stacklevel=3)
185    try:
186        trace = rich_traceback.Traceback.extract(
187            exception_class, exception, exception.__traceback__
188        )
189        rtb = rich_traceback.Traceback(trace)
190    except Exception as e:
191        trace, rtb = None, None
192    if trace is None or rtb is None:
193        nopretty = True
194    if not nopretty and stack:
195        if get_console() is not None:
196            get_console().print(rtb)
197    frame = sys._getframe(len(inspect.stack()) - 1)
198    if raise_:
199        raise color_exception

Raise an exception with supressed traceback.

def dprint( msg: str, leader: bool = True, package: bool = True, color: Union[str, List[str], NoneType] = None, attrs: Optional[List[str]] = None, nopretty: bool = False, _progress: Optional[rich.progress.Progress] = None, _task: Optional[int] = None, **kw) -> None:
13def dprint(
14        msg: str,
15        leader: bool = True,
16        package: bool = True,
17        color: Optional[Union[str, List[str]]] = None,
18        attrs: Optional[List[str]] = None,
19        nopretty: bool = False,
20        _progress: Optional['rich.progress.Progress'] = None,
21        _task: Optional[int] = None,
22        **kw
23    ) -> None:
24    """Print a debug message."""
25    if attrs is None:
26        attrs = []
27    if not isinstance(color, bool) and not nopretty:
28        try:
29            from meerschaum.utils.formatting import CHARSET, ANSI, colored
30        except Exception as e:
31            CHARSET, ANSI, colored = 'ascii', False, None
32        from meerschaum.config._paths import CONFIG_DIR_PATH, PERMANENT_PATCH_DIR_PATH
33        from meerschaum.config import _config
34        cf = _config('formatting')
35        _color = color
36    else:
37        CHARSET, ANSI, colored, _color, cf = 'ascii', False, None, None, None
38
39    import logging, sys, inspect
40    logging.basicConfig(format='%(message)s')
41    log = logging.getLogger(__name__)
42
43    parent_frame = inspect.stack()[1][0]
44    parent_info = inspect.getframeinfo(parent_frame)
45    parent_lineno = parent_info.lineno
46    parent_globals = parent_frame.f_globals
47    parent_package = parent_globals['__name__']
48    msg = str(msg)
49    premsg = ""
50    if package:
51        premsg = parent_package + ':' + str(parent_lineno) + '\n'
52    if leader and cf is not None:
53        try:
54            debug_leader = cf['formatting']['debug'][CHARSET]['icon'] if cf is not None else ''
55        except KeyError:
56            print(
57                "Failed to load config. " +
58                "Please delete the following directories and restart Meerschaum:"
59            )
60            for p in [CONFIG_DIR_PATH, PERMANENT_PATCH_DIR_PATH]:
61                print('  - ' + str(p))
62            debug_leader = ''
63        premsg = ' ' + debug_leader + ' ' + premsg
64
65    if ANSI:
66        if _color is not None:
67            if isinstance(_color, str):
68                _color = [_color]
69        else:
70            if cf is not None and not nopretty:
71                try:
72                    _color = cf['formatting']['debug']['ansi']['rich'] if cf is not None else {}
73                except KeyError:
74                    _color = {}
75            else:
76                _color = {}
77        if colored is not None:
78            premsg = colored(premsg, **_color)
79    if _progress is not None:
80        from meerschaum.utils.packages import import_rich, attempt_import
81        rich = import_rich()
82        rich_text = attempt_import('rich.text')
83        text = rich_text.Text.from_ansi(premsg + msg)
84        _progress.console.log(text)
85    else:
86        print(premsg + msg)

Print a debug message.