timeout exception: session.get() vs requests.get() behavior discrepancy

When testing my code for its behavior in case of time-outs, I have run into the following observation. When I issue my request through:

***A.*** `requests.get('http://xxx.yy.com:8080/', timeout=(10.0001,0.000001))`, the stack-trace and the Exception triggered look as such:

    /.../anaconda3/lib/python3.4/site-packages/requests/sessions.py in send(self, request, **kwargs)
        571
        572         # Send the request
    --> 573         r = adapter.send(request, **kwargs)
        574
        575         # Total elapsed time of the request (approximately)
    
    /.../anaconda3/lib/python3.4/site-packages/requests/adapters.py in send(self, request, stream, timeout, verify, cert, proxies)
        431                 raise SSLError(e, request=request)
        432             elif isinstance(e, ReadTimeoutError):
    --> 433                 raise ReadTimeout(e, request=request)
        434             else:
        435                 raise
    
    ReadTimeout: HTTPConnectionPool(host='xxx.yy.com', port=8080): Read timed out. (read timeout=1e-06)

***B.*** `session.get('http://xxx.yy.com:8080/', timeout=(10.0001,0.000001))`, the stack-trace and the Exception triggered look as such:

    /.../anaconda3/lib/python3.4/site-packages/requests/sessions.py in send(self, request, **kwargs)
        571
        572         # Send the request
    --> 573         r = adapter.send(request, **kwargs)
        574
        575         # Total elapsed time of the request (approximately)
    
    /.../anaconda3/lib/python3.4/site-packages/requests/adapters.py in send(self, request, stream, timeout, verify, cert, proxies)
        422                 raise RetryError(e, request=request)
        423
    --> 424             raise ConnectionError(e, request=request)
        425
        426         except _ProxyError as e:
    
    ConnectionError: HTTPConnectionPool(host='xxx.yy.com', port=8080): Max retries exceeded with url: / (Caused by ReadTimeoutError("HTTPConnectionPool(host='xxx.yy.com', port=8080): Read timed out. (read timeout=1e-06)",))

This is however to a fundamentally different end-result in terms of Exception handling, as - surprisingly ! - it looks as if when using the `session.get`, one needs to catch `ConnectionError` rather than `ReadTimeout`? This is quite counter intuitive. Can someone advise? Why the difference, and how/what to catch in the code? Is that a bug?

This is for requests 2.7.0.

Hi @lakokaz, would you mind seeing if this is reproducible with 2.23.0? The version you’re using is 5 years old and no longer supported.

I could not reproduce this for the newer version.

But I'm experiencing something related for 2.22.0. Namely:

```
from requests.sessions import Session
from requests.adapters import HTTPAdapter
from requests.exceptions import Timeout
from requests.exceptions import ConnectionError

MAX_RETRIES = 1
req_session = Session()
req_session.mount('http://',HTTPAdapter(max_retries=MAX_RETRIES))
try:
    req_session.get("http://github.com/", timeout=(10.,0.000001))
except Exception as exc:
    assert(isinstance(exc,Timeout)), \
        "Nope, not a Timeout, but rather: {}".format(exc.__class__)
```
where I use deliberately the ridiculous value for the read time-out. Now, for `MAX_RETRIES=0` this results (as one would expect) in `requests.exceptions.Timeout`. But as soon as `MAX_RETRIES>0`, this throws (surprisingly, to me at least) `requests.exceptions.ConnectionError`.

I find that very counterintuitive... Is that a feature? Is that a bug? 


Thanks for the update @lakokaz. So for this specific example, I do agree it looks odd at first glance. The problem here is these are two separate failure types and the second could have a number of underlying causes.

When MAX_RETRIES is 0, urllib3 bypasses the retry mechanism and throws a `urllib3.exceptions.ReadTimeoutError`, which we surface as an appropriate `requests.exceptions.ReadTimeout`. When `MAX_RETRIES` is > 0, urllib3 raises a generic `MaxRetryError` which we'll present as a  `requests.exceptions.ConnectionError`. The possible triggers for `MaxRetryError` are more than just timeouts so we need to raise something sensible to handle all potential cases.

We could potentially have a more detailed exception object presented by urllib3 in this case, and then perform some introspection to surface a more specific error. That introduces its own new set of inconsistencies though and would be a breaking change. Right now, I don't see us changing the behaviour around this.

Resolving since we haven't heard any further feedback.