From 419b4c1f60e6d18d6433104dd6d3855123f15801 Mon Sep 17 00:00:00 2001 From: qozle Date: Sun, 5 Apr 2026 01:21:58 -0400 Subject: [PATCH] fix(files): resolve PathLike inside FileTypes tuples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `is_file_content()` included `isinstance(obj, tuple)` in its guard, but `FileContent` is `Union[IO[bytes], bytes, PathLike[str]]` — tuples are not file content, they're `FileTypes` variants. This made the `is_tuple_t(file)` branch in `_transform_file` and `_async_transform_file` unreachable for tuple inputs, so `PathLike` objects in the second position of a `(name, PathLike, content_type)` tuple were returned as-is instead of being read with `read_file_content()`. httpx then received a `PathLike` where it expected `bytes` or `IO[bytes]`. Removing `isinstance(obj, tuple)` from `is_file_content` lets tuples fall through to the correct `is_tuple_t` branch, which calls `read_file_content(file[1])` and resolves the `PathLike`. Also corrects the `assert_is_file_content` error message which incorrectly listed tuples as valid `FileContent`. Fixes #1318 --- src/anthropic/_files.py | 6 ++---- tests/test_files.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/anthropic/_files.py b/src/anthropic/_files.py index f2a6f94e..5bd0cb07 100644 --- a/src/anthropic/_files.py +++ b/src/anthropic/_files.py @@ -25,16 +25,14 @@ def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: def is_file_content(obj: object) -> TypeGuard[FileContent]: - return ( - isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) - ) + return isinstance(obj, bytes) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) def assert_is_file_content(obj: object, *, key: str | None = None) -> None: if not is_file_content(obj): prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" raise RuntimeError( - f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/anthropics/anthropic-sdk-python/tree/main#file-uploads" + f"{prefix} to be bytes, an io.IOBase instance, or a PathLike but received {type(obj)} instead. See https://github.com/anthropics/anthropic-sdk-python/tree/main#file-uploads" ) from None diff --git a/tests/test_files.py b/tests/test_files.py index 4b118fe0..ead9e279 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -42,6 +42,17 @@ async def test_async_tuple_input() -> None: assert result == IsList(IsTuple("file", IsTuple("README.md", IsBytes()))) +def test_pathlike_in_tuple() -> None: + result = to_httpx_files({"file": ("custom_name.txt", readme_path, "text/plain")}) + assert result == IsDict({"file": IsTuple("custom_name.txt", IsBytes(), "text/plain")}) + + +@pytest.mark.asyncio +async def test_async_pathlike_in_tuple() -> None: + result = await async_to_httpx_files({"file": ("custom_name.txt", readme_path, "text/plain")}) + assert result == IsDict({"file": IsTuple("custom_name.txt", IsBytes(), "text/plain")}) + + def test_string_not_allowed() -> None: with pytest.raises(TypeError, match="Expected file types input to be a FileContent type or to be a tuple"): to_httpx_files(