Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_better_exceptions.py574 linesDownload Raw Back to loguru
1import builtins2import inspect3import io4import keyword5import linecache6import os7import re8import sys9import sysconfig10import tokenize11import traceback12 13if sys.version_info >= (3, 11):14 15    def is_exception_group(exc):16        return isinstance(exc, ExceptionGroup)17 18else:19    try:20        from exceptiongroup import ExceptionGroup21    except ImportError:22 23        def is_exception_group(exc):24            return False25 26    else:27 28        def is_exception_group(exc):29            return isinstance(exc, ExceptionGroup)30 31 32class SyntaxHighlighter:33    _default_style = frozenset(34        {35            "comment": "\x1b[30m\x1b[1m{}\x1b[0m",36            "keyword": "\x1b[35m\x1b[1m{}\x1b[0m",37            "builtin": "\x1b[1m{}\x1b[0m",38            "string": "\x1b[36m{}\x1b[0m",39            "number": "\x1b[34m\x1b[1m{}\x1b[0m",40            "operator": "\x1b[35m\x1b[1m{}\x1b[0m",41            "punctuation": "\x1b[1m{}\x1b[0m",42            "constant": "\x1b[36m\x1b[1m{}\x1b[0m",43            "identifier": "\x1b[1m{}\x1b[0m",44            "other": "{}",45        }.items()46    )47 48    _builtins = frozenset(dir(builtins))49    _constants = frozenset({"True", "False", "None"})50    _punctuation = frozenset({"(", ")", "[", "]", "{", "}", ":", ",", ";"})51 52    if sys.version_info >= (3, 12):53        _strings = frozenset(54            {tokenize.STRING, tokenize.FSTRING_START, tokenize.FSTRING_MIDDLE, tokenize.FSTRING_END}55        )56        _fstring_middle = tokenize.FSTRING_MIDDLE57    else:58        _strings = frozenset({tokenize.STRING})59        _fstring_middle = None60 61    def __init__(self, style=None):62        self._style = style or dict(self._default_style)63 64    def highlight(self, source):65        style = self._style66        row, column = 0, 067        output = ""68 69        for token in self.tokenize(source):70            type_, string, (start_row, start_column), (_, end_column), line = token71 72            if type_ == self._fstring_middle:73                # When an f-string contains "{{" or "}}", they appear as "{" or "}" in the "string"74                # attribute of the token. However, they do not count in the column position.75                end_column += string.count("{") + string.count("}")76 77            if type_ == tokenize.NAME:78                if string in self._constants:79                    color = style["constant"]80                elif keyword.iskeyword(string):81                    color = style["keyword"]82                elif string in self._builtins:83                    color = style["builtin"]84                else:85                    color = style["identifier"]86            elif type_ == tokenize.OP:87                if string in self._punctuation:88                    color = style["punctuation"]89                else:90                    color = style["operator"]91            elif type_ == tokenize.NUMBER:92                color = style["number"]93            elif type_ in self._strings:94                color = style["string"]95            elif type_ == tokenize.COMMENT:96                color = style["comment"]97            else:98                color = style["other"]99 100            if start_row != row:101                source = source[column:]102                row, column = start_row, 0103 104            if type_ != tokenize.ENCODING:105                output += line[column:start_column]106                output += color.format(line[start_column:end_column])107 108            column = end_column109 110        output += source[column:]111 112        return output113 114    @staticmethod115    def tokenize(source):116        # Worth reading: https://www.asmeurer.com/brown-water-python/117        source = source.encode("utf-8")118        source = io.BytesIO(source)119 120        try:121            yield from tokenize.tokenize(source.readline)122        except tokenize.TokenError:123            return124 125 126class ExceptionFormatter:127    _default_theme = frozenset(128        {129            "introduction": "\x1b[33m\x1b[1m{}\x1b[0m",130            "cause": "\x1b[1m{}\x1b[0m",131            "context": "\x1b[1m{}\x1b[0m",132            "dirname": "\x1b[32m{}\x1b[0m",133            "basename": "\x1b[32m\x1b[1m{}\x1b[0m",134            "line": "\x1b[33m{}\x1b[0m",135            "function": "\x1b[35m{}\x1b[0m",136            "exception_type": "\x1b[31m\x1b[1m{}\x1b[0m",137            "exception_value": "\x1b[1m{}\x1b[0m",138            "arrows": "\x1b[36m{}\x1b[0m",139            "value": "\x1b[36m\x1b[1m{}\x1b[0m",140        }.items()141    )142 143    def __init__(144        self,145        colorize=False,146        backtrace=False,147        diagnose=True,148        theme=None,149        style=None,150        max_length=128,151        encoding="ascii",152        hidden_frames_filename=None,153        prefix="",154    ):155        self._colorize = colorize156        self._diagnose = diagnose157        self._theme = theme or dict(self._default_theme)158        self._backtrace = backtrace159        self._syntax_highlighter = SyntaxHighlighter(style)160        self._max_length = max_length161        self._encoding = encoding162        self._hidden_frames_filename = hidden_frames_filename163        self._prefix = prefix164        self._lib_dirs = self._get_lib_dirs()165        self._pipe_char = self._get_char("\u2502", "|")166        self._cap_char = self._get_char("\u2514", "->")167        self._catch_point_identifier = " <Loguru catch point here>"168 169    @staticmethod170    def _get_lib_dirs():171        schemes = sysconfig.get_scheme_names()172        names = ["stdlib", "platstdlib", "platlib", "purelib"]173        paths = {sysconfig.get_path(name, scheme) for scheme in schemes for name in names}174        return [os.path.abspath(path).lower() + os.sep for path in paths if path in sys.path]175 176    @staticmethod177    def _indent(text, count, *, prefix="| "):178        if count == 0:179            yield text180            return181        for line in text.splitlines(True):182            indented = "  " * count + prefix + line183            yield indented.rstrip() + "\n"184 185    def _get_char(self, char, default):186        try:187            char.encode(self._encoding)188        except (UnicodeEncodeError, LookupError):189            return default190        else:191            return char192 193    def _is_file_mine(self, file):194        filepath = os.path.abspath(file).lower()195        if not filepath.endswith(".py"):196            return False197        return not any(filepath.startswith(d) for d in self._lib_dirs)198 199    def _extract_frames(self, tb, is_first, *, limit=None, from_decorator=False):200        frames, final_source = [], None201 202        if tb is None or (limit is not None and limit <= 0):203            return frames, final_source204 205        def is_valid(frame):206            return frame.f_code.co_filename != self._hidden_frames_filename207 208        def get_info(frame, lineno):209            filename = frame.f_code.co_filename210            function = frame.f_code.co_name211            source = linecache.getline(filename, lineno).strip()212            return filename, lineno, function, source213 214        infos = []215 216        if is_valid(tb.tb_frame):217            infos.append((get_info(tb.tb_frame, tb.tb_lineno), tb.tb_frame))218 219        get_parent_only = from_decorator and not self._backtrace220 221        if (self._backtrace and is_first) or get_parent_only:222            frame = tb.tb_frame.f_back223            while frame:224                if is_valid(frame):225                    infos.insert(0, (get_info(frame, frame.f_lineno), frame))226                    if get_parent_only:227                        break228                frame = frame.f_back229 230            if infos and not get_parent_only:231                (filename, lineno, function, source), frame = infos[-1]232                function += self._catch_point_identifier233                infos[-1] = ((filename, lineno, function, source), frame)234 235        tb = tb.tb_next236 237        while tb:238            if is_valid(tb.tb_frame):239                infos.append((get_info(tb.tb_frame, tb.tb_lineno), tb.tb_frame))240            tb = tb.tb_next241 242        if limit is not None:243            infos = infos[-limit:]244 245        for (filename, lineno, function, source), frame in infos:246            final_source = source247            if source:248                colorize = self._colorize and self._is_file_mine(filename)249                lines = []250                if colorize:251                    lines.append(self._syntax_highlighter.highlight(source))252                else:253                    lines.append(source)254                if self._diagnose:255                    relevant_values = self._get_relevant_values(source, frame)256                    values = self._format_relevant_values(list(relevant_values), colorize)257                    lines += list(values)258                source = "\n    ".join(lines)259            frames.append((filename, lineno, function, source))260 261        return frames, final_source262 263    def _get_relevant_values(self, source, frame):264        value = None265        pending = None266        is_attribute = False267        is_valid_value = False268        is_assignment = True269 270        for token in self._syntax_highlighter.tokenize(source):271            type_, string, (_, col), *_ = token272 273            if pending is not None:274                # Keyword arguments are ignored275                if type_ != tokenize.OP or string != "=" or is_assignment:276                    yield pending277                pending = None278 279            if type_ == tokenize.NAME and not keyword.iskeyword(string):280                if not is_attribute:281                    for variables in (frame.f_locals, frame.f_globals):282                        try:283                            value = variables[string]284                        except KeyError:285                            continue286                        else:287                            is_valid_value = True288                            pending = (col, self._format_value(value))289                            break290                elif is_valid_value:291                    try:292                        value = inspect.getattr_static(value, string)293                    except AttributeError:294                        is_valid_value = False295                    else:296                        yield (col, self._format_value(value))297            elif type_ == tokenize.OP and string == ".":298                is_attribute = True299                is_assignment = False300            elif type_ == tokenize.OP and string == ";":301                is_assignment = True302                is_attribute = False303                is_valid_value = False304            else:305                is_attribute = False306                is_valid_value = False307                is_assignment = False308 309        if pending is not None:310            yield pending311 312    def _format_relevant_values(self, relevant_values, colorize):313        for i in reversed(range(len(relevant_values))):314            col, value = relevant_values[i]315            pipe_cols = [pcol for pcol, _ in relevant_values[:i]]316            pre_line = ""317            index = 0318 319            for pc in pipe_cols:320                pre_line += (" " * (pc - index)) + self._pipe_char321                index = pc + 1322 323            pre_line += " " * (col - index)324            value_lines = value.split("\n")325 326            for n, value_line in enumerate(value_lines):327                if n == 0:328                    arrows = pre_line + self._cap_char + " "329                else:330                    arrows = pre_line + " " * (len(self._cap_char) + 1)331 332                if colorize:333                    arrows = self._theme["arrows"].format(arrows)334                    value_line = self._theme["value"].format(value_line)335 336                yield arrows + value_line337 338    def _format_value(self, v):339        try:340            v = repr(v)341        except Exception:342            v = "<unprintable %s object>" % type(v).__name__343 344        max_length = self._max_length345        if max_length is not None and len(v) > max_length:346            v = v[: max_length - 3] + "..."347        return v348 349    def _format_locations(self, frames_lines, *, has_introduction):350        prepend_with_new_line = has_introduction351        regex = r'^  File "(?P<file>.*?)", line (?P<line>[^,]+)(?:, in (?P<function>.*))?\n'352 353        for frame in frames_lines:354            match = re.match(regex, frame)355 356            if match:357                file, line, function = match.group("file", "line", "function")358 359                is_mine = self._is_file_mine(file)360 361                if function is not None:362                    pattern = '  File "{}", line {}, in {}\n'363                else:364                    pattern = '  File "{}", line {}\n'365 366                if self._backtrace and function and function.endswith(self._catch_point_identifier):367                    function = function[: -len(self._catch_point_identifier)]368                    pattern = ">" + pattern[1:]369 370                if self._colorize and is_mine:371                    dirname, basename = os.path.split(file)372                    if dirname:373                        dirname += os.sep374                    dirname = self._theme["dirname"].format(dirname)375                    basename = self._theme["basename"].format(basename)376                    file = dirname + basename377                    line = self._theme["line"].format(line)378                    function = self._theme["function"].format(function)379 380                if self._diagnose and (is_mine or prepend_with_new_line):381                    pattern = "\n" + pattern382 383                location = pattern.format(file, line, function)384                frame = location + frame[match.end() :]385                prepend_with_new_line = is_mine386 387            yield frame388 389    def _format_exception(390        self, value, tb, *, seen=None, is_first=False, from_decorator=False, group_nesting=0391    ):392        # Implemented from built-in traceback module:393        # https://github.com/python/cpython/blob/a5b76167/Lib/traceback.py#L468394        exc_type, exc_value, exc_traceback = type(value), value, tb395 396        if seen is None:397            seen = set()398 399        seen.add(id(exc_value))400 401        if exc_value:402            if exc_value.__cause__ is not None and id(exc_value.__cause__) not in seen:403                yield from self._format_exception(404                    exc_value.__cause__,405                    exc_value.__cause__.__traceback__,406                    seen=seen,407                    group_nesting=group_nesting,408                )409                cause = "The above exception was the direct cause of the following exception:"410                if self._colorize:411                    cause = self._theme["cause"].format(cause)412                if self._diagnose:413                    yield from self._indent("\n\n" + cause + "\n\n\n", group_nesting)414                else:415                    yield from self._indent("\n" + cause + "\n\n", group_nesting)416 417            elif (418                exc_value.__context__ is not None419                and id(exc_value.__context__) not in seen420                and not exc_value.__suppress_context__421            ):422                yield from self._format_exception(423                    exc_value.__context__,424                    exc_value.__context__.__traceback__,425                    seen=seen,426                    group_nesting=group_nesting,427                )428                context = "During handling of the above exception, another exception occurred:"429                if self._colorize:430                    context = self._theme["context"].format(context)431                if self._diagnose:432                    yield from self._indent("\n\n" + context + "\n\n\n", group_nesting)433                else:434                    yield from self._indent("\n" + context + "\n\n", group_nesting)435 436        is_grouped = is_exception_group(value)437 438        if is_grouped and group_nesting == 0:439            yield from self._format_exception(440                value,441                tb,442                seen=seen,443                group_nesting=1,444                is_first=is_first,445                from_decorator=from_decorator,446            )447            return448 449        try:450            traceback_limit = sys.tracebacklimit451        except AttributeError:452            traceback_limit = None453 454        frames, final_source = self._extract_frames(455            exc_traceback, is_first, limit=traceback_limit, from_decorator=from_decorator456        )457        exception_only = traceback.format_exception_only(exc_type, exc_value)458 459        # Determining the correct index for the "Exception: message" part in the formatted exception460        # is challenging. This is because it might be preceded by multiple lines specific to461        # "SyntaxError" or followed by various notes. However, we can make an educated guess based462        # on the indentation; the preliminary context for "SyntaxError" is always indented, while463        # the Exception itself is not. This allows us to identify the correct index for the464        # exception message.465        no_indented_indexes = (i for i, p in enumerate(exception_only) if not p.startswith(" "))466        error_message_index = next(no_indented_indexes, None)467 468        if error_message_index is not None:469            # Remove final new line temporarily.470            error_message = exception_only[error_message_index][:-1]471 472            if self._colorize:473                if ":" in error_message:474                    exception_type, exception_value = error_message.split(":", 1)475                    exception_type = self._theme["exception_type"].format(exception_type)476                    exception_value = self._theme["exception_value"].format(exception_value)477                    error_message = exception_type + ":" + exception_value478                else:479                    error_message = self._theme["exception_type"].format(error_message)480 481            if self._diagnose and frames:482                if issubclass(exc_type, AssertionError) and not str(exc_value) and final_source:483                    if self._colorize:484                        final_source = self._syntax_highlighter.highlight(final_source)485                    error_message += ": " + final_source486 487                error_message = "\n" + error_message488 489            exception_only[error_message_index] = error_message + "\n"490 491        if is_first:492            yield self._prefix493 494        has_introduction = bool(frames)495 496        if has_introduction:497            if is_grouped:498                introduction = "Exception Group Traceback (most recent call last):"499            else:500                introduction = "Traceback (most recent call last):"501            if self._colorize:502                introduction = self._theme["introduction"].format(introduction)503            if group_nesting == 1:  # Implies we're processing the root ExceptionGroup.504                yield from self._indent(introduction + "\n", group_nesting, prefix="+ ")505            else:506                yield from self._indent(introduction + "\n", group_nesting)507 508        frames_lines = self._format_list(frames) + exception_only509        if self._colorize or self._backtrace or self._diagnose:510            frames_lines = self._format_locations(frames_lines, has_introduction=has_introduction)511 512        yield from self._indent("".join(frames_lines), group_nesting)513 514        if is_grouped:515            exc = None516            for n, exc in enumerate(value.exceptions, start=1):517                ruler = "+" + (" %s " % ("..." if n > 15 else n)).center(35, "-")518                yield from self._indent(ruler, group_nesting, prefix="+-" if n == 1 else "  ")519                if n > 15:520                    message = "and %d more exceptions\n" % (len(value.exceptions) - 15)521                    yield from self._indent(message, group_nesting + 1)522                    break523                elif group_nesting == 10 and is_exception_group(exc):524                    message = "... (max_group_depth is 10)\n"525                    yield from self._indent(message, group_nesting + 1)526                else:527                    yield from self._format_exception(528                        exc,529                        exc.__traceback__,530                        seen=seen,531                        group_nesting=group_nesting + 1,532                    )533            if not is_exception_group(exc) or group_nesting == 10:534                yield from self._indent("-" * 35, group_nesting + 1, prefix="+-")535 536    def _format_list(self, frames):537 538        def source_message(filename, lineno, name, line):539            message = '  File "%s", line %d, in %s\n' % (filename, lineno, name)540            if line:541                message += "    %s\n" % line.strip()542            return message543 544        def skip_message(count):545            plural = "s" if count > 1 else ""546            return "  [Previous line repeated %d more time%s]\n" % (count, plural)547 548        result = []549        count = 0550        last_source = None551 552        for *source, line in frames:553            if source != last_source and count > 3:554                result.append(skip_message(count - 3))555 556            if source == last_source:557                count += 1558                if count > 3:559                    continue560            else:561                count = 1562 563            result.append(source_message(*source, line))564            last_source = source565 566        # Add a final skip message if the iteration of frames ended mid-repetition.567        if count > 3:568            result.append(skip_message(count - 3))569 570        return result571 572    def format_exception(self, type_, value, tb, *, from_decorator=False):573        yield from self._format_exception(value, tb, is_first=True, from_decorator=from_decorator)574 
codekingpro/portable-devtools · Team Ai