Requests: exceções excedidas no máximo de repetições são confusas

Criado em 15 fev. 2013  ·  39Comentários  ·  Fonte: psf/requests

Oi,
por exemplo:

>>> requests.get('http://localhost:1111')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "requests/api.py", line 55, in get
    return request('get', url, **kwargs)
  File "requests/api.py", line 44, in request
    return session.request(method=method, url=url, **kwargs)
  File "requests/sessions.py", line 312, in request
    resp = self.send(prep, **send_kwargs)
  File "requests/sessions.py", line 413, in send
    r = adapter.send(request, **kwargs)
  File "requests/adapters.py", line 223, in send
    raise ConnectionError(e)
requests.exceptions.ConnectionError: HTTPConnectionPool(host='localhost', port=1111): Max retries exceeded with url: / (Caused by <class 'socket.error'>: [Errno 61] Connection refused)

(assumindo que nada está escutando na porta 1111)

a exceção diz "Número máximo de tentativas excedido". Achei isso confuso porque não especifiquei nenhum parâmetro relacionado a novas tentativas. na verdade, não consigo encontrar nenhuma documentação sobre como especificar a contagem de tentativas. depois de passar pelo código, parece que urllib3 é o transporte subjacente e é chamado com max_retries = 0 (então, na verdade, não há novas tentativas). e as solicitações simplesmente encerram a exceção. então é compreensível, mas confunde o usuário final (desenvolvedor final)? Acho que algo melhor deveria ser feito aqui, especialmente considerando que é muito fácil obter esse erro.

Feature Request

Comentários muito úteis

Eu concordo que isso é bastante confuso. As solicitações nunca são repetidas (define as tentativas = 0 para o HTTPConnectionPool do urllib3), então o erro seria muito mais óbvio sem o material HTTPConnectionPool / MaxRetryError. Não percebi que as solicitações usavam o urllib3 até agora, quando tive que mergulhar no código-fonte das duas bibliotecas para me ajudar a descobrir quantas tentativas ele estava fazendo:

ConnectionError(MaxRetryError("HTTPSConnectionPool(host='api.venere.com', port=443): \
    Max retries exceeded with url: /xhi-1.0/services/XHI_HotelAvail.json (\
    Caused by <class 'socket.error'>: [Errno 10054] \
    An existing connection was forcibly closed by the remote host)",),)

Idealmente, a exceção seria algo assim:

ConnectionError(<class 'socket.error'>: [Errno 10054] \
    An existing connection was forcibly closed by the remote host))

Todos 39 comentários

As solicitações envolvem a exceção para a conveniência do usuário. A exceção original é parte da mensagem, embora o Traceback seja enganoso. Vou pensar em como melhorar isso.

Acho que preciso de uma nova tentativa automática para ignorar alguns erros

Eu concordo que isso é bastante confuso. As solicitações nunca são repetidas (define as tentativas = 0 para o HTTPConnectionPool do urllib3), então o erro seria muito mais óbvio sem o material HTTPConnectionPool / MaxRetryError. Não percebi que as solicitações usavam o urllib3 até agora, quando tive que mergulhar no código-fonte das duas bibliotecas para me ajudar a descobrir quantas tentativas ele estava fazendo:

ConnectionError(MaxRetryError("HTTPSConnectionPool(host='api.venere.com', port=443): \
    Max retries exceeded with url: /xhi-1.0/services/XHI_HotelAvail.json (\
    Caused by <class 'socket.error'>: [Errno 10054] \
    An existing connection was forcibly closed by the remote host)",),)

Idealmente, a exceção seria algo assim:

ConnectionError(<class 'socket.error'>: [Errno 10054] \
    An existing connection was forcibly closed by the remote host))

Isso seria ideal. O problema é envolver essas exceções como fazemos. Eles são uma ótima API, mas uma experiência de depuração ruim. Eu tenho uma ideia de como consertar e preservar todas as informações

Também precisaríamos considerar o caso em que um usuário _faz_ configura novas tentativas, caso em que essa exceção é apropriada.

@Lukasa , não tenho certeza se você precisa considerar isso - Kenneth disse aqui que as solicitações explicitamente não devem oferecer suporte a novas tentativas como parte de sua API.

Certo, mas não há como impedir que um usuário realmente faça isso.

Meu plano, para que fique registrado, é ir o mais para baixo possível até a exceção de nível mais baixo e usá-la em seu lugar. O problema com o exemplo de @benhoyt é que parece que a exceção de erro de soquete não está disponível para nós. (Só de olhar o que ele colou. Ainda não tentei reproduzir e brincar com ele.)

O exemplo de @gabor realmente torna isso fácil de reproduzir. Capturando a exceção que foi gerada, fiz o seguinte:

>>> e
ConnectionError(MaxRetryError("HTTPConnectionPool(host='localhost', port=1111): Max retries exceeded with url: / (Caused by <class 'socket.error'>: [Errno 111] Connection refused)",),)
>>> e.args
(MaxRetryError("HTTPConnectionPool(host='localhost', port=1111): Max retries exceeded with url: / (Caused by <class 'socket.error'>: [Errno 111] Connection refused)",),)
>>> e.args[0].args
("HTTPConnectionPool(host='localhost', port=1111): Max retries exceeded with url: / (Caused by <class 'socket.error'>: [Errno 111] Connection refused)",)
>>> e.args[0].args[0]
"HTTPConnectionPool(host='localhost', port=1111): Max retries exceeded with url: / (Caused by <class 'socket.error'>: [Errno 111] Connection refused)"
>>> isinstance(e.args[0].args[0], str)
True

Portanto, o melhor que podemos fazer é usar apenas a mensagem armazenada em e.args[0].args[0] que também pode ser potencialmente confuso, mas provavelmente menos do que o que @benhoyt encontrou. De qualquer forma, não analisaremos as mensagens de erro para tentar obter mais ou menos detalhes porque isso seria apenas uma completa insanidade.

@ sigmavirus24 , concordo que analisar strings em exceções é uma ideia terrível. No entanto, MaxRetryError de urllib3 já expõe um atributo reason que contém a exceção subjacente (consulte o código-fonte ). Assim, você pode obter o que deseja com e.args[0].reason .

Continuando com o exemplo acima, e.args[0].reason é uma instância de socket.error :

>>> requests.get('http://localhost:1111')
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: HTTPConnectionPool(host='localhost', port=1111): Max retries exceeded with url: / (Caused by <class 'socket.error'>: [Errno 10061] No connection could be made because the target machine actively refused it)
>>> e = sys.last_value
>>> e
ConnectionError(MaxRetryError("HTTPConnectionPool(host='localhost', port=1111): Max retries exceeded with url: / (Caused by <class 'socket.error'>: [Errno 10061] No connection could be made because the target machine actively refused it)",),)
>>> e.args[0]
MaxRetryError("HTTPConnectionPool(host='localhost', port=1111): Max retries exceeded with url: / (Caused by <class 'socket.error'>: [Errno 10061] No connection could be made because the target machine actively refused it)",)
>>> e.args[0].reason
error(10061, 'No connection could be made because the target machine actively refused it')

Boa captura @benhoyt. Não estou tão familiarizado com o urllib3 quanto gostaria.

Se realmente parecer como você mostrou, isto é.
requests.exceptions.ConnectionError: HTTPConnectionPool(host='localhost', port=1111): Max retries exceeded with url: / (Caused by <class 'socket.error'>: [Errno 61] Connection refused)

então eu não poderia sonhar com exceção melhor, realmente.

@ piotr-dobrogost, o principal problema (para mim) era o fato de falar sobre "máximo de tentativas excedidas", quando não há nenhuma nova tentativa envolvida. A princípio pensei que era o serviço da web que estava usando dizendo isso, então entrei em contato com eles. Então, cavando mais fundo, descobri que essa era uma peculiaridade do urllib3. Então você pode ver a confusão.

Você perdeu (Caused by <class 'socket.error'>: [Errno 61] Connection refused) parte da exceção?

Sim, você está certo - está tudo lá. Mas, como mencionei, perdi isso no início, porque o MaxRetryError é uma pista falsa.

Essa coisa de tentativas máximas sempre me deixa louco. Alguém se importa se eu mergulhar e ver se não consigo montar um PR para esmagar a mensagem de novas tentativas?

Não tenho a intenção de aparecer do nada, mas uso muitas solicitações no trabalho de Python que fazemos na Cloudant. Recebemos páginas que incluem a coisa de novas tentativas, e isso pode ser uma pista falsa.

A resposta é _talvez_.

O problema é que, embora por padrão não realizemos nenhuma nova tentativa, você pode configurar as Solicitações para repetir automaticamente as solicitações com falha. Nessas situações, o MaxRetryError que envolvemos é totalmente razoável. Se você puder chegar a uma solução que deixe MaxRetryError no lugar quando deveria, mas o remova quando você puder garantir que nenhuma nova tentativa foi feita, nós a consideraremos. =)

@Lukasa obrigado, estou me atualizando no backlog aqui. Se eu tiver a chance de mergulhar, definitivamente irei alcançá-la.

Quase me parece que o lugar certo para a mudança é no urllib3? MaxRetryError faz sentido aumentar no contexto de novas tentativas automáticas, mas no caso de zero novas tentativas (talvez a experiência de solicitações ingênuas) pode ser confuso.

No urllib3, parece que os erros confusos podem ser acionados aqui por meio de solicitações. Seria quase bom levantar um MaxRetryError apenas quando retries==0 and max_retries!=0 . Se max_retries==0 vez disso, aumentar, um RequestError simples será gerado.

Vejo que o urllib3 usado por requisições existe em um pacote incluso - só por curiosidade, por que isso? De qualquer forma, essas foram apenas algumas ideias que eu queria lançar por aí. Ainda estou atualizando as bases de código.

Se a correção pertence ou não ao urllib3, depende totalmente de @shazow. Dado que urllib3 por padrão _não tenta_ novamente (3 vezes IIRC), pode ser que ele queira manter o comportamento de urllib3 como está. Enviando um ping para ele para obter sua opinião.

Nós vendemos o urllib3 para evitar alguns problemas de dependência. Basicamente, isso significa que estamos sempre operando contra uma versão conhecida do urllib3. Isso foi discutido de forma excruciante em # 1384 e # 1812 se você quiser os detalhes corajosos.

Ufa corajoso, mas informativo. @shazow, essas são apenas algumas idéias que tive - levantando um RequestError em vez de MaxRetryError como acima. Realmente, acho que entendi melhor o MaxRetryError depois de verificar o urlopen .

Edição dupla: realmente apenas um kwarg para que se possa raise MaxRetryError(retries=0) e alterar a mensagem em retries==0 .

Que tal um retries=False que desabilitaria as novas tentativas e sempre geraria a exceção original em vez de MaxRetryError ?

Ser capaz de distinguir entre pedir ao urlopen sem novas tentativas e fazer com que ele conte até 0 no número de tentativas seria útil. É chocante ver o MaxRetryError quando você não pediu novas tentativas.

Se alguém quiser fazer um patch + teste para isso, será grato. :)

@shazow ótimo, eu faria isso se eu pudesse encontrar os ciclos. Vou pingar se tiver alguma coisa.

\o/

^ Eu queria saber se houve algum patch lançado? Este problema parece ter sido feito há um ano.

Não que eu saiba. =)

retries=False deve gerar a exceção original a partir de v1.9, sem agrupamento.

pensamentos @kevinburke ?

Preciso de um pouco mais de tempo

Kevin burke
telefone: 925.271.7005 | twentymilliseconds.com

No domingo, 5 de outubro de 2014 às 10:37, Ian Cordasco [email protected]
escreveu:

@kevinburke https://github.com/kevinburke pensamentos?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/kennethreitz/requests/issues/1198#issuecomment -57945403
.

Sim, isso foi consertado, eu acho

requests.get('http://localhost:11211')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "requests/api.py", line 60, in get
    return request('get', url, **kwargs)
  File "requests/api.py", line 49, in request
    return session.request(method=method, url=url, **kwargs)
  File "requests/sessions.py", line 457, in request
    resp = self.send(prep, **send_kwargs)
  File "requests/sessions.py", line 569, in send
    r = adapter.send(request, **kwargs)
  File "requests/adapters.py", line 407, in send
    raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', error(61, 'Connection refused'))

Você poderia me dizer como isso foi resolvido, já que estou recebendo um problema de conexão recusada no meu lado. No meu script python, estou tentando conectar o servidor RPC

@SiddheshS Este problema foi corrigido reformulando algumas exceções: não tem nada a ver com o erro real de conexão recusada. Para pedir ajuda com um problema, você deve considerar o uso de Stack Overflow .

Eu encontrei o mesmo problema. aconteceu ocasionalmente. como consertar alguem pode me ajudar? .obrigado.

request.exceptions.ConnectionError: HTTPSConnectionPool (host = 'api.xxxx.com', port = 443): Máximo de tentativas excedido com url: / v2 / goods /? category = 0 & sort_type = 2 & page_size = 3 & page_num = 13 & t = 0 & count = 110 (causado por NewConnectionError (': Falha ao estabelecer uma nova conexão: [Errno 110] Tempo limite de conexão esgotado ',))
Traceback (última chamada mais recente):
Arquivo "test.py", linha 335, em
a Principal()
Arquivo "test.py", linha 290, em principal
resultado = get_goods_info ()
Arquivo "test.py", linha 67, em get_goods_info
resultado = solicitações.get (url)
Arquivo "/usr/local/lib/python2.7/site-packages/requests/api.py", linha 69, em get
solicitação de retorno ('get', url, params = params, * _kwargs)
Arquivo "/usr/local/lib/python2.7/site-packages/requests/api.py", linha 50, na solicitação
resposta = sessão.request (método = método, url = url, * _kwargs)
Arquivo "/usr/local/lib/python2.7/site-packages/requests/sessions.py", linha 468, na solicitação
resp = self.send (prep, * _send_kwargs)
Arquivo "/usr/local/lib/python2.7/site-packages/requests/sessions.py", linha 576, em envio
r = adapter.send (request, * _kwargs)
Arquivo "/usr/local/lib/python2.7/site-packages/requests/adapters.py", linha 423, em envio
aumentar ConnectionError (e, solicitação = solicitação)

@nkjulia A tentativa de conexão está

Eu também me enganei com isso ....

@kevinburke como seu problema foi resolvido depois de obter o erro de conexão recusada? Você poderia, por favor, um conselho amigo. TIA

Ignore meu pós-colega. Eu tinha várias versões de pythons em minha máquina, devido ao qual ela não foi capaz de escolher a correta e estava lançando um erro. Postar isso pensando que pode ser útil para alguém.

Eu encontrei o mesmo problema. aconteceu ocasionalmente. como consertar alguem pode me ajudar? .obrigado.

request.exceptions.ConnectionError: HTTPSConnectionPool (host = 'api.xxxx.com', port = 443): Máximo de tentativas excedido com url: / v2 / goods /? category = 0 & sort_type = 2 & page_size = 3 & page_num = 13 & t = 0 & count = 110 (causado por NewConnectionError (': Falha ao estabelecer uma nova conexão: [Errno 110] Tempo limite de conexão esgotado ',))
Traceback (última chamada mais recente):
Arquivo "test.py", linha 335, em
a Principal()
Arquivo "test.py", linha 290, em principal
resultado = get_goods_info ()
Arquivo "test.py", linha 67, em get_goods_info
resultado = solicitações.get (url)
Arquivo "/usr/local/lib/python2.7/site-packages/requests/api.py", linha 69, em get
solicitação de retorno ('get', url, params = params, * _kwargs)
Arquivo "/usr/local/lib/python2.7/site-packages/requests/api.py", linha 50, na solicitação
resposta = sessão.request (método = método, url = url, * _kwargs)
Arquivo "/usr/local/lib/python2.7/site-packages/requests/sessions.py", linha 468, na solicitação
resp = self.send (prep, * _send_kwargs)
Arquivo "/usr/local/lib/python2.7/site-packages/requests/sessions.py", linha 576, em envio
r = adapter.send (request, * _kwargs)
Arquivo "/usr/local/lib/python2.7/site-packages/requests/adapters.py", linha 423, em envio
aumentar ConnectionError (e, solicitação = solicitação)

Se este problema foi resolvido, por favor me dê alguns conselhos.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

brainwane picture brainwane  ·  3Comentários

eromoe picture eromoe  ·  3Comentários

avinassh picture avinassh  ·  4Comentários

JimHokanson picture JimHokanson  ·  3Comentários

8key picture 8key  ·  3Comentários