Exceptions having references to urllib3 exceptions in "message" field

Hey,

tldr; is exceptions keeping references to urllib3 exceptions a design choice?

I ran an issue when trying to automatically serialise `TimeoutError.message`. It appears it contains a reference to urllib3 exceptions:

```python
>>> try:
...     requests.get('http://google.com', timeout=0.05)
... except Exception as e:
        pass
>>> e.message
ReadTimeoutError("HTTPConnectionPool(host='www.google.pl', port=80): Read timed out. (read timeout=0.05)",)
>>> e.message.__class__
<class 'requests.packages.urllib3.exceptions.ReadTimeoutError'>
```
Which is inline with I see in [in the source](https://github.com/kennethreitz/requests/blob/master/requests/adapters.py#L499)

```python
    elif isinstance(e, ReadTimeoutError):
          raise ReadTimeout(e, request=request)
```

but I wasn't expecting this.

[Python docs](https://docs.python.org/2/library/exceptions.html#exceptions.BaseException) say:

> The tuple of arguments given to the exception constructor. Some built-in exceptions (like IOError) expect a certain number of arguments and assign a special meaning to the elements of this tuple, while others are usually called only with a single string giving an error message.

Given the above I was expecting either:

1. `message` to be a string ( per typical use case )
2. `message` to be `None` and `errno` and `strerror` being set: ( because [Request exceptions extend IOError](https://github.com/kennethreitz/requests/blob/master/requests/exceptions.py#L12) and [IOError having a specific constructor defined](https://docs.python.org/2/library/exceptions.html#exceptions.EnvironmentError) )

Was the current implementation a conscious decision? If not, what would be your preference, option (1) or (2) from the above?
(2) seems more correct, (1) seems simpler.

Happy to try to PR.

Thanks!

Yes, keeping references to urllib3 exceptions is a deliberate design decision. It ensures that low-level causes of high-level problems can be accurately diagnosed without relying on Python 3 exception chaining, which has not historically existed and continues to not exist on Python 2.

I am pretty disinclined to want to mess with the exceptions at this time: they're pretty stable, and almost any change to them is backward compatible in a big way with pretty minimal gain. 

Ah, I see.

It almost felt like Requests exception should abstract the low-level ( by copying relevant/required things ) but I guess that's a lot of "how does urllib3 work" work :]

If we were to keep having a reference, why not introduce a `parent_exception` field though? Using `message` for this purpose really feels like a field abuse? :)

It's also ultimately not worthwhile: it ties us so closely to decisions that urllib3 makes in its internals that we become essentially unable to tolerate changes within urllib3 itself.

I don't think there's any particular problem with passing a non-string exception message. What makes you think it's field abuse? I should note as well that `Exception.message` does not exist on Python 3, so this is a Python-2-only concern.

I don't really do python3 :(

Well,
- is called "message"
- I've only ever seen it seen it treated as a debug/string message. (not necessarily an argument ;))
- The documentation says "while others are usually called only with a single string giving an error message.".

I've just always assumed it's a general language pattern. (again possibly I'm wrong ;)).

From afar, it would also seem cleaner to have it explicitly set as an attribute, rather than saying _"the "root" exception is the first argument of the tuple of arguments passed in to the exception"_, which I guess is the case in Python3 too?

We ran into a related issue lately and fixed it downstream: https://github.com/ckan/ckanapi/pull/111.
While a case can be made that this is not `request`'s problem as `Exception.message` is deprecated since Python 2.6, it might be worth considering to put something like

`self.message = str(args) + str(kwargs)`

into `RequestException.__init__`

to give legacy applications the string they want if they use `Exception.message`.

    

We had a bug in our code related to this. The docs at http://docs.python-requests.org/en/master/api/#requests.Response.reason state:

> Textual reason of responded HTTP Status, e.g. "Not Found" or "OK".

But `Response.reason` can also contain non-strings, for example we got `SSLError` instance there. I assume this is because requests just grabs `arg[0]` and we expect it to be a string (like it normally is)?

If nothing else, the docs should be updated at least.

What? `Response.reason` is only ever set by `build_response` to the same as the string in urllib3, and that is only ever set to the reason phrase from `httplib`. So...where do you think we're getting `args[0]` from to set `Response.reason`?

@Lukasa That was just assumption, I didn't look into `requests` code.

That is the behavior we witnessed, however. If you're saying that shouldn't be possible, I should double check.

As far as I can tell that should not be possible. If you're using a custom transport adapter, though, they could be doing any number of things.

Ok nevermind, ignore what I wrote earlier. I interpreted the situation a bit too far. Nothing wrong with `Response.reason`.

The problem was that guys were catching `HttpException` and then grabbing `exception.message`, which returned `SSLError` instead of a string. But that is in the core of this ticket, right?

Yup, sure, I was just responding to your analysis. =)

My position on the core ticket remains broadly the same: it affects only Python 2, and it's sufficiently stable that changing it requires a breaking change (so it'd have to go into v3). Given that v3 will release with no more than about two years on Python 2's supported lifecycle, I'm pretty disinclined to consider this a high-priority concern.

With `Exception.message` being deprecated, I think that's a reasonable conclusion. We should always call `str(exception)` if we are after string representation.

I've had convention of adding `cause` kwarg to `Exception`s that hold it. That makes it clear where the cause is retrievable at (not `exception.args[0]` or `exception.args[1]` but `exception.cause`). I'm not sure, maybe I could redirect/copy that as `Exception.__cause__` to make it similar to how things work in Python 3...

But yeah, changes would break backwards-compatibility here, so probably best to do nothing, I agree.

Resolving as Python 2.7 being deprecated and we're unlikely to be investing time fixing bugs that aren't specific to Python 3.