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()
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()
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()
def _build_body(original_body: Dict[str, any]) -> Dict[str, any]: body = {k: v for k, v in original_body.items() if v is not None} body = convert_bool_to_0_or_1(body) _parse_web_class_objects(body) return body
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()