Get errno from Python requests `ConnectionError`
Using the Python requests library, I wanted to make a PUT
request, and ignore any ConnectionError
if it was an error 104
“Connection reset by peer”. (104
is ECONNRESET
defined in errno.h
). If this error was raised, the traceback I received was as follows:
[...]
File "/usr/bob/venv/lib/python2.7/site-packages/requests/sessions.py", line 518, in request
resp = self.send(prep, **send_kwargs)
File "/usr/bob/venv/lib/python2.7/site-packages/requests/sessions.py", line 639, in send
r = adapter.send(request, **kwargs)
File "/usr/bob/venv/lib/python2.7/site-packages/requests/adapters.py", line 488, in send
raise ConnectionError(err, request=request)
ConnectionError: ('Connection aborted.', error(104, 'Connection reset by peer'))
Using dir(requests.ConnectionError)
showed that it had an errno
attribute, and so I naïvely tried this solution first:
try:
result = requests.put(url, json=payload)
result.raise_for_status()
except requests.ConnectionError as err:
# WRONG
if not err.errno == 104:
raise
This did not work. The exception was always raised.
It turned out that err.errno
was None
. (This appears to be because err.errno
actually comes from IOError
, the standard Python class from which ConnectionError inherits).
Tenacious use of dir
revealed that the correct rune is not err.errno
, but err.args[0].args[1].errno
. This process of discovery can be seen in the log below:
>>> dir(err)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__getslice__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', '__weakref__', 'args', 'errno', 'filename', 'message', 'request', 'response', 'strerror']
>>> dir(err.args[0])
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__getslice__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', '__weakref__', 'args', 'message']
>>> err.args[0].args
('Connection aborted.', error(104, 'Connection reset by peer'))
>>> err.args[0].message
>>> dir(err.args[0].args)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
>>> dir(err.args[0].message)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>> err.args[0].args[1]
[Errno 104] Connection reset by peer
>>> err.args[0].args[1].errno
104
So finally, to ignore ConnectionError 104
:
try:
result = requests.put(url, json=payload)
result.raise_for_status()
except requests.ConnectionError as err:
# RIGHT -- though will these attributes and indices will always exist?
if not err.args[0].args[1].errno == 104:
raise