From c4bde830735559ce7e84fdd4bc831da9f15767d6 Mon Sep 17 00:00:00 2001 From: allen-pattern <117094633+allen-pattern@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:13:57 -0500 Subject: [PATCH 1/2] Support pathlike objects in upload util Also switch to inbuilt filename encode helper, since it accepts paths --- slack_sdk/web/async_client.py | 2 +- slack_sdk/web/client.py | 2 +- slack_sdk/web/internal_utils.py | 8 ++++---- slack_sdk/web/legacy_client.py | 2 +- tests/slack_sdk/web/test_internal_utils.py | 8 ++++++++ 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/slack_sdk/web/async_client.py b/slack_sdk/web/async_client.py index b7ea40771..dadde5a49 100644 --- a/slack_sdk/web/async_client.py +++ b/slack_sdk/web/async_client.py @@ -3749,7 +3749,7 @@ async def files_upload_v2( *, # for sending a single file filename: Optional[str] = None, # you can skip this only when sending along with content parameter - file: Optional[Union[str, bytes, IOBase]] = None, + file: Optional[Union[str, bytes, IOBase, os.PathLike]] = None, content: Optional[Union[str, bytes]] = None, title: Optional[str] = None, alt_txt: Optional[str] = None, diff --git a/slack_sdk/web/client.py b/slack_sdk/web/client.py index 55e9763de..433e447c2 100644 --- a/slack_sdk/web/client.py +++ b/slack_sdk/web/client.py @@ -3739,7 +3739,7 @@ def files_upload_v2( *, # for sending a single file filename: Optional[str] = None, # you can skip this only when sending along with content parameter - file: Optional[Union[str, bytes, IOBase]] = None, + file: Optional[Union[str, bytes, IOBase, os.PathLike]] = None, content: Optional[Union[str, bytes]] = None, title: Optional[str] = None, alt_txt: Optional[str] = None, diff --git a/slack_sdk/web/internal_utils.py b/slack_sdk/web/internal_utils.py index 6a87b914e..ca76e3555 100644 --- a/slack_sdk/web/internal_utils.py +++ b/slack_sdk/web/internal_utils.py @@ -317,8 +317,8 @@ def _to_v2_file_upload_item(upload_file: Dict[str, Any]) -> Dict[str, Optional[A content = upload_file.get("content") data: Optional[bytes] = None if file is not None: - if isinstance(file, str): # filepath - with open(file.encode("utf-8", "ignore"), "rb") as readable: + if isinstance(file, (str, os.PathLike)): # filepath + with open(os.fsencode(file), "rb") as readable: data = readable.read() elif isinstance(file, bytes): data = file @@ -339,8 +339,8 @@ def _to_v2_file_upload_item(upload_file: Dict[str, Any]) -> Dict[str, Optional[A filename = upload_file.get("filename") if filename is None: # use the local filename if filename is missing - if isinstance(file, str): - filename = file.split(os.path.sep)[-1] + if isinstance(file, (str, os.PathLike)): + filename = os.fspath(file).split(os.path.sep)[-1] else: filename = "Uploaded file" diff --git a/slack_sdk/web/legacy_client.py b/slack_sdk/web/legacy_client.py index 18f745632..2caa6db0a 100644 --- a/slack_sdk/web/legacy_client.py +++ b/slack_sdk/web/legacy_client.py @@ -3751,7 +3751,7 @@ def files_upload_v2( *, # for sending a single file filename: Optional[str] = None, # you can skip this only when sending along with content parameter - file: Optional[Union[str, bytes, IOBase]] = None, + file: Optional[Union[str, bytes, IOBase, os.PathLike]] = None, content: Optional[Union[str, bytes]] = None, title: Optional[str] = None, alt_txt: Optional[str] = None, diff --git a/tests/slack_sdk/web/test_internal_utils.py b/tests/slack_sdk/web/test_internal_utils.py index 1740841f3..ac7704b30 100644 --- a/tests/slack_sdk/web/test_internal_utils.py +++ b/tests/slack_sdk/web/test_internal_utils.py @@ -1,6 +1,7 @@ import json import unittest from io import BytesIO +from pathlib import Path from typing import Dict, Sequence, Union import pytest @@ -101,6 +102,13 @@ def test_files_upload_v2_issue_1356(self): file_io_item = _to_v2_file_upload_item({"file": file_io, "filename": "foo.txt"}) assert file_io_item.get("filename") == "foo.txt" + def test_to_v2_file_upload_item_can_accept_file_as_path(self): + filepath = "tests/slack_sdk/web/test_internal_utils.py" + upload_item_str = _to_v2_file_upload_item({"file": filepath}) + upload_item_path = _to_v2_file_upload_item({"file": Path(filepath)}) + assert upload_item_path == upload_item_str + assert upload_item_str.get("filename") == "test_internal_utils.py" + def test_next_cursor_is_present(self): assert _next_cursor_is_present({"next_cursor": "next-page"}) is True assert _next_cursor_is_present({"next_cursor": ""}) is False From 51a7c1247175402a85f0323198928f7689fc1cc3 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Fri, 14 Mar 2025 14:06:49 -0400 Subject: [PATCH 2/2] Add an integration test --- integration_tests/web/test_files_upload_v2.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/integration_tests/web/test_files_upload_v2.py b/integration_tests/web/test_files_upload_v2.py index f660f2097..4100051b4 100644 --- a/integration_tests/web/test_files_upload_v2.py +++ b/integration_tests/web/test_files_upload_v2.py @@ -1,5 +1,6 @@ import logging import os +from pathlib import Path import unittest from io import BytesIO @@ -59,6 +60,18 @@ def test_uploading_bytes_io(self): self.assertIsNotNone(upload.get("files")[0].get("id")) self.assertIsNotNone(upload.get("files")[0].get("title")) + def test_uploading_text_files_path(self): + client = self.sync_client + file = __file__ + upload = client.files_upload_v2( + channel=self.channel_id, + file=Path(file), + title="Test code", + ) + self.assertIsNotNone(upload) + self.assertIsNotNone(upload.get("files")[0].get("id")) + self.assertIsNotNone(upload.get("files")[0].get("title")) + def test_uploading_multiple_files(self): client = self.sync_client file = __file__