main
1from __future__ import annotations
2
3import io
4import os
5import pathlib
6from typing import overload
7from typing_extensions import TypeGuard
8
9import anyio
10
11from ._types import (
12 FileTypes,
13 FileContent,
14 RequestFiles,
15 HttpxFileTypes,
16 Base64FileInput,
17 HttpxFileContent,
18 HttpxRequestFiles,
19)
20from ._utils import is_tuple_t, is_mapping_t, is_sequence_t
21
22
23def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
24 return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
25
26
27def is_file_content(obj: object) -> TypeGuard[FileContent]:
28 return (
29 isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
30 )
31
32
33def assert_is_file_content(obj: object, *, key: str | None = None) -> None:
34 if not is_file_content(obj):
35 prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`"
36 raise RuntimeError(
37 f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/openai/openai-python/tree/main#file-uploads"
38 ) from None
39
40
41@overload
42def to_httpx_files(files: None) -> None: ...
43
44
45@overload
46def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...
47
48
49def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
50 if files is None:
51 return None
52
53 if is_mapping_t(files):
54 files = {key: _transform_file(file) for key, file in files.items()}
55 elif is_sequence_t(files):
56 files = [(key, _transform_file(file)) for key, file in files]
57 else:
58 raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")
59
60 return files
61
62
63def _transform_file(file: FileTypes) -> HttpxFileTypes:
64 if is_file_content(file):
65 if isinstance(file, os.PathLike):
66 path = pathlib.Path(file)
67 return (path.name, path.read_bytes())
68
69 return file
70
71 if is_tuple_t(file):
72 return (file[0], read_file_content(file[1]), *file[2:])
73
74 raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
75
76
77def read_file_content(file: FileContent) -> HttpxFileContent:
78 if isinstance(file, os.PathLike):
79 return pathlib.Path(file).read_bytes()
80 return file
81
82
83@overload
84async def async_to_httpx_files(files: None) -> None: ...
85
86
87@overload
88async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...
89
90
91async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
92 if files is None:
93 return None
94
95 if is_mapping_t(files):
96 files = {key: await _async_transform_file(file) for key, file in files.items()}
97 elif is_sequence_t(files):
98 files = [(key, await _async_transform_file(file)) for key, file in files]
99 else:
100 raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence")
101
102 return files
103
104
105async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
106 if is_file_content(file):
107 if isinstance(file, os.PathLike):
108 path = anyio.Path(file)
109 return (path.name, await path.read_bytes())
110
111 return file
112
113 if is_tuple_t(file):
114 return (file[0], await async_read_file_content(file[1]), *file[2:])
115
116 raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
117
118
119async def async_read_file_content(file: FileContent) -> HttpxFileContent:
120 if isinstance(file, os.PathLike):
121 return await anyio.Path(file).read_bytes()
122
123 return file