Warning

Most of the documentation was written prior to version 0.5 and needs to be updated. This work has now started for version 0.7 and we aim to have it completed before version 0.8 is available.

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 @friendly_traceback.info_generic.register decorator that registers a generic message that describes an error type.

Example:

from friendly_traceback import info_generic

@info_generic.register(CustomError)
def describe():
    return "CustomError is raised when ..."

@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:

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 @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:

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:

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:

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

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:

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:

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:  <function 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:  <PreparedRequest [GET]>
        global ConnectionError:  <class requests.exceptions.ConnectionError>