Source code for friendly_traceback.message_parser

"""This module contains the necessary class and functions needed to
help finding the cause (specific information aka answer to ``why()``)
of an exception. Most of the content should be considered to be private.

It does contain one function (``get_parser``)
which is intended to be part of the public API, but needs to be
imported from this module instead of simply from ``friendly_traceback``.
"""


from importlib import import_module
from typing import List, Type, TypeVar

from . import debug_helper
from .ft_gettext import internal_error, no_information, unknown_case
from .tb_data import TracebackData  # for type checking only
from .typing_info import _E, CauseInfo, Parser

_P = TypeVar("_P", bound=Parser)

INCLUDED_PARSERS = {
    AttributeError: "attribute_error",
    FileNotFoundError: "file_not_found_error",
    ImportError: "import_error",
    IndexError: "index_error",
    KeyError: "key_error",
    ModuleNotFoundError: "module_not_found_error",
    NameError: "name_error",
    OSError: "os_error",
    RuntimeError: "runtime_error",
    TypeError: "type_error",
    UnboundLocalError: "unbound_local_error",
    ValueError: "value_error",
    ZeroDivisionError: "zero_division_error",
}
RUNTIME_MESSAGE_PARSERS = {}


class RuntimeMessageParser:
    """This class is used to create objects that collect message parsers."""

    def __init__(self) -> None:
        self.parsers: List[Parser] = []
        self.core_parsers: List[Parser] = []
        self.custom_parsers: List[Parser] = []

    def _add(self, func: _P) -> _P:
        """This method is meant to be used only within friendly-traceback.
        It is used as a decorator to add a message parser to a list that is
        automatically updated.
        """
        self.parsers.append(func)
        self.core_parsers.append(func)
        return func

    def add(self, func: _P) -> _P:
        """This method is meant to be used by projects that extend
        friendly-traceback. It is used as a decorator to add a message parser
        to a list that is automatically updated::

            @instance.add
            def some_message_parser(message, traceback_data):
                ....
        """
        self.custom_parsers.append(func)
        self.parsers = self.custom_parsers + self.core_parsers
        return func


[docs]def get_parser(exception_type: Type[_E]) -> RuntimeMessageParser: """Gets a 'parser' to find the cause for a given exception. Args: exception_type: an exception class. Usage:: parser = get_parser(SomeSpecificError) @parser.add def some_meaningful_name(message: str, tb_data: TracebackData) -> dict: if not handled_by_this_function(message): return {} # let other parsers deal with it ... """ if exception_type not in RUNTIME_MESSAGE_PARSERS: RUNTIME_MESSAGE_PARSERS[exception_type] = RuntimeMessageParser() if exception_type in INCLUDED_PARSERS: base_path = "friendly_traceback.runtime_errors." import_module(base_path + INCLUDED_PARSERS[exception_type]) return RUNTIME_MESSAGE_PARSERS[exception_type]
def get_likely_cause( exception_type, message: str, tb_data: TracebackData, ) -> CauseInfo: """Attempts to get the likely cause of an exception.""" try: return get_cause(exception_type, message, tb_data) except Exception as e: # noqa # pragma: no cover debug_helper.log(message) return {"cause": internal_error(e), "suggest": internal_error(e)} def get_cause( exception_type, message: str, tb_data: TracebackData, ) -> CauseInfo: """For a given exception type, cycle through the known message parsers, looking for one that can find a cause of the exception.""" message_parser = get_parser(exception_type) for parser in message_parser.parsers: # This could be simpler if we could use the walrus operator cause = parser(message, tb_data) if cause: return cause # Special case where a connection attempt failed when using # socket, or urllib, urllib3, etc. try: if issubclass(exception_type, OSError): os_error_parser = get_parser(OSError) for parser in os_error_parser.parsers: cause = parser(message, tb_data) if cause: return cause else: return {"cause": no_information(), "suggest": unknown_case()} except Exception: # noqa # pragma: no cover pass if not message_parser.parsers: return {} debug_helper.log(str(message)) return {"cause": no_information(), "suggest": unknown_case()}