From 3be383ccfdc906d220145ccf4ea6984740478570 Mon Sep 17 00:00:00 2001 From: Emad Mokhtar Date: Thu, 13 Feb 2020 08:44:31 +0100 Subject: [PATCH 1/3] Decode the error response if it is in byte format --- pydruid/client.py | 4 + tests/test_client.py | 178 ++++++++++++++++++++++++++----------------- 2 files changed, 114 insertions(+), 68 deletions(-) diff --git a/pydruid/client.py b/pydruid/client.py index 95dca1dd..70074af7 100755 --- a/pydruid/client.py +++ b/pydruid/client.py @@ -19,6 +19,7 @@ import json import re +from six import binary_type from six.moves import urllib from pydruid.query import QueryBuilder @@ -557,6 +558,9 @@ def _post(self, query): if e.code == 500: # has Druid returned an error? try: + if isinstance(err, binary_type): + # Decode the error before serialize it to JSON + err = err.decode("utf-8") err = json.loads(err) except ValueError: if HTML_ERROR.search(err): diff --git a/tests/test_client.py b/tests/test_client.py index 8f2d0a1b..c24e35fd 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -4,7 +4,7 @@ import pytest from mock import patch, Mock from six.moves import urllib -from six import StringIO +from six import StringIO, binary_type, BytesIO from pydruid.client import PyDruid from pydruid.query import Query @@ -17,23 +17,22 @@ def create_client(): def create_blank_query(): - return Query({}, 'none') + return Query({}, "none") -def _http_error(code, msg, data = ''): +def _http_error(code, msg, data=""): # Need a file-like object for the response data - fp = StringIO(data) + if isinstance(data, binary_type): + fp = BytesIO(data) + else: + fp = StringIO(data) return urllib.error.HTTPError( - url='http://fakeurl:8080/druid/v2/', - hdrs={}, - code=code, - msg=msg, - fp=fp, + url="http://fakeurl:8080/druid/v2/", hdrs={}, code=code, msg=msg, fp=fp, ) class TestPyDruid: - @patch('pydruid.client.urllib.request.urlopen') + @patch("pydruid.client.urllib.request.urlopen") def test_druid_returns_error(self, mock_urlopen): # given mock_urlopen.side_effect = _http_error(500, "Druid error") @@ -42,52 +41,63 @@ def test_druid_returns_error(self, mock_urlopen): # when / then with pytest.raises(IOError): client.topn( - datasource="testdatasource", - granularity="all", - intervals="2015-12-29/pt1h", - aggregations={"count": doublesum("count")}, - dimension="user_name", - metric="count", - filter=Dimension("user_lang") == "en", - threshold=1, - context={"timeout": 1000}) - - @patch('pydruid.client.urllib.request.urlopen') + datasource="testdatasource", + granularity="all", + intervals="2015-12-29/pt1h", + aggregations={"count": doublesum("count")}, + dimension="user_name", + metric="count", + filter=Dimension("user_lang") == "en", + threshold=1, + context={"timeout": 1000}, + ) + + @patch("pydruid.client.urllib.request.urlopen") def test_druid_returns_html_error(self, mock_urlopen): # given - message = textwrap.dedent(""" + message = textwrap.dedent( + """ - + Error 500

HTTP ERROR: 500

Problem accessing /druid/v2/. Reason: -

    javax.servlet.ServletException: java.lang.OutOfMemoryError: GC overhead limit exceeded

-
Powered by Jetty:// 9.3.19.v20170502
+
    javax.servlet.ServletException:
+            java.lang.OutOfMemoryError: GC overhead limit exceeded

+
+ Powered by Jetty:// 9.3.19.v20170502
- """).strip() - mock_urlopen.side_effect = _http_error(500, 'Internal Server Error', message) + """ + ).strip() + mock_urlopen.side_effect = _http_error(500, "Internal Server Error", message) client = create_client() # when / then with pytest.raises(IOError) as e: client.topn( - datasource="testdatasource", - granularity="all", - intervals="2015-12-29/pt1h", - aggregations={"count": doublesum("count")}, - dimension="user_name", - metric="count", - filter=Dimension("user_lang") == "en", - threshold=1, - context={"timeout": 1000}) - - assert str(e.value) == textwrap.dedent(""" - HTTP Error 500: Internal Server Error - Druid Error: javax.servlet.ServletException: java.lang.OutOfMemoryError: GC overhead limit exceeded + datasource="testdatasource", + granularity="all", + intervals="2015-12-29/pt1h", + aggregations={"count": doublesum("count")}, + dimension="user_name", + metric="count", + filter=Dimension("user_lang") == "en", + threshold=1, + context={"timeout": 1000}, + ) + + assert ( + str(e.value) + == textwrap.dedent( + """ + HTTP Error 500: Internal Server Error + Druid Error: javax.servlet.ServletException: + java.lang.OutOfMemoryError: GC overhead limit exceeded Query is: { "aggregations": [ { @@ -112,9 +122,34 @@ def test_druid_returns_html_error(self, mock_urlopen): "queryType": "topN", "threshold": 1 } - """).strip() + """ + ).strip() + ) + + @patch("pydruid.client.urllib.request.urlopen") + def test_druid_returns_string_error_bytes_error_response(self, mock_urlopen): + # given + message = b"Error as bytes, please decode me" + mock_urlopen.side_effect = _http_error(500, "Internal Server Error", message) + client = create_client() - @patch('pydruid.client.urllib.request.urlopen') + # when / then + with pytest.raises(IOError) as e: + client.topn( + datasource="testdatasource", + granularity="all", + intervals="2015-12-29/pt1h", + aggregations={"count": doublesum("count")}, + dimension="user_name", + metric="count", + filter=Dimension("user_lang") == "en", + threshold=1, + context={"timeout": 1000}, + ) + + assert "Error as bytes, please decode me" in str(e.value) + + @patch("pydruid.client.urllib.request.urlopen") def test_druid_returns_results(self, mock_urlopen): # given response = Mock() @@ -126,28 +161,31 @@ def test_druid_returns_results(self, mock_urlopen): "metric" : 100 } ] } ] - """.encode("utf-8") + """.encode( + "utf-8" + ) mock_urlopen.return_value = response client = create_client() # when top = client.topn( - datasource="testdatasource", - granularity="all", - intervals="2015-12-29/pt1h", - aggregations={"count": doublesum("count")}, - dimension="user_name", - metric="count", - filter=Dimension("user_lang") == "en", - threshold=1, - context={"timeout": 1000}) + datasource="testdatasource", + granularity="all", + intervals="2015-12-29/pt1h", + aggregations={"count": doublesum("count")}, + dimension="user_name", + metric="count", + filter=Dimension("user_lang") == "en", + threshold=1, + context={"timeout": 1000}, + ) # then assert top is not None assert len(top.result) == 1 - assert len(top.result[0]['result']) == 1 + assert len(top.result[0]["result"]) == 1 - @patch('pydruid.client.urllib.request.urlopen') + @patch("pydruid.client.urllib.request.urlopen") def test_client_allows_to_export_last_query(self, mock_urlopen): # given response = Mock() @@ -159,29 +197,33 @@ def test_client_allows_to_export_last_query(self, mock_urlopen): "metric" : 100 } ] } ] - """.encode("utf-8") + """.encode( + "utf-8" + ) mock_urlopen.return_value = response client = create_client() client.topn( - datasource="testdatasource", - granularity="all", - intervals="2015-12-29/pt1h", - aggregations={"count": doublesum("count")}, - dimension="user_name", - metric="count", - filter=Dimension("user_lang") == "en", - threshold=1, - context={"timeout": 1000}) + datasource="testdatasource", + granularity="all", + intervals="2015-12-29/pt1h", + aggregations={"count": doublesum("count")}, + dimension="user_name", + metric="count", + filter=Dimension("user_lang") == "en", + threshold=1, + context={"timeout": 1000}, + ) # when / then - # assert that last_query.export_tsv method was called (it should throw an exception, given empty path) + # assert that last_query.export_tsv method was called (it should throw + # an exception, given empty path) with pytest.raises(TypeError): client.export_tsv(None) - @patch('pydruid.client.urllib.request.urlopen') + @patch("pydruid.client.urllib.request.urlopen") def test_client_auth_creds(self, mock_urlopen): client = create_client() query = create_blank_query() - client.set_basic_auth_credentials('myUsername', 'myPassword') + client.set_basic_auth_credentials("myUsername", "myPassword") headers, _, _ = client._prepare_url_headers_and_body(query) - assert headers['Authorization'] == "Basic bXlVc2VybmFtZTpteVBhc3N3b3Jk" + assert headers["Authorization"] == "Basic bXlVc2VybmFtZTpteVBhc3N3b3Jk" From aec8fe4a41c56ca93e7f367fcb72e58f563735d6 Mon Sep 17 00:00:00 2001 From: Emad Mokhtar Date: Sun, 21 Jun 2020 10:50:43 +0200 Subject: [PATCH 2/3] Add a config to ignore the E501:Too long line in the test_client file --- .flake8 | 3 +++ tests/test_client.py | 12 ++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.flake8 b/.flake8 index 3d4715b0..09963000 100644 --- a/.flake8 +++ b/.flake8 @@ -8,3 +8,6 @@ exclude = build import-order-style = google max-line-length = 90 + +per-file-ignores = + tests/test_client.py: E501 diff --git a/tests/test_client.py b/tests/test_client.py index c24e35fd..e236d1dc 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -59,17 +59,14 @@ def test_druid_returns_html_error(self, mock_urlopen): """ - + Error 500

HTTP ERROR: 500

Problem accessing /druid/v2/. Reason: -

    javax.servlet.ServletException:
-            java.lang.OutOfMemoryError: GC overhead limit exceeded

-
- Powered by Jetty:// 9.3.19.v20170502
+
    javax.servlet.ServletException: java.lang.OutOfMemoryError: GC overhead limit exceeded

+
Powered by Jetty:// 9.3.19.v20170502
""" @@ -96,8 +93,7 @@ def test_druid_returns_html_error(self, mock_urlopen): == textwrap.dedent( """ HTTP Error 500: Internal Server Error - Druid Error: javax.servlet.ServletException: - java.lang.OutOfMemoryError: GC overhead limit exceeded + Druid Error: javax.servlet.ServletException: java.lang.OutOfMemoryError: GC overhead limit exceeded Query is: { "aggregations": [ { From 1093a8e006bdf5a34809d19743f6544064d93932 Mon Sep 17 00:00:00 2001 From: Emad Mokhtar Date: Sun, 5 Jul 2020 09:54:07 +0200 Subject: [PATCH 3/3] Fix isort and ignore the white tail in flake8 --- .flake8 | 2 +- tests/test_client.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.flake8 b/.flake8 index 09963000..e9589b0c 100644 --- a/.flake8 +++ b/.flake8 @@ -10,4 +10,4 @@ import-order-style = google max-line-length = 90 per-file-ignores = - tests/test_client.py: E501 + tests/test_client.py: E501 W291 diff --git a/tests/test_client.py b/tests/test_client.py index 44ab6907..1e16cd7f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,7 +1,7 @@ # -*- coding: UTF-8 -*- import textwrap import urllib -from io import StringIO, BytesIO +from io import BytesIO, StringIO from unittest.mock import Mock, patch import pytest @@ -92,8 +92,8 @@ def test_druid_returns_html_error(self, mock_urlopen): str(e.value) == textwrap.dedent( """ - HTTP Error 500: Internal Server Error - Druid Error: javax.servlet.ServletException: java.lang.OutOfMemoryError: GC overhead limit exceeded + HTTP Error 500: Internal Server Error + Druid Error: javax.servlet.ServletException: java.lang.OutOfMemoryError: GC overhead limit exceeded Query is: { "aggregations": [ {