.. warning:: In this section, no attention is given to providing support for translating the information shown to the user. Registering custom error types ============================== By default, ``friendly`` provides a set of traceback explanations for builtin exceptions. However, it is also possible to add explanation texts for custom error types. For that, two entry points are available. The first one is :func:`@friendly_traceback.info_generic.register ` decorator that registers a generic message that describes an error type. .. sidebar:: Generic and specific The **generic** explanation is what is shown as a result of typing ``what()`` in an interactive session. The **specific** explanation is what is shown as a result of typing ``why()`` in an interactive session. Example: .. code-block:: from friendly_traceback import info_generic @info_generic.register(CustomError) def describe(): return "CustomError is raised when ..." :func:`@friendly_traceback.info_generic.register ` will accept any parameterless function that returns a string. .. danger:: info_specific no longer exists with version 0.5.59 and above. the following will need to be rewritten for the newer version. Example: .. code-block:: from friendly_traceback import info_specific @info_specific.register(CustomError) def describe(error, frame, traceback_data): return { "cause": ( f"The particular custom error was {error}, " f"raised in file {traceback_data.filename!r} " f"on line {traceback_data.program_stopped_bad_line!r}." ) } Notice that a function accepted by :func:`@friendly_traceback.info_specific.register ` gets three input arguments: the raised exception instance, the current frame and the traceback data object generated by ``friendly`` that provides great help for eventual introspection. The function should return not a string this time, but a dictionary with the custom description stored under ``"cause"``. The dictionary may also contain a ``"suggest"`` key: this is most often used to show a simple suggestion for avoiding or mitigating the error as part of the friendly traceback (``friendly_tb()``). Example ------- Imagine we have a container ``api`` with a web service running behind the address ``https://my-services/api``. The following snippet requests data from the service: .. code-block:: import requests def fetch_data(): response = requests.get("https://my-services/api") response.raise_for_status() return response.json() fetch_data() When the connection can not be established, a ``requests.ConnectionError`` will be raised. Fortunately, since it inherits from ``OSError``, ``friendly`` is **already** able to provide a default explanation about the error cause **in this particular case**: .. code-block:: none An exception of type ``ConnectionError`` is a subclass of ``OSError``. An ``OSError`` exception is usually raised by the Operating System to indicate that an operation is not allowed or that a resource is not available. I suspect that you are trying to connect to a server and that a connection cannot be made. If that is the case, check for typos in the URL and check your internet connectivity. What if we want to add a more detailed information? First, we can register a custom generic description for any occurences of the ``requests.ConnectionError``: .. code-block:: from friendly_traceback import info_generic @info_generic.register(requests.ConnectionError) return ( "A `ConnectionError` from the `requests` package\n" "usually indicates that a service cannot be reached\n" "because it is offline.\n" ) Now ``friendly`` will print .. code-block:: none A `ConnectionError` from the `requests` package usually indicates that a service cannot be reached because it is offline. I suspect that you are trying to connect to a server and that a connection cannot be made. If that is the case, check for typos in the URL and check your internet connectivity. Second, we register a custom hook that generates a specific description for the particular error: .. code-block:: from friendly_traceback import info_specific answer_to_why = """ First, check whether the container is running: $ docker container inspect -f '{{.State.Running}}' api If necessary, restart with $ docker restart api """ @info_specific.register(requests.ConnectionError) def describe(error, frame, traceback_data): hint_added = ( f"The {error.request.method} request " f"for `{error.request.url}` has failed.\n" ) return {"cause": answer_to_why, "suggest": hint_added} This results in the following customized ``friendly`` output: .. code-block:: Traceback (most recent call last): [Very long traceback omitted] The GET request for `https://my-services/api` has failed. A `ConnectionError` from the `requests` package usually indicates that a service cannot be reached because it is offline. First, check whether the container is running: $ docker container inspect -f '{{.State.Running}}' api If necessary, restart with $ docker restart api Execution stopped on line 38 of file example.py. 33: response = requests.get("https://my-services/api") 34: response.raise_for_status() 35: return response.json() -->38: fetch_data() fetch_data: Exception raised on line 516 of file LOCAL:\requests\adapters.py. 510: raise ProxyError(e, request=request) 512: if isinstance(e.reason, _SSLError): 513: # This branch is for urllib3 v1.22 and later. 514: raise SSLError(e, request=request) -->516: raise ConnectionError(e, request=request) 518: except ClosedPoolError as e: request: global ConnectionError: