Skip to content

Commit 505e389

Browse files
authored
SNOW-1524206: introduce a private parameter to enable raising raw http error not handled by connector logic (#2041)
1 parent d9e3dc4 commit 505e389

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

src/snowflake/connector/network.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,14 @@ def _request_exec_wrapper(
892892
full_url = retry_ctx.add_retry_params(full_url)
893893
full_url = SnowflakeRestful.add_request_guid(full_url)
894894
is_fetch_query_status = kwargs.pop("is_fetch_query_status", False)
895+
# raise_raw_http_failure is not a public parameter and may change in the future
896+
# it enables raising raw http errors that are not handled by
897+
# connector, connector handles the following http error:
898+
# 1. FORBIDDEN error when trying to login
899+
# 2. retryable http code defined in method is_retryable_http_code
900+
# 3. UNAUTHORIZED error when using okta authentication
901+
# raise_raw_http_failure doesn't work for the 3 mentioned cases.
902+
raise_raw_http_failure = kwargs.pop("raise_raw_http_failure", False)
895903
try:
896904
return_object = self._request_exec(
897905
session=session,
@@ -900,6 +908,7 @@ def _request_exec_wrapper(
900908
headers=headers,
901909
data=data,
902910
token=token,
911+
raise_raw_http_failure=raise_raw_http_failure,
903912
**kwargs,
904913
)
905914
if return_object is not None:
@@ -967,7 +976,9 @@ def _request_exec_wrapper(
967976
)
968977
return None # retry
969978
except Exception as e:
970-
if not no_retry:
979+
if (
980+
raise_raw_http_failure and isinstance(e, requests.exceptions.HTTPError)
981+
) or not no_retry:
971982
raise e
972983
logger.debug("Ignored error", exc_info=True)
973984
return {}
@@ -1036,6 +1047,7 @@ def _request_exec(
10361047
binary_data_handler=None,
10371048
socket_timeout: int | None = None,
10381049
is_okta_authentication: bool = False,
1050+
raise_raw_http_failure: bool = False,
10391051
):
10401052
if socket_timeout is None:
10411053
if self._connection.socket_timeout is not None:
@@ -1113,6 +1125,8 @@ def _request_exec(
11131125
raise_okta_unauthorized_error(self._connection, raw_ret)
11141126
return None # required for tests
11151127
else:
1128+
if raise_raw_http_failure:
1129+
raw_ret.raise_for_status()
11161130
raise_failed_request_error(
11171131
self._connection, full_url, method, raw_ret
11181132
)

test/unit/test_network.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python
2+
#
3+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
4+
#
5+
6+
import io
7+
import unittest.mock
8+
from test.unit.mock_utils import mock_connection
9+
10+
import pytest
11+
12+
try:
13+
from snowflake.connector import Error, InterfaceError
14+
from snowflake.connector.network import SnowflakeRestful
15+
from snowflake.connector.vendored.requests import HTTPError, Response
16+
except ImportError:
17+
# skipping old driver test
18+
pass
19+
20+
pytestmark = pytest.mark.skipolddriver
21+
22+
23+
def test_fetch():
24+
connection = mock_connection()
25+
connection.errorhandler = Error.default_errorhandler
26+
rest = SnowflakeRestful(
27+
host="testaccount.snowflakecomputing.com",
28+
port=443,
29+
connection=connection,
30+
)
31+
32+
default_parameters = {
33+
"method": "POST",
34+
"full_url": "https://testaccount.snowflakecomputing.com/",
35+
"headers": {},
36+
"data": '{"code": 12345}',
37+
"token": None,
38+
}
39+
40+
failed_response = Response()
41+
failed_response.status_code = 409
42+
failed_response.raw = io.StringIO("error")
43+
failed_response.url = default_parameters["full_url"]
44+
failed_response.reason = "conflict"
45+
with unittest.mock.patch(
46+
"snowflake.connector.vendored.requests.sessions.Session.request",
47+
return_value=failed_response,
48+
):
49+
with pytest.raises(HTTPError) as exc:
50+
rest.fetch(**default_parameters, raise_raw_http_failure=True)
51+
assert exc.value.response.status_code == failed_response.status_code
52+
assert exc.value.response.reason == failed_response.reason
53+
54+
with pytest.raises(HTTPError) as exc:
55+
rest.fetch(**default_parameters, raise_raw_http_failure=True, no_retry=True)
56+
assert exc.value.response.status_code == failed_response.status_code
57+
assert exc.value.response.reason == failed_response.reason
58+
59+
# if not setting the flag, the function returns an empty dictionary
60+
assert (
61+
rest.fetch(
62+
**default_parameters, raise_raw_http_failure=False, no_retry=True
63+
)
64+
== {}
65+
)
66+
assert rest.fetch(**default_parameters, no_retry=True) == {}
67+
# if no retry is set to False, the function raises an InterfaceError
68+
with pytest.raises(InterfaceError) as exc:
69+
assert rest.fetch(**default_parameters, no_retry=False)

0 commit comments

Comments
 (0)