Пользовательские исключения — перехватывающие классы, которые не наследуются от BaseException

#python #exception #python-requests #web3

Вопрос:

Я пытаюсь написать некоторую пользовательскую обработку исключений, но продолжаю сталкиваться с проблемами с ошибками «Ошибка типа: перехват классов, которые не наследуются от BaseException, запрещен». У меня есть базовый класс исключений под названием NodeError, который наследуется от Exception. Оттуда у меня есть несколько пользовательских исключений, которые наследуются от NodeError.

Модуль web3 использует модуль запросов для связи с узлом. Мой тест постоянно пытается получить количество tx от узла, и пока он это делает, я пытаюсь имитировать отключение, отключив свою сетевую карту. Я пытаюсь перехватывать запросы.исключения.Ошибка подключения в get_tx_count() и создаю свое собственное исключение. Кажется, что он правильно попал в пользовательское исключение NodeConnectionError на основе трассировки стека, но затем получает другое исключение и жалуется на перехват классов, которые не наследуются от BaseException.

Не знаю, почему он думает, что я не наследую исключение BaseException, но у меня такое чувство, что это связано с тем, чтобы сначала перехватить исключение запросов.

Трассировка стека:

 Traceback (most recent call last):
  File "C:Python39libsite-packagesurllib3connection.py", line 169, in _new_conn
    conn = connection.create_connection(
  File "C:Python39libsite-packagesurllib3utilconnection.py", line 73, in create_connection
    for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
  File "C:Python39libsocket.py", line 953, in getaddrinfo
    for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno 11001] getaddrinfo failed

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:Python39libsite-packagesurllib3connectionpool.py", line 699, in urlopen
    httplib_response = self._make_request(
  File "C:Python39libsite-packagesurllib3connectionpool.py", line 382, in _make_request
    self._validate_conn(conn)
  File "C:Python39libsite-packagesurllib3connectionpool.py", line 1010, in _validate_conn
    conn.connect()
  File "C:Python39libsite-packagesurllib3connection.py", line 353, in connect
    conn = self._new_conn()
  File "C:Python39libsite-packagesurllib3connection.py", line 181, in _new_conn
    raise NewConnectionError(
urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPSConnection object at 0x000001413971AB50>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:Python39libsite-packagesrequestsadapters.py", line 439, in send
    resp = conn.urlopen(
  File "C:Python39libsite-packagesurllib3connectionpool.py", line 755, in urlopen
    retries = retries.increment(
  File "C:Python39libsite-packagesurllib3utilretry.py", line 574, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='matic-mainnet-full-rpc.bwarelabs.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x000001413971AB50>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:textest.py", line 87, in get_tx_count
    nonce = w3.eth.get_transaction_count(address)
  File "C:Python39libsite-packagesweb3module.py", line 57, in caller
    result = w3.manager.request_blocking(method_str,
  File "C:Python39libsite-packagesweb3manager.py", line 186, in request_blocking
    response = self._make_request(method, params)
  File "C:Python39libsite-packagesweb3manager.py", line 147, in _make_request
    return request_func(method, params)
  File "cytoolz/functoolz.pyx", line 250, in cytoolz.functoolz.curry.__call__
    return self.func(*args, **kwargs)
  File "C:Python39libsite-packagesweb3middlewareformatting.py", line 76, in apply_formatters
    response = make_request(method, params)
  File "C:Python39libsite-packagesweb3middlewaregas_price_strategy.py", line 84, in middleware
    return make_request(method, params)
  File "cytoolz/functoolz.pyx", line 250, in cytoolz.functoolz.curry.__call__
    return self.func(*args, **kwargs)
  File "C:Python39libsite-packagesweb3middlewareformatting.py", line 74, in apply_formatters
    response = make_request(method, formatted_params)
  File "C:Python39libsite-packagesweb3middlewareattrdict.py", line 33, in middleware
    response = make_request(method, params)
  File "cytoolz/functoolz.pyx", line 250, in cytoolz.functoolz.curry.__call__
    return self.func(*args, **kwargs)
  File "C:Python39libsite-packagesweb3middlewareformatting.py", line 74, in apply_formatters
    response = make_request(method, formatted_params)
  File "cytoolz/functoolz.pyx", line 250, in cytoolz.functoolz.curry.__call__
    return self.func(*args, **kwargs)
  File "C:Python39libsite-packagesweb3middlewareformatting.py", line 76, in apply_formatters
    response = make_request(method, params)
  File "cytoolz/functoolz.pyx", line 250, in cytoolz.functoolz.curry.__call__
    return self.func(*args, **kwargs)
  File "C:Python39libsite-packagesweb3middlewareformatting.py", line 74, in apply_formatters
    response = make_request(method, formatted_params)
  File "C:Python39libsite-packagesweb3middlewarebuffered_gas_estimate.py", line 40, in middleware
    return make_request(method, params)
  File "C:Python39libsite-packagesweb3middlewareexception_retry_request.py", line 105, in middleware
    return make_request(method, params)
  File "C:Python39libsite-packagesweb3providersrpc.py", line 88, in make_request
    raw_response = make_post_request(
  File "C:Python39libsite-packagesweb3_utilsrequest.py", line 48, in make_post_request
    response = session.post(endpoint_uri, data=data, *args, **kwargs)  # type: ignore
  File "C:Python39libsite-packagesrequestssessions.py", line 590, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
  File "C:Python39libsite-packagesrequestssessions.py", line 542, in request
    resp = self.send(prep, **send_kwargs)
  File "C:Python39libsite-packagesrequestssessions.py", line 655, in send
    r = adapter.send(request, **kwargs)
  File "C:Python39libsite-packagesrequestsadapters.py", line 516, in send
    raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='matic-mainnet-full-rpc.bwarelabs.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x000001413971AB50>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed'))

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:textest.py", line 114, in main
    print(get_tx_count(w3, address))
  File "C:textest.py", line 90, in get_tx_count
    raise NodeConnectionError(w3) from e
__main__.NodeConnectionError: An error occurred with the node at A web3 connection error occurred talking to https://matic-mainnet-full-rpc.bwarelabs.com:443..

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:textest.py", line 140, in <module>
    main()
  File "C:textest.py", line 116, in main
    except NodeNotConnected(w3.provider.endpoint_uri, w3):
TypeError: catching classes that do not inherit from BaseException is not allowed
 

Тестовый код:

 from web3 import Web3
from time import sleep
from requests.exceptions import ConnectionError, ConnectTimeout, HTTPError
import sys

class NodeError(Exception):
    """Base exception for node errors"""
    def __init__(self, url, msg=None):
        if msg is None:
            msg = f"An error occurred with the node at {url}."
        super().__init__(msg)
        self.url = url

class NodeNotConnected(NodeError):
    """web3 could not connect to a node"""
    def __init__(self, url, w3=None):
        msg = f"A web3 connection could not be made to URL {url}."
        super().__init__(url, msg=msg)
        self.w3 = w3

class NodeConnectionError(NodeError):
    """A web3 error occurred communicating with a node"""
    def __init__(self, w3):
        msg = (
            f"A web3 connection error occurred talking to "
            f"{w3.provider.endpoint_uri}."
        )
        super().__init__(msg)
        self.w3 = w3

class NodeTooManyRequests(NodeError):
    def __init__(self, w3):
        msg = (
            f"Too many requests made to {w3.provider.endpoint_uri}.  Try a "
            f"different node."
        )
        super().__init__(msg)
        self.w3 = w3

class NodeNoAvailableNodes(NodeError):
    def __init__(self):
        msg = f"Unable to connect to any nodes."
        super().__init__(msg)

class NodeInternalError(NodeError):
    def __init__(self):
        msg = "The node had an internal error."
        super().__init__(msg)

def lib_connect_to_node(url):
    """Emulates library connect to node function"""
    try:
        w3 = Web3(
            Web3.HTTPProvider(
                url,
                request_kwargs={"timeout": 5}
            )
        )
        if not w3.isConnected():
            raise NodeNotConnected(url, w3)
    except Exception as e:
        raise NodeNotConnected(url) from e
    else:
        return w3

def connect_to_node(urls, node_retries):
    while node_retries >= 0:
        try:
            w3 = lib_connect_to_node(urls[0])
        except NodeNotConnected(urls[0]) as e:
            if node_retries == 0:
                raise NodeNoAvailableNodes from e
            node_retries -= 1
            urls = get_next_node(urls)
            print('Trying another node')
            sleep(1)
            continue
        else:
            return w3

def get_next_node(urls):
    urls.append(urls.pop(urls.index(urls[0])))
    return urls

def get_tx_count(w3, address):
    try:
        nonce = w3.eth.get_transaction_count(address)
    except (ConnectionError, ConnectTimeout) as e:
        print("A requests.exceptions.ConnectionError occurred.")
        raise NodeConnectionError(w3) from e
    except HTTPError as e:
        if e.code == 429:
            raise NodeTooManyRequests from e
    else:
        return nonce

def main():
    urls = [
        'https://matic-mainnet-full-rpc.bwarelabs.com:443',
        'https://matic-mainnet.chainstacklabs.com:443',
        'https://rpc-mainnet.maticvigil.com:443',
        'https://rpc-mainnet.matic.network:443'
    ]
    node_retries = 3
    address = '0xe18A0D121057B002BaFb90aD5F1AB951594A61E8'
    try:
        w3 = connect_to_node(urls, node_retries)
    except NodeNoAvailableNodes as e:
        print(e)
        sys.exit()

    while True:
        try:
            print(get_tx_count(w3, address))
            sleep(0.05)
        except NodeNotConnected(w3.provider.endpoint_uri, w3):
            urls = get_next_node(urls)
            try:
                w3 = connect_to_node(urls, node_retries)
            except NodeNoAvailableNodes as e:
                print(e)
                sys.exit()
        except NodeConnectionError(w3) as e:
            urls = get_next_node(urls)
            try:
                w3 = connect_to_node(urls, node_retries)
            except NodeNoAvailableNodes as e:
                print(e)
                sys.exit() 
        except NodeTooManyRequests(w3) as e:
            print('Too many requests')
            urls = get_next_node(urls)
            try:
                w3 = connect_to_node(urls, node_retries)
            except NodeNoAvailableNodes as e:
                print(e)
                sys.exit()

if __name__ == '__main__':
    main()
 

Комментарии:

1. синтаксис таков except NodeTooManyRequests , а не except NodeTooManyRequests(...) так . Вам нужен класс там, а не экземпляр этого класса

Ответ №1:

Вы должны быть в состоянии перехватить все, что является исключением, даже если оно напрямую не наследуется от исключения BaseException.

Однако удалите параметр класса из except предложения и укажите только имя класса:

 try:
    raise NodeNotConnected('abc')
except NodeNotConnected as e:
    print(e)
 

Результат:

 A web3 connection could not be made to URL abc.
 

При указании параметров класса создается экземпляр класса, который вычисляется как экземпляр, а не как тип.

Комментарии:

1. Сообщение об ошибке довольно вводит в заблуждение. Я нахожу весьма удивительным, что нечто подобное try: 1/0; except 42 as e: pass производит точно такой же результат. Почему в сообщении об ошибке просто не указано, что параметр не является классом ?

2. Это сработало. Я неправильно думал об исключении. Повышение-это то, где я хотел передать любые аргументы, исключение-это просто перехват исключения. Если я хочу использовать какой-либо из аргументов, я просто добавляю часть «как e», и я могу использовать там все, что угодно. Спасибо!

3. @ekhumoro сообщение об ошибке не вводит в заблуждение ИМО, но когда используется параметр, все ( NodeNotConnected('abc') ) является экземпляром класса, а не самим классом

4. Да, это явно вводит в заблуждение, потому что ошибка типа должна указывать, что параметр не является классом , как я уже говорил ранее.

5. Это тоже было бы неплохо