Exemple #1
0
    def iter_rate(response: SlackResponse):
        """
        Rate-limited aware iterator generator ... iterator rate ... iter rate.
        Puns are important in life.
        """

        # SlackResponse needs to initialize the internal iterator count and initial data
        response.__iter__()

        while True:
            try:
                yield next(response)
            except StopIteration:
                # Reached the end of the Slack data, time to end the loop
                break
            except SlackApiError as ex:
                # According to the internals of SlackResponse, the internal data and iterator counter
                # will not be updated if the reaponse._client._request raises an exception. Thus, it should
                # be possible to just wait the required cool-off period and the next iteration of the loop
                # should still be at the same spot.
                if ex.response.status_code == 429 and "Retry-After" in ex.response.headers:
                    # If Slack is rate-limiting our calls, wait the required time
                    wait_period = float(ex.response.headers["Retry-After"])
                    warning(
                        "Rate limited, waiting for {}s".format(wait_period))
                    time.sleep(wait_period)
                else:
                    # Only suppress the SlackApiError when we're rate-limited
                    raise
Exemple #2
0
def mock_rtm_response():
    coro = Mock(name="RTMResponse")
    data = {
        "client": ANY,
        "http_verb": ANY,
        "api_url": ANY,
        "req_args": ANY,
        "data": {
            "ok": True,
            "url": "ws://localhost:8765",
            "self": {
                "id": "U01234ABC",
                "name": "robotoverlord"
            },
            "team": {
                "domain": "exampledomain",
                "id": "T123450FP",
                "name": "ExampleName",
            },
        },
        "headers": ANY,
        "status_code": 200,
    }
    coro.return_value = SlackResponse(**data)
    corofunc = Mock(name="mock_rtm_response",
                    side_effect=asyncio.coroutine(coro))
    corofunc.coro = coro
    return corofunc
Exemple #3
0
    async def _send(self, http_verb, api_url, req_args):
        """Sends the request out for transmission.

        Args:
            http_verb (str): The HTTP verb. e.g. 'GET' or 'POST'.
            api_url (str): The Slack API url. e.g. 'https://slack.com/api/chat.postMessage'
            req_args (dict): The request arguments to be attached to the request.
            e.g.
            {
                json: {
                    'attachments': [{"pretext": "pre-hello", "text": "text-world"}],
                    'channel': '#random'
                }
            }
        Returns:
            The response parsed into a SlackResponse object.
        """
        res = await self._request(http_verb=http_verb,
                                  api_url=api_url,
                                  req_args=req_args)
        data = {
            "client": self,
            "http_verb": http_verb,
            "api_url": api_url,
            "req_args": req_args,
        }
        return SlackResponse(**{**data, **res}).validate()
Exemple #4
0
def test_send_message_raise_error_false():
    channel = "#fake_channel_name"
    message = "This is a test message."
    response_data = {
        "type": "SlackError",
        "text": f'The message: "{message}" failed to send to channel: {channel}',
        "details": "channel_not_found",
    }
    mock_client = WebClient()
    mock_response = SlackResponse(
        client=mock_client,
        http_verb="POST",
        api_url="http://localhost:3000/api.test",
        req_args={},
        data=response_data,
        headers={},
        status_code=200,
    )
    mock_client.chat_postMessage = MagicMock(return_value=mock_response)
    response = notifications.slack.send_message(
        slack_client=mock_client, channel=channel, message=message, raise_error=False,
    )
    assert response == {
        "type": "SlackError",
        "text": f'The message: "{message}" failed to send to channel: {channel}',
        "details": "channel_not_found",
    }
Exemple #5
0
def test_roulette_endpoint():
    mock_client = Mock(LazySlackClient)

    response_data = {
        "ok": True,
        "members": ["U023BECGF", "U061F7AUR", "W012A3CDE"],
        "response_metadata": {
            "next_cursor": "e3VzZXJfaWQ6IFcxMjM0NTY3fQ=="
        }
    }
    response = SlackResponse(client=mock_client,
                             http_verb="POST",
                             api_url="",
                             req_args={},
                             data=response_data,
                             headers={},
                             status_code=200)
    app.slack_client = mock_client
    mock_client.conversations_members.return_value = response

    app.current_request = www_request_of(
        data={"channel_id": "1234"},
        headers={"Content-Type": "application/x-www-form-urlencoded"})
    resp = roulette()
    assert resp
    text = resp["text"]
    assert "How about chats between" in text
    assert BULLET in text
Exemple #6
0
def _slack_response(token, data):
    return SlackResponse(client=WebClient(token=token),
                         http_verb="POST",
                         api_url="http://localhost:3000/api.test",
                         req_args={},
                         data=data,
                         headers={},
                         status_code=200)
Exemple #7
0
 def build_slack_response(self, data: Dict[str, Any]) -> SlackResponse:
     """Build a fake SlackResponse containing the given data."""
     response_data = copy.deepcopy(data)
     if "ok" not in response_data:
         response_data["ok"] = True
     return SlackResponse(
         client=self,
         http_verb="GET",
         api_url="/mock",
         req_args={},
         data=response_data,
         headers={},
         status_code=200,
     )
Exemple #8
0
    async def post_message_to_slack(self,
                                    message: SlackMessage) -> SlackResponse:
        if not message:
            return None

        request_content = {
            "token": self.options.slack_bot_token,
            "channel": message.channel,
            "text": message.text,
        }

        if message.thread_ts:
            request_content["thread_ts"] = message.thread_ts

        if message.blocks:
            request_content["blocks"] = json.dumps(message.blocks)

        session = aiohttp.ClientSession(
            timeout=aiohttp.ClientTimeout(total=30), )

        http_verb = "POST"
        api_url = POST_EPHEMERAL_MESSAGE_URL if message.ephemeral else POST_MESSAGE_URL
        req_args = {"data": request_content}

        async with session.request(http_verb, api_url, **req_args) as res:
            response_content = {}
            try:
                response_content = await res.json()
            except aiohttp.ContentTypeError:
                pass

            response_data = {
                "data": response_content,
                "headers": res.headers,
                "status_code": res.status,
            }

            data = {
                "client": self,
                "http_verb": http_verb,
                "api_url": api_url,
                "req_args": req_args,
            }
            response = SlackResponse(**{**data, **response_data}).validate()

        await session.close()

        return response
    async def _send(self, http_verb, api_url, req_args):
        """Sends the request out for transmission.

        Args:
            http_verb (str): The HTTP verb. e.g. 'GET' or 'POST'.
            api_url (str): The Slack API url. e.g. 'https://slack.com/api/chat.postMessage'
            req_args (dict): The request arguments to be attached to the request.
            e.g.
            {
                json: {
                    'attachments': [{"pretext": "pre-hello", "text": "text-world"}],
                    'channel': '#random'
                }
            }
        Returns:
            The response parsed into a SlackResponse object.
        """
        open_files = []
        files = req_args.pop("files", None)
        if files is not None:
            for k, v in files.items():
                if isinstance(v, str):
                    f = open(v.encode("utf-8", "ignore"), "rb")
                    open_files.append(f)
                    req_args["data"].update({k: f})
                else:
                    req_args["data"].update({k: v})

        if "params" in req_args:
            # True/False -> "1"/"0"
            req_args["params"] = convert_bool_to_0_or_1(req_args["params"])

        res = await self._request(
            http_verb=http_verb, api_url=api_url, req_args=req_args
        )

        for f in open_files:
            f.close()

        data = {
            "client": self,
            "http_verb": http_verb,
            "api_url": api_url,
            "req_args": req_args,
            "use_sync_aiohttp": self.use_sync_aiohttp,
        }
        return SlackResponse(**{**data, **res}).validate()
Exemple #10
0
def _mock_response(channel: str, text: str) -> SlackResponse:
    """
    Simulate response for a call to chat_PostMessage() in the Slack WebClient
    https://python-slackclient.readthedocs.io/en/latest/basic_usage.html

    Args:
        channel: The slack channel to post to (ignored in mock response)
        text: The body of the message in the response
    Returns:
        A JSON SlackResponse object
    """
    mock_client = WebClient()
    response = {
        "bot_id": "BOT_ID",
        "type": "message",
        "text": text,
        "user": "******",
        "team": "TEAM",
        "bot_profile": {
            "id": "BOT_ID",
            "deleted": False,
            "name": "Tamr Jobs Watch",
            "updated": 1593130492,
            "app_id": "APP_ID",
            "icons": {
                "image_36":
                "https://a.slack-edge.com/80588/img/plugins/app/bot_36.png",
                "image_48":
                "https://a.slack-edge.com/80588/img/plugins/app/bot_48.png",
                "image_72":
                "https://a.slack-edge.com/80588/img/plugins/app/service_72.png",
            },
            "team_id": "TEAM",
        },
    }
    response_data = {"message": response}
    mock_response = SlackResponse(
        client=mock_client,
        http_verb="POST",
        api_url="http://localhost:3000/api.test",
        req_args={},
        data=response_data,
        headers={},
        status_code=200,
    )
    return mock_response
Exemple #11
0
    def test_issue_559(self):
        response = SlackResponse(
            client=WebClient(token="xoxb-dummy"),
            http_verb="POST",
            api_url="http://localhost:3000/api.test",
            req_args={},
            data={
                "ok": True,
                "args": {
                    "hello": "world"
                }
            },
            headers={},
            status_code=200,
        )

        self.assertTrue("ok" in response.data)
        self.assertTrue("args" in response.data)
        self.assertFalse("error" in response.data)
    async def _send(
        self, http_verb: str, api_url: str, req_args: dict
    ) -> SlackResponse:
        """Sends the request out for transmission.

        Args:
            http_verb (str): The HTTP verb. e.g. 'GET' or 'POST'.
            api_url (str): The Slack API url. e.g. 'https://slack.com/api/chat.postMessage'
            req_args (dict): The request arguments to be attached to the request.
            e.g.
            {
                json: {
                    'attachments': [{"pretext": "pre-hello", "text": "text-world"}],
                    'channel': '#random'
                }
            }
        Returns:
            The response parsed into a SlackResponse object.
        """
        open_files = _files_to_data(req_args)
        try:
            if "params" in req_args:
                # True/False -> "1"/"0"
                req_args["params"] = convert_bool_to_0_or_1(req_args["params"])

            res = await self._request(
                http_verb=http_verb, api_url=api_url, req_args=req_args
            )
        finally:
            for f in open_files:
                f.close()

        data = {
            "client": self,
            "http_verb": http_verb,
            "api_url": api_url,
            "req_args": req_args,
            "use_sync_aiohttp": self.use_sync_aiohttp,
        }
        return SlackResponse(**{**data, **res}).validate()
Exemple #13
0
    def _urllib_api_call(
        self,
        *,
        token: str = None,
        url: str,
        query_params: Dict[str, str] = {},
        json_body: Dict = {},
        body_params: Dict[str, str] = {},
        files: Dict[str, io.BytesIO] = {},
        additional_headers: Dict[str, str] = {},
    ) -> SlackResponse:
        """Performs a Slack API request and returns the result.

        :param token: Slack API Token (either bot token or user token)
        :param url: a complete URL (e.g., https://www.slack.com/api/chat.postMessage)
        :param query_params: query string
        :param json_body: json data structure (it's still a dict at this point),
            if you give this argument, body_params and files will be skipped
        :param body_params: form params
        :param files: files to upload
        :param additional_headers: request headers to append
        :return: API response
        """

        files_to_close: List[BinaryIO] = []
        try:
            # True/False -> "1"/"0"
            query_params = convert_bool_to_0_or_1(query_params)
            body_params = convert_bool_to_0_or_1(body_params)

            if self._logger.level <= logging.DEBUG:

                def convert_params(values: dict) -> dict:
                    if not values or not isinstance(values, dict):
                        return {}
                    return {
                        k: ("(bytes)" if isinstance(v, bytes) else v)
                        for k, v in values.items()
                    }

                headers = {
                    k: "(redacted)" if k.lower() == "authorization" else v
                    for k, v in additional_headers.items()
                }
                self._logger.debug(
                    f"Sending a request - url: {url}, "
                    f"query_params: {convert_params(query_params)}, "
                    f"body_params: {convert_params(body_params)}, "
                    f"files: {convert_params(files)}, "
                    f"json_body: {json_body}, "
                    f"headers: {headers}")

            request_data = {}
            if files is not None and isinstance(files,
                                                dict) and len(files) > 0:
                if body_params:
                    for k, v in body_params.items():
                        request_data.update({k: v})

                for k, v in files.items():
                    if isinstance(v, str):
                        f: BinaryIO = open(v.encode("utf-8", "ignore"), "rb")
                        files_to_close.append(f)
                        request_data.update({k: f})
                    elif isinstance(v, (bytearray, bytes)):
                        request_data.update({k: io.BytesIO(v)})
                    else:
                        request_data.update({k: v})

            request_headers = self._build_urllib_request_headers(
                token=token or self.token,
                has_json=json is not None,
                has_files=files is not None,
                additional_headers=additional_headers,
            )
            request_args = {
                "headers": request_headers,
                "data": request_data,
                "params": body_params,
                "files": files,
                "json": json_body,
            }
            if query_params:
                q = urlencode(query_params)
                url = f"{url}&{q}" if "?" in url else f"{url}?{q}"

            response = self._perform_urllib_http_request(url=url,
                                                         args=request_args)
            if response.get("body"):
                try:
                    response_body_data: dict = json.loads(response["body"])
                except json.decoder.JSONDecodeError as e:
                    message = f"Failed to parse the response body: {str(e)}"
                    raise err.SlackApiError(message, response)
            else:
                response_body_data: dict = None

            if query_params:
                all_params = copy.copy(body_params)
                all_params.update(query_params)
            else:
                all_params = body_params
            request_args["params"] = all_params  # for backward-compatibility

            return SlackResponse(
                client=self,
                http_verb="POST",  # you can use POST method for all the Web APIs
                api_url=url,
                req_args=request_args,
                data=response_body_data,
                headers=dict(response["headers"]),
                status_code=response["status"],
                use_sync_aiohttp=False,
            ).validate()
        finally:
            for f in files_to_close:
                if not f.closed:
                    f.close()
Exemple #14
0
    def api_call(
            self,
            *,
            token: str = None,
            url: str,
            query_params: Dict[str, str] = dict(),
            json_body: Dict = dict(),
            body_params: Dict[str, str] = dict(),
            files: Dict[str, io.BytesIO] = dict(),
            additional_headers: Dict[str, str] = dict(),
    ) -> SlackResponse:
        """Performs a Slack API request and returns the result.

        :param token: Slack API Token (either bot token or user token)
        :param url: a complete URL (e.g., https://www.slack.com/api/chat.postMessage)
        :param query_params: query string
        :param json_body: json data structure (it's still a dict at this point),
            if you give this argument, body_params and files will be skipped
        :param body_params: form params
        :param files: files to upload
        :param additional_headers: request headers to append
        :return: API response
        """

        show_2020_01_deprecation(self._to_api_method(url))

        files_to_close: List[BinaryIO] = []
        try:
            # True/False -> "1"/"0"
            query_params = convert_bool_to_0_or_1(query_params)
            body_params = convert_bool_to_0_or_1(body_params)

            if self.logger.level <= logging.DEBUG:
                self.logger.debug(f"Slack API Request - url: {url}, "
                                  f"query_params: {query_params}, "
                                  f"json_body: {json_body}, "
                                  f"body_params: {body_params}, "
                                  f"files: {files}, "
                                  f"additional_headers: {additional_headers}")

            request_data = {}
            if files:
                if body_params:
                    for k, v in body_params.items():
                        request_data.update({k: v})

                for k, v in files.items():
                    if isinstance(v, str):
                        f: BinaryIO = open(v.encode("ascii", "ignore"), "rb")
                        files_to_close.append(f)
                        request_data.update({k: f})
                    else:
                        request_data.update({k: v})

            request_headers = self._build_request_headers(
                token=token or self.token,
                has_json=json is not None,
                has_files=files is not None,
                additional_headers=additional_headers,
            )
            request_args = {
                "headers": request_headers,
                "data": request_data,
                "params": body_params,
                "files": files,
                "json": json_body,
            }
            if query_params:
                q = urlencode(query_params)
                url = f"{url}&{q}" if "?" in url else f"{url}?{q}"

            response, response_body = self._perform_http_request(
                url=url, args=request_args)
            if response_body:
                response_body_data: dict = json.loads(response_body)
            else:
                response_body_data: dict = None

            if query_params:
                all_params = copy.copy(body_params)
                all_params.update(query_params)
            else:
                all_params = body_params
            request_args["params"] = all_params  # for backward-compatibility
            return SlackResponse(
                client=self.web_client,
                http_verb="POST",  # you can use POST method for all the Web APIs
                api_url=url,
                req_args=request_args,
                data=response_body_data,
                headers=dict(response.headers),
                status_code=response.status,
                use_sync_aiohttp=False,
            ).validate()
        finally:
            for f in files_to_close:
                if not f.closed:
                    f.close()