diff options
-rw-r--r-- | slack/error.py | 32 | ||||
-rw-r--r-- | slack/http.py | 10 | ||||
-rw-r--r-- | slack/slack_api.py | 100 | ||||
-rw-r--r-- | slack/slack_conversation.py | 6 | ||||
-rw-r--r-- | slack/slack_user.py | 6 | ||||
-rw-r--r-- | slack/slack_workspace.py | 14 | ||||
-rw-r--r-- | slack/task.py | 18 | ||||
-rw-r--r-- | tests/test_http_request.py | 8 |
8 files changed, 123 insertions, 71 deletions
diff --git a/slack/error.py b/slack/error.py new file mode 100644 index 0000000..8a8c1b9 --- /dev/null +++ b/slack/error.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Mapping, Union + +if TYPE_CHECKING: + from slack_api.slack_error import SlackErrorResponse + + from slack.slack_workspace import SlackWorkspace + + +class HttpError(Exception): + def __init__(self, url: str, return_code: int, http_status_code: int, error: str): + super().__init__() + self.url = url + self.return_code = return_code + self.http_status_code = http_status_code + self.error = error + + +class SlackApiError(Exception): + def __init__( + self, + workspace: SlackWorkspace, + method: str, + response: SlackErrorResponse, + params: Mapping[str, Union[str, int, bool]] = {}, + ): + super().__init__() + self.workspace = workspace + self.method = method + self.params = params + self.response = response diff --git a/slack/http.py b/slack/http.py index a0baa74..820abb8 100644 --- a/slack/http.py +++ b/slack/http.py @@ -7,20 +7,12 @@ from typing import Dict import weechat +from slack.error import HttpError from slack.log import LogLevel, log from slack.task import FutureProcess, sleep, weechat_task_cb from slack.util import get_callback_name -class HttpError(Exception): - def __init__(self, url: str, return_code: int, http_status: int, error: str): - super().__init__() - self.url = url - self.return_code = return_code - self.http_status = http_status - self.error = error - - def available_file_descriptors(): num_current_file_descriptors = len(os.listdir("/proc/self/fd/")) max_file_descriptors = min(resource.getrlimit(resource.RLIMIT_NOFILE)) diff --git a/slack/slack_api.py b/slack/slack_api.py index 492aae4..f68abd1 100644 --- a/slack/slack_api.py +++ b/slack/slack_api.py @@ -1,9 +1,10 @@ from __future__ import annotations import json -from typing import TYPE_CHECKING, Dict, Iterable, Union +from typing import TYPE_CHECKING, Iterable, Mapping, Union from urllib.parse import urlencode +from slack.error import SlackApiError from slack.http import http_request from slack.shared import shared @@ -18,6 +19,8 @@ if TYPE_CHECKING: from slack.slack_conversation import SlackConversation from slack.slack_workspace import SlackWorkspace +Params = Mapping[str, Union[str, int, bool]] + class SlackApi: def __init__(self, workspace: SlackWorkspace): @@ -30,7 +33,7 @@ class SlackApi: "cookie": self.workspace.config.api_cookies.value, # TODO: url_encode_if_not_encoded } - async def _fetch(self, method: str, params: Dict[str, Union[str, int]] = {}): + async def _fetch(self, method: str, params: Params = {}): url = f"https://api.slack.com/api/{method}" options = self._get_request_options() options["postfields"] = urlencode(params) @@ -45,30 +48,40 @@ class SlackApi: self, method: str, list_key: str, - params: Dict[str, Union[str, int]] = {}, + params: Params = {}, pages: int = -1, # negative or 0 means all pages ): response = await self._fetch(method, params) next_cursor = response.get("response_metadata", {}).get("next_cursor") if pages != 1 and next_cursor and response["ok"]: - params["cursor"] = next_cursor - next_pages = await self._fetch_list(method, list_key, params, pages - 1) + new_params = {**params, "cursor": next_cursor} + next_pages = await self._fetch_list(method, list_key, new_params, pages - 1) response[list_key].extend(next_pages[list_key]) return response return response - async def fetch_rtm_connect(self) -> SlackRtmConnectResponse: - return await self._fetch("rtm.connect") + async def fetch_rtm_connect(self): + method = "rtm.connect" + response: SlackRtmConnectResponse = await self._fetch(method) + if response["ok"] is False: + raise SlackApiError(self.workspace, method, response) + return response - async def fetch_conversations_history( - self, conversation: SlackConversation - ) -> SlackConversationsHistoryResponse: - return await self._fetch("conversations.history", {"channel": conversation.id}) + async def fetch_conversations_history(self, conversation: SlackConversation): + method = "conversations.history" + params = {"channel": conversation.id} + response: SlackConversationsHistoryResponse = await self._fetch(method, params) + if response["ok"] is False: + raise SlackApiError(self.workspace, method, response, params) + return response - async def fetch_conversations_info( - self, conversation: SlackConversation - ) -> SlackConversationsInfoResponse: - return await self._fetch("conversations.info", {"channel": conversation.id}) + async def fetch_conversations_info(self, conversation: SlackConversation): + method = "conversations.info" + params = {"channel": conversation.id} + response: SlackConversationsInfoResponse = await self._fetch(method, params) + if response["ok"] is False: + raise SlackApiError(self.workspace, method, response, params) + return response async def fetch_users_conversations( self, @@ -76,26 +89,51 @@ class SlackApi: exclude_archived: bool = True, limit: int = 1000, pages: int = -1, - ) -> SlackUsersConversationsResponse: - return await self._fetch_list( - "users.conversations", + ): + method = "users.conversations" + params = { + "types": types, + "exclude_archived": exclude_archived, + "limit": limit, + } + response: SlackUsersConversationsResponse = await self._fetch_list( + method, "channels", - { - "types": types, - "exclude_archived": exclude_archived, - "limit": limit, - }, + params, pages, ) + if response["ok"] is False: + raise SlackApiError(self.workspace, method, response, params) + return response - async def fetch_user_info(self, user_id: str) -> SlackUserInfoResponse: - return await self._fetch("users.info", {"user": user_id}) + async def fetch_user_info(self, user_id: str): + method = "users.info" + params = {"user": user_id} + response: SlackUserInfoResponse = await self._fetch(method, params) + if response["ok"] is False: + raise SlackApiError(self.workspace, method, response, params) + return response - async def fetch_users_info(self, user_ids: Iterable[str]) -> SlackUsersInfoResponse: - return await self._fetch("users.info", {"users": ",".join(user_ids)}) + async def fetch_users_info(self, user_ids: Iterable[str]): + method = "users.info" + params = {"users": ",".join(user_ids)} + response: SlackUsersInfoResponse = await self._fetch(method, params) + if response["ok"] is False: + raise SlackApiError(self.workspace, method, response, params) + return response - async def fetch_bot_info(self, bot_id: str) -> SlackBotInfoResponse: - return await self._fetch("bots.info", {"bot": bot_id}) + async def fetch_bot_info(self, bot_id: str): + method = "bots.info" + params = {"bot": bot_id} + response: SlackBotInfoResponse = await self._fetch(method, params) + if response["ok"] is False: + raise SlackApiError(self.workspace, method, response, params) + return response - async def fetch_bots_info(self, bot_ids: Iterable[str]) -> SlackBotsInfoResponse: - return await self._fetch("bots.info", {"bots": ",".join(bot_ids)}) + async def fetch_bots_info(self, bot_ids: Iterable[str]): + method = "bots.info" + params = {"bots": ",".join(bot_ids)} + response: SlackBotsInfoResponse = await self._fetch(method, params) + if response["ok"] is False: + raise SlackApiError(self.workspace, method, response, params) + return response diff --git a/slack/slack_conversation.py b/slack/slack_conversation.py index 8123df8..8fb1c0e 100644 --- a/slack/slack_conversation.py +++ b/slack/slack_conversation.py @@ -54,9 +54,6 @@ class SlackConversation: async def init(self): with self.loading(): info = await self._api.fetch_conversations_info(self) - if info["ok"] is False: - # TODO: Handle error - raise Exception("Failed fetching conversation info") info_channel = info["channel"] if info_channel["is_im"] is True: @@ -84,9 +81,6 @@ class SlackConversation: self.history_pending = True history = await self._api.fetch_conversations_history(self) - if history["ok"] is False: - # TODO: Handle error - raise Exception("Failed fetching conversation history") start = time.time() messages = [SlackMessage(self, message) for message in history["messages"]] diff --git a/slack/slack_user.py b/slack/slack_user.py index 6b9b056..b895872 100644 --- a/slack/slack_user.py +++ b/slack/slack_user.py @@ -49,9 +49,6 @@ class SlackUser: async def _set_info(self): info_response = await self._api.fetch_user_info(self.id) - if info_response["ok"] is False: - # TODO: Handle error - raise Exception("Failed fetching user info") self._info = info_response["user"] async def ensure_initialized(self): @@ -113,9 +110,6 @@ class SlackBot: async def _set_info(self): info_response = await self._api.fetch_bot_info(self.id) - if info_response["ok"] is False: - # TODO: Handle error - raise Exception("Failed fetching bot info") self._info = info_response["bot"] async def ensure_initialized(self): diff --git a/slack/slack_workspace.py b/slack/slack_workspace.py index 5074174..96740ec 100644 --- a/slack/slack_workspace.py +++ b/slack/slack_workspace.py @@ -54,9 +54,6 @@ class SlackUsers(Dict[str, Future[SlackUser]]): async def _fetch_items_info(self, item_ids: Iterable[str]): response = await self.workspace.api.fetch_users_info(item_ids) - if response["ok"] is False: - # TODO: Handle error - raise Exception("Failed fetching users") return {info["id"]: info for info in response["users"]} @@ -92,9 +89,6 @@ class SlackBots(Dict[str, Future[SlackBot]]): async def _fetch_items_info(self, item_ids: Iterable[str]): response = await self.workspace.api.fetch_bots_info(item_ids) - if response["ok"] is False: - # TODO: Handle error - raise Exception("Failed fetching bots") return {info["id"]: info for info in response["bots"]} @@ -119,10 +113,6 @@ class SlackWorkspace: async def connect(self): rtm_connect = await self.api.fetch_rtm_connect() - if rtm_connect["ok"] is False: - # TODO: Handle error - raise Exception("Failed fetching rtm.connect") - self.id = rtm_connect["team"]["id"] self.my_user = await self.users[rtm_connect["self"]["id"]] @@ -132,10 +122,6 @@ class SlackWorkspace: user_channels_response = await self.api.fetch_users_conversations( "public_channel" ) - if user_channels_response["ok"] is False: - # TODO: Handle error - raise Exception("Failed fetching conversations") - user_channels = user_channels_response["channels"] for channel in user_channels: diff --git a/slack/task.py b/slack/task.py index 9ebcad5..ac3677b 100644 --- a/slack/task.py +++ b/slack/task.py @@ -15,6 +15,8 @@ from uuid import uuid4 import weechat +from slack.error import HttpError, SlackApiError +from slack.log import print_error from slack.shared import shared from slack.util import get_callback_name @@ -74,7 +76,21 @@ def weechat_task_cb(data: str, *args: Any) -> int: def task_runner(task: Task[Any], response: Any): while True: try: - future = task.coroutine.send(response) + try: + future = task.coroutine.send(response) + except HttpError as e: + print_error( + f"Error calling URL {e.url}: return code: {e.return_code}, " + f"http status code: {e.http_status_code}, error: {e.error}" + ) + return + except SlackApiError as e: + print_error( + f"Error from Slack API method {e.method} for workspace " + f"{e.workspace.name}: {e.response}" + ) + return + if future.finished: response = future.result else: diff --git a/tests/test_http_request.py b/tests/test_http_request.py index 300882d..3c74e1f 100644 --- a/tests/test_http_request.py +++ b/tests/test_http_request.py @@ -44,7 +44,7 @@ def test_http_request_error_process_return_code(): assert excinfo.value.url == url assert excinfo.value.return_code == -2 - assert excinfo.value.http_status == 0 + assert excinfo.value.http_status_code == 0 assert excinfo.value.error == "" @@ -58,7 +58,7 @@ def test_http_request_error_process_stderr(): assert excinfo.value.url == url assert excinfo.value.return_code == 0 - assert excinfo.value.http_status == 0 + assert excinfo.value.http_status_code == 0 assert excinfo.value.error == "err" @@ -75,7 +75,7 @@ def test_http_request_error_process_http(): assert excinfo.value.url == url assert excinfo.value.return_code == 0 - assert excinfo.value.http_status == 400 + assert excinfo.value.http_status_code == 400 assert excinfo.value.error == response @@ -110,7 +110,7 @@ def test_http_request_error_retry_error(): assert excinfo.value.url == url assert excinfo.value.return_code == -2 - assert excinfo.value.http_status == 0 + assert excinfo.value.http_status_code == 0 assert excinfo.value.error == "" |