{"id":1271,"date":"2017-08-09T03:18:33","date_gmt":"2017-08-09T09:18:33","guid":{"rendered":"http:\/\/jaredrobinson.com\/blog\/?p=1271"},"modified":"2017-08-10T11:07:17","modified_gmt":"2017-08-10T17:07:17","slug":"iterating-the-python3-exception-chain","status":"publish","type":"post","link":"https:\/\/jaredrobinson.com\/blog\/iterating-the-python3-exception-chain\/","title":{"rendered":"Iterating the python3 exception chain"},"content":{"rendered":"<p>I use the python requests library to make HTTP requests. Handling exceptions and giving the non-technical end user a friendly message can be a challenge when the original exception is wrapped up in an exception chain. For example:<\/p>\n<pre><code>import requests\n\nurl = \"http:\/\/one.two.threeFourFiveSixSevenEight\"\ntry:\n    resp = requests.get(url)\nexcept requests.RequestException as e:\n    print(\"Couldn't contact\", url, \":\", e)\n<\/code><\/pre>\n<p>Prints:<\/p>\n<blockquote>\n<p>Couldn&#8217;t contact http:\/\/one.two.threeFourFiveSixSevenEight : HTTPConnectionPool(host=&#8217;one.two.threeFourFiveSixSevenEight&#8217;, port=80): Max retries exceeded with url: \/ (Caused by NewConnectionError(&#8216;&lt;requests.packages.urllib3.connection.HTTPConnection object at 0x7f527329c978>: Failed to establish a new connection: [Errno -2] Name or service not known&#8217;,))<\/p>\n<\/blockquote>\n<p>And that&#8217;s a mouthful.<\/p>\n<p>I want to tell the end user that DNS isn&#8217;t working, rather than showing the ugly stringified error message. How do I do that, in python3? Are python3 exceptions iterable? No. So I searched the internet, and found inspiration from the <a href=\"https:\/\/github.com\/getsentry\/raven-python\/pull\/811\/files?diff=unified\">raven project<\/a>. I adapted their code in two different ways to give me the result I wanted.<\/p>\n<p>Update Aug 10: See the end of this blog post for a more elegant solution.<\/p>\n<pre><code>import socket\nimport requests\nimport sys\n\ndef chained_exceptions(exc_info=None):\n    \"\"\"\n    Adapted from: https:\/\/github.com\/getsentry\/raven-python\/pull\/811\/files?diff=unified\n\n    Return a generator iterator over an exception's chain.\n\n    The exceptions are yielded from outermost to innermost (i.e. last to\n    first when viewing a stack trace).\n    \"\"\"\n    if not exc_info or exc_info is True:\n        exc_info = sys.exc_info()\n\n    if not exc_info:\n        raise ValueError(\"No exception found\")\n\n    yield exc_info\n    exc_type, exc, exc_traceback = exc_info\n\n    while True:\n        if exc.__suppress_context__:\n            # Then __cause__ should be used instead.\n            exc = exc.__cause__\n        else:\n            exc = exc.__context__\n        if exc is None:\n            break\n        yield type(exc), exc, exc.__traceback__\n\ndef chained_exception_types(e=None):\n    \"\"\"\n    Return a generator iterator of exception types in the exception chain\n\n    The exceptions are yielded from outermost to innermost (i.e. last to\n    first when viewing a stack trace).\n\n    Adapted from: https:\/\/github.com\/getsentry\/raven-python\/pull\/811\/files?diff=unified\n    \"\"\"\n    if not e or e is True:\n        e = sys.exc_info()[1]\n\n    if not e:\n        raise ValueError(\"No exception found\")\n\n    yield type(e)\n\n    while True:\n        if e.__suppress_context__:\n            # Then __cause__ should be used instead.\n            e = e.__cause__\n        else:\n            e = e.__context__\n        if e is None:\n            break\n        yield type(e)\n\nsaved_exception = None\ntry:\n    resp = requests.get(\"http:\/\/one.two.threeFourFiveSixSevenEight\")\nexcept Exception as e:\n    saved_exception = e\n    if socket.gaierror in chained_exception_types(e):\n        print(\"Found socket.gaierror in exception block via e\")\n    if socket.gaierror in chained_exception_types():\n        print(\"Found socket.gaierror in exception block via traceback\")\n    if socket.gaierror in chained_exception_types(True):\n        print(\"Found socket.gaierror in exception block via traceback\")\n\nif saved_exception:\n    print(\"\\nIterating exception chain for a saved exception...\")\n    for t, ex, tb in chained_exceptions((type(saved_exception), saved_exception, saved_exception.__traceback__)):\n        print(\"\\ttype:\", t, \"Exception:\", ex)\n        if t == socket.gaierror:\n            print(\"\\t*** Found socket.gaierror:\", ex)\n    if socket.gaierror in chained_exception_types(saved_exception):\n        print(\"\\t*** Found socket.gaierror via chained_exception_types\")\n<\/code><\/pre>\n<p>Here&#8217;s the output:<\/p>\n<pre><code>Found socket.gaierror in exception block via e\nFound socket.gaierror in exception block via traceback\nFound socket.gaierror in exception block via traceback\n\nIterating exception chain for a saved exception...\n    type: &lt;class 'requests.exceptions.ConnectionError'&gt; Exception: HTTPConnectionPool(host='one.two.threeFourFiveSixSevenEight', port=80): Max retries exceeded with url: \/ (Caused by NewConnectionError('&lt;requests.packages.urllib3.connection.HTTPConnection object at 0x7fae7d0bfa20&gt;: Failed to establish a new connection: [Errno -2] Name or service not known',))\n    type: &lt;class 'requests.packages.urllib3.exceptions.MaxRetryError'&gt; Exception: HTTPConnectionPool(host='one.two.threeFourFiveSixSevenEight', port=80): Max retries exceeded with url: \/ (Caused by NewConnectionError('&lt;requests.packages.urllib3.connection.HTTPConnection object at 0x7fae7d0bfa20&gt;: Failed to establish a new connection: [Errno -2] Name or service not known',))\n    type: &lt;class 'requests.packages.urllib3.exceptions.NewConnectionError'&gt; Exception: &lt;requests.packages.urllib3.connection.HTTPConnection object at 0x7fae7d0bfa20&gt;: Failed to establish a new connection: [Errno -2] Name or service not known\n    type: &lt;class 'socket.gaierror'&gt; Exception: [Errno -2] Name or service not known\n    *** Found socket.gaierror: [Errno -2] Name or service not known\n    *** Found socket.gaierror via chained_exception_types()\n<\/code><\/pre>\n<p>Now I can write the following code:<\/p>\n<pre><code>url = \"http:\/\/one.two.threeFourFiveSixSevenEight\"\ntry:\n    resp = requests.get(url)\nexcept requests.RequestException as e:\n    if socket.gaierror in chained_exception_types(e):\n        print(\"Couldn't get IP address for hostname in URL\", url, \" -- connect device to Internet\")\n    else:\n        raise\n<\/code><\/pre>\n<p>Very nice &#8212; just what I wanted.<\/p>\n<p>Note that Python 2 does not support exception chaining, so this only works in Python 3.<\/p>\n<p>Aug 10: A colleague of mine, Lance Anderson, came up with a far more elegant solution:<\/p>\n<pre><code>import requests\nimport socket\n\nclass IterableException(object):\n\n        def __init__(self, ex):\n                self.ex = ex\n\n        def __iter__(self):\n                self.next = self.ex\n                return self\n\n        def __next__(self):\n                if self.next.__suppress_context__:\n                        self.next = self.next.__cause__\n                else:\n                        self.next = self.next.__context__\n                if self.next:\n                        return self.next\n                else:\n                        raise StopIteration\n\nurl = \"http:\/\/one.two.threeFourFiveSixSevenEight\"\n\ntry:\n        resp = requests.get(url)\nexcept requests.RequestException as e:\n        ie = IterableException(e)\n        if socket.gaierror in [type(x) for x in ie]:\n                print(\"Couldn't get IP address for hostname in URL\", url, \" -- connect device to Internet.\")\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>I use the python requests library to make HTTP requests. Handling exceptions and giving the non-technical end user a friendly message can be a challenge when the original exception is wrapped up in an exception chain. For example: import requests url = &#8220;http:\/\/one.two.threeFourFiveSixSevenEight&#8221; try: resp = requests.get(url) except requests.RequestException as e: print(&#8220;Couldn&#8217;t contact&#8221;, url, &#8220;:&#8221;, &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/jaredrobinson.com\/blog\/iterating-the-python3-exception-chain\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Iterating the python3 exception chain&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12,17],"tags":[],"class_list":["post-1271","post","type-post","status-publish","format-standard","hentry","category-programming","category-tech"],"_links":{"self":[{"href":"https:\/\/jaredrobinson.com\/blog\/wp-json\/wp\/v2\/posts\/1271","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jaredrobinson.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jaredrobinson.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jaredrobinson.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jaredrobinson.com\/blog\/wp-json\/wp\/v2\/comments?post=1271"}],"version-history":[{"count":5,"href":"https:\/\/jaredrobinson.com\/blog\/wp-json\/wp\/v2\/posts\/1271\/revisions"}],"predecessor-version":[{"id":1288,"href":"https:\/\/jaredrobinson.com\/blog\/wp-json\/wp\/v2\/posts\/1271\/revisions\/1288"}],"wp:attachment":[{"href":"https:\/\/jaredrobinson.com\/blog\/wp-json\/wp\/v2\/media?parent=1271"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jaredrobinson.com\/blog\/wp-json\/wp\/v2\/categories?post=1271"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jaredrobinson.com\/blog\/wp-json\/wp\/v2\/tags?post=1271"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}