Getting errno from an SSLError

I noticed a couple of issues trying to get the `errno` attribute from an `SSLError`.

Firstly the `errno` attribute is accessed via `err.message.message.errno`, which is a little annoying but more annoyingly, for `ConnectionError` the attribute is accessed via `err.message.reason.errno` which is a problem as an except clause catching `ConnectionErrors` will also catch `SSLError` as it's a subclass, so they should really have the same interface.

Another issue I noticed is that the `errno` attribute for the an `SSLError` instance returns `None`


This seems reasonable. I'll take a crack at standardizing the exceptions tonight.


Thanks! The bigger issue for me is that the `errno` attribute seems to get lost somehow in the wrapped exception, the differing interface is just a bit of a gotcha.


Yeah. I just figure if we standardize it a bit, I'll find why errno isn't 
being properly saved


Awesome!

Also something which I noticed, which I guess you probably know about already but might be worth considering is exceptions like these:

`requests.exceptions.SSLError(requests.packages.urllib3.exceptions.SSLError(ssl.SSLError('_ssl.c:488: The handshake operation timed out')))`, `requests.exceptions.SSLError(requests.packages.urllib3.exceptions.SSLError(ssl.SSLError('The read operation timed out')))`

wont get caught by a `requests.exceptions.Timeout` handler.


So while we could introspect those exceptions, we really only re-wrap 
exceptions so that the user doesn't have to do:

``` python
from requests.packages.urllib3 import SSLError, Timeout, #etc.

try:
    pass
except SSLError as ssle:
    pass
```

Those errors are strictly rising out of the SSL library being used so their 
being re-wrapped as SSLErrors is perfectly valid. What we would rely on is 
urllib3 doing the introspection but I doubt @shazow would want that there 
either. So that you can consider a "Won't fix" item.


@kennethreitz @Lukasa @GP89 is there any interest in this issue any longer? I'm not keen on the solution I had started to work out and the issue wasn't really ours in the first place as can be seen by the discussion on e4e7eb8


I view this as a "Wouldn't It Be Nice If" issue. I'm happy to leave it open and mark it 'Contributor Friendly'.


I don't mind "flattening" the exceptions but I want to spend my limited spare time on items that aren't on the fence. I'm sure there is value to not having XError(YError(ZError(...),...),...) But we'd need to have a way of accessing those.


Hmm. I think the only way to do this in any sensible way is, each time we raise an exception, to grab the string error and the errno explicitly. Otherwise the exceptions themselves know way too much about the things they're wrapping.  The issue there is that we become very, very brittle against upstream changes (e.g. in urllib3).


Ugh, this is a nightmare to do cleanly. Our mapping of exceptions isn't 1:1, so not all the exceptions we wrap have the same interface. =(


I think this was resolved (sort of) by #1475. Closing for now but it can be reopened if necessary.


#1475 was never merged. =)


GitHub sometimes displays merged PRs as closed so I figured it was probably merged. Oh well.

This isn't exactly a bug but more of a feature request, do we have any updated ideas about this? Last I saw: 

> this is a nightmare to do cleanly.

This makes me wonder if we should even be bothering.


Possibly not. It's certainly hard for me to get excited about at the minute.


I'll put it on my TODO list. Not sure when I'll get to it though.


Greetings!

I'm also in a situation where SSLError is getting thrown:

```
 File "/usr/lib/python2.7/dist-packages/requests/adapters.py", line 382, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError
```

Although in my case I'm trying to pull message

```
except requests.exceptions.SSLError as e:
                return e.message
```

 and then I get

```
Traceback (most recent call last):
  File "exceptionTest.py", line 5, in <module>
    print con.request()
TypeError: __str__ returned non-string (type Error)
```

It seems related, but I am relatively new to python, so let me know if I'm missing something (or doing something wrong).


Hey @jgillmanjr 

It doesn't seem that the `TypeError` is caused by how you're handling the exception. It seems like there's a line in your script (`exceptionTest.py`) that reads `print con.request()` and whatever `con.request()` returns cannot be coerced to a string.


@sigmavirus24 

Here's a gist that I created to give more context: https://gist.github.com/jgillmanjr/fb4431bec403de48cf22

I ultimately realized that it was the cert on the testing box that wasn't passing verification. But I'd still like to know if I was trying to get the exception properly or if the exception that is being returned could use some fixing.

Thanks again!


@jgillmanjr sorry for the delay. I tried out something on my end with requests 2.4.3

``` pycon
Python 2.7.8 (default, Aug 24 2014, 21:26:19)
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> try:
...   r = requests.get('https://www.invalid-cert.com')
... except requests.exceptions.SSLError as e:
...   m = e.message
...
>>> m
SSLError(SSLError(1, '_ssl.c:510: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed'),)
>>> print m
[Errno 1] _ssl.c:510: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
>>> print e
[Errno 1] _ssl.c:510: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
```

So I'm not sure why you're seeing a problem honestly.


@sigmavirus24 Not a problem.

It seems when I  run it from the interactive shell, it sort of works:

```
Python 2.7.8 (default, Oct  7 2014, 17:59:21) 
[GCC 4.9.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> try:
...     r = requests.get('https://api-public.jgillman.dev.liquidweb.com:20900')
... except requests.exceptions.SSLError as e:
...     m = e.message
... 
>>> m
SSLError(SSLError('bad handshake', Error([('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', 'certificate verify failed')],)),)
>>> print m
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __str__ returned non-string (type Error)
>>> m
SSLError(SSLError('bad handshake', Error([('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', 'certificate verify failed')],)),)
>>> print e
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __str__ returned non-string (type Error)
>>> e
SSLError(SSLError(SSLError('bad handshake', Error([('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', 'certificate verify failed')],)),),)
```

However, it looks as though I'm using 2.3.0

/// Break ///

Upgraded to 2.4.3 and still seeing the same thing.


@jgillmanjr, I just realized that this is likely due to your using pyOpenSSL, ndg-httpsclient, and pyasn1. I'm going to test with those installed to see if I can reproduce this.


@sigmavirus24 Well, I _may_ have figured it out.

I forgot that I actually initially installed pip via apt. This subsequently required the package `python-requests` which was only at version 2.3.0. I realized this once I started getting issues with requests after I did a pip upgrade on it and pip proceeded to not work very well any more.

Now that I've removed the packages, installed pip via the `get-pip.py` script, I am not recieving any more errors:

```
>>> m
SSLError(SSLError(1, u'[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)'),)
>>> print m
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)
>>> e
SSLError(SSLError(SSLError(1, u'[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)'),),)
>>> print e
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)
```

I might have to report a bug to the package managers.

Sorry for the time sink!

-Jason


@sigmavirus24 If you care to weigh in, here is a link to the bug report: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=766481


That's really bizarre @jgillmanjr I'm going to keep my neck out of that bug report but I trust @eriolv will be a great help to you.


@sigmavirus24 Thanks as usual for bringing me in! :)
I have investigated a bit and the problem seems related to pyasn1. See my reply
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=766481#10
Unfortunately I can check deeply right now...


@sigmavirus24 It seems to be somting related to pyasn1: I can reproduce on a virtualenv and @jgillmanjr confirmed it. Can I link the Debian Bug with this one? Or you prefer a new issue since the TypeError is only due pyasn1?


I'm not sure what there is for requests to do in this case. As you found in your examination this is a problem with pyasn1. I think we can continue the discussion here. Can you tell me what the type is of `e.message`? (That might also have it's own `.message` attribute that would be good to know the type of.)


@sigmavirus24 `<class 'requests.packages.urllib3.exceptions.SSLError'>` is what I get when I run `print type(e.message)`


So I can get the SSLError with https://hg.mornie.org but I cannot reproduce this still.

``` python
try:
    requests.get('https://hg.mornie.org')
except requests.exceptions.SSLError as e:
    m = e.message

print(type(m))
print(type(m.message))  # ssl.SSLError for me
print(m.args)  # ('bad handshake', Error([('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', 'certificate verify failed')],))
print(type(m.message.args[1]))  # OpenSSL.SSL.Error for me
print(type(m.message.args[1].message))  # list
```

Feel free to explore other attributes to figure out which one is triggering this for you. I can't reproduce it sadly :(