1+ import inspect
12from typing import Any
23
34
5+ class FileUpload :
6+ """Unified file upload container with framework-agnostic API"""
7+
8+ __slots__ = ("filename" , "content_type" , "size" , "_file" )
9+
10+ def __init__ (
11+ self ,
12+ filename : str ,
13+ content_type : str | None = None ,
14+ size : int | None = None ,
15+ file : Any = None ,
16+ ):
17+ self .filename = filename
18+ self .content_type = content_type or "application/octet-stream"
19+ self .size = size
20+ self ._file = file
21+
22+ @property
23+ def file (self ) -> Any :
24+ """Access native framework file object"""
25+ return self ._file
26+
27+ def read (self ) -> bytes :
28+ """Read entire file content (sync frameworks)"""
29+ if isinstance (self ._file , bytes ):
30+ return self ._file
31+ if hasattr (self ._file , "read" ):
32+ result = self ._file .read ()
33+ if isinstance (result , bytes ):
34+ return result
35+ return result .encode ("utf-8" ) if isinstance (result , str ) else result
36+ raise NotImplementedError ("File object does not support synchronous read" )
37+
38+ async def aread (self ) -> bytes :
39+ """Read entire file content (async frameworks)"""
40+ if isinstance (self ._file , bytes ):
41+ return self ._file
42+ if hasattr (self ._file , "read" ):
43+ result = self ._file .read ()
44+ if inspect .iscoroutine (result ):
45+ data = await result
46+ if isinstance (data , bytes ):
47+ return data
48+ return data .encode ("utf-8" ) if isinstance (data , str ) else data
49+ if isinstance (result , bytes ):
50+ return result
51+ return result .encode ("utf-8" ) if isinstance (result , str ) else result
52+ raise NotImplementedError ("File object does not support read" )
53+
54+ def __repr__ (self ) -> str :
55+ return (
56+ f"FileUpload(filename={ self .filename !r} , "
57+ f"content_type={ self .content_type !r} , size={ self .size } )"
58+ )
59+
60+
461class Response :
562 """Custom response with headers"""
663
@@ -26,7 +83,7 @@ def __init__(
2683 cookies : dict [str , str ] = None ,
2784 body : Any = None ,
2885 form_data : dict [str , Any ] = None ,
29- files : dict [str , bytes ] = None ,
86+ files : dict [str , FileUpload | list [ FileUpload ] ] = None ,
3087 ):
3188 self .path_params = path_params or {}
3289 self .query_params = query_params or {}
0 commit comments