Skip to content

Commit 9cab809

Browse files
authored
Improve #1084 to run rate limited error retry handler correctly (#1094)
1 parent f3f2adb commit 9cab809

File tree

7 files changed

+77
-12
lines changed

7 files changed

+77
-12
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import logging
2+
import os
3+
import unittest
4+
5+
import pytest
6+
7+
from integration_tests.env_variable_names import (
8+
SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN,
9+
)
10+
from integration_tests.helpers import async_test, is_not_specified
11+
from slack_sdk.http_retry import RateLimitErrorRetryHandler
12+
from slack_sdk.http_retry.builtin_async_handlers import AsyncRateLimitErrorRetryHandler
13+
from slack_sdk.web import WebClient
14+
from slack_sdk.web.async_client import AsyncWebClient
15+
16+
17+
class TestWebClient(unittest.TestCase):
18+
"""Runs integration tests with real Slack API"""
19+
20+
def setUp(self):
21+
self.logger = logging.getLogger(__name__)
22+
self.org_admin_token = os.environ[SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN]
23+
self.sync_client: WebClient = WebClient(token=self.org_admin_token)
24+
self.sync_client.retry_handlers.append(RateLimitErrorRetryHandler(max_retry_count=2))
25+
self.async_client: AsyncWebClient = AsyncWebClient(token=self.org_admin_token)
26+
self.async_client.retry_handlers.append(
27+
AsyncRateLimitErrorRetryHandler(max_retry_count=2)
28+
)
29+
30+
def tearDown(self):
31+
pass
32+
33+
@pytest.mark.skipif(condition=is_not_specified(), reason="execution can take long")
34+
def test_sync(self):
35+
client = self.sync_client
36+
for response in client.admin_users_session_list(limit=1):
37+
self.assertIsNotNone(response.get("active_sessions"))
38+
39+
@pytest.mark.skipif(condition=is_not_specified(), reason="execution can take long")
40+
@async_test
41+
async def test_async(self):
42+
client = self.async_client
43+
async for response in await client.admin_users_session_list(limit=1):
44+
self.assertIsNotNone(response.get("active_sessions"))

slack_sdk/http_retry/builtin_async_handlers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ async def prepare_for_next_attempt_async(
7070
if response is None:
7171
raise error
7272

73-
state.increment_current_attempt()
73+
state.next_attempt_requested = True
7474
retry_after_header_name: Optional[str] = None
7575
for k in response.headers.keys():
7676
if k.lower() == "retry-after":
@@ -85,6 +85,7 @@ async def prepare_for_next_attempt_async(
8585
int(response.headers.get(retry_after_header_name)[0]) + random.random()
8686
)
8787
await asyncio.sleep(duration)
88+
state.increment_current_attempt()
8889

8990

9091
def async_default_handlers() -> List[AsyncRetryHandler]:

slack_sdk/http_retry/builtin_handlers.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ def _can_retry(
3939
if error is None:
4040
return False
4141

42+
if isinstance(error, URLError):
43+
if response is not None:
44+
return False # status 40x
45+
4246
for error_type in self.error_types_to_do_retries:
4347
if isinstance(error, error_type):
4448
return True
@@ -69,7 +73,7 @@ def prepare_for_next_attempt(
6973
if response is None:
7074
raise error
7175

72-
state.increment_current_attempt()
76+
state.next_attempt_requested = True
7377
retry_after_header_name: Optional[str] = None
7478
for k in response.headers.keys():
7579
if k.lower() == "retry-after":
@@ -84,3 +88,4 @@ def prepare_for_next_attempt(
8488
int(response.headers.get(retry_after_header_name)[0]) + random.random()
8589
)
8690
time.sleep(duration)
91+
state.increment_current_attempt()

slack_sdk/http_retry/response.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ class HttpResponse:
1212
def __init__(
1313
self,
1414
*,
15-
status_code: int,
15+
status_code: Union[int, str],
1616
headers: Dict[str, Union[str, List[str]]],
1717
body: Optional[Dict[str, Any]] = None,
1818
data: Optional[bytes] = None,
1919
):
20-
self.status_code = status_code
20+
self.status_code = int(status_code)
2121
self.headers = {
2222
k: v if isinstance(v, list) else [v] for k, v in headers.items()
2323
}

slack_sdk/web/async_internal_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,15 @@ def convert_params(values: dict) -> dict:
143143
res,
144144
)
145145

146+
if logger.level <= logging.DEBUG:
147+
body = data if isinstance(data, dict) else "(binary)"
148+
logger.debug(
149+
"Received the following response - "
150+
f"status: {res.status}, "
151+
f"headers: {dict(res.headers)}, "
152+
f"body: {body}"
153+
)
154+
146155
if res.status == 429:
147156
for handler in retry_handlers:
148157
if await handler.can_retry_async(

slack_sdk/web/base_client.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,10 +522,24 @@ def _perform_urllib_http_request_internal(
522522
if resp.headers.get_content_type() == "application/gzip":
523523
# admin.analytics.getFile
524524
body: bytes = resp.read()
525+
if self._logger.level <= logging.DEBUG:
526+
self._logger.debug(
527+
"Received the following response - "
528+
f"status: {resp.code}, "
529+
f"headers: {dict(resp.headers)}, "
530+
f"body: (binary)"
531+
)
525532
return {"status": resp.code, "headers": resp.headers, "body": body}
526533

527534
charset = resp.headers.get_content_charset() or "utf-8"
528535
body: str = resp.read().decode(charset) # read the response body here
536+
if self._logger.level <= logging.DEBUG:
537+
self._logger.debug(
538+
"Received the following response - "
539+
f"status: {resp.code}, "
540+
f"headers: {dict(resp.headers)}, "
541+
f"body: {body}"
542+
)
529543
return {"status": resp.code, "headers": resp.headers, "body": body}
530544
raise SlackRequestError(f"Invalid URL detected: {url}")
531545

slack_sdk/web/slack_response.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,14 +186,6 @@ def validate(self):
186186
Raises:
187187
SlackApiError: The request to the Slack API failed.
188188
"""
189-
if self._logger.level <= logging.DEBUG:
190-
body = self.data if isinstance(self.data, dict) else "(binary)"
191-
self._logger.debug(
192-
"Received the following response - "
193-
f"status: {self.status_code}, "
194-
f"headers: {dict(self.headers)}, "
195-
f"body: {body}"
196-
)
197189
if (
198190
self.status_code == 200
199191
and self.data

0 commit comments

Comments
 (0)