@@ -90,15 +90,19 @@ class FileResponse(StreamResponse):
90
90
91
91
def __init__ (
92
92
self ,
93
- path : PathLike ,
93
+ path : PathLike | io . BufferedReader ,
94
94
chunk_size : int = 256 * 1024 ,
95
95
status : int = 200 ,
96
96
reason : Optional [str ] = None ,
97
- headers : Optional [LooseHeaders ] = None ,
97
+ headers : Optional [LooseHeaders ] = None
98
98
) -> None :
99
99
super ().__init__ (status = status , reason = reason , headers = headers )
100
-
101
- self ._path = pathlib .Path (path )
100
+ if isinstance (path , io .BufferedReader ):
101
+ self ._io = path
102
+ self ._path = None
103
+ else :
104
+ self ._io = None
105
+ self ._path = pathlib .Path (path )
102
106
self ._chunk_size = chunk_size
103
107
104
108
def _seek_and_read (self , fobj : IO [Any ], offset : int , chunk_size : int ) -> bytes :
@@ -189,8 +193,6 @@ def _make_response(
189
193
file_path , st , file_encoding = self ._get_file_path_stat_encoding (
190
194
accept_encoding
191
195
)
192
- if not file_path :
193
- return _FileResponseResult .NOT_ACCEPTABLE , None , st , None
194
196
195
197
etag_value = f"{ st .st_mtime_ns :x} -{ st .st_size :x} "
196
198
@@ -220,6 +222,12 @@ def _make_response(
220
222
):
221
223
return _FileResponseResult .NOT_MODIFIED , None , st , file_encoding
222
224
225
+ if file_path is None :
226
+ if self ._io is None :
227
+ return _FileResponseResult .NOT_ACCEPTABLE , None , st , None
228
+
229
+ return _FileResponseResult .SEND_FILE , self ._io , st , file_encoding
230
+
223
231
fobj = file_path .open ("rb" )
224
232
with suppress (OSError ):
225
233
# fstat() may not be available on all platforms
@@ -232,6 +240,10 @@ def _make_response(
232
240
def _get_file_path_stat_encoding (
233
241
self , accept_encoding : str
234
242
) -> Tuple [Optional [pathlib .Path ], os .stat_result , Optional [str ]]:
243
+ if self ._path is None :
244
+ assert self ._io is not None
245
+ return None , os .stat (self ._io .fileno ()), None
246
+
235
247
file_path = self ._path
236
248
for file_extension , file_encoding in ENCODING_EXTENSIONS .items ():
237
249
if file_encoding not in accept_encoding :
@@ -377,11 +389,14 @@ async def _prepare_open_file(
377
389
# extension of the request path. The encoding returned by guess_type
378
390
# can be ignored since the map was cleared above.
379
391
if hdrs .CONTENT_TYPE not in self ._headers :
380
- if sys .version_info >= (3 , 13 ):
381
- guesser = CONTENT_TYPES .guess_file_type
392
+ if self ._path is not None :
393
+ if sys .version_info >= (3 , 13 ):
394
+ guesser = CONTENT_TYPES .guess_file_type
395
+ else :
396
+ guesser = CONTENT_TYPES .guess_type
397
+ self .content_type = guesser (self ._path )[0 ] or FALLBACK_CONTENT_TYPE
382
398
else :
383
- guesser = CONTENT_TYPES .guess_type
384
- self .content_type = guesser (self ._path )[0 ] or FALLBACK_CONTENT_TYPE
399
+ self .content_type = FALLBACK_CONTENT_TYPE
385
400
386
401
if file_encoding :
387
402
self ._headers [hdrs .CONTENT_ENCODING ] = file_encoding
0 commit comments