11import aiohttp
2+ import aiofiles
23import asyncio
4+ from pathlib import Path
35from typing import Any , Optional
46from ..types import FileInput
57from ..models import ModelDefinition
68from ..errors import InvalidInputError , ProcessingError
79
810
9- async def file_input_to_bytes (input_data : FileInput ) -> tuple [bytes , str ]:
11+ async def file_input_to_bytes (
12+ input_data : FileInput , session : aiohttp .ClientSession
13+ ) -> tuple [bytes , str ]:
14+ """Convert various file input types to bytes asynchronously.
15+
16+ Args:
17+ input_data: The file input (bytes, Path, str, or file-like object)
18+ session: Reusable aiohttp session for URL fetching
19+
20+ Returns:
21+ Tuple of (content bytes, content type)
22+
23+ Raises:
24+ InvalidInputError: If input is invalid or processing fails
25+ """
26+
1027 if isinstance (input_data , bytes ):
1128 return input_data , "application/octet-stream"
1229
13- if hasattr (input_data , "read" ):
30+ if isinstance (input_data , Path ):
31+ # Async file reading with aiofiles
32+ try :
33+ async with aiofiles .open (input_data , mode = "rb" ) as f :
34+ content = await f .read ()
35+ return content , "application/octet-stream"
36+ except FileNotFoundError :
37+ raise InvalidInputError (f"File not found: { input_data } " )
38+ except Exception as e :
39+ raise InvalidInputError (f"Failed to read file { input_data } : { str (e )} " )
40+
41+ if isinstance (input_data , str ):
42+ # Check if it's a file path
43+ path = Path (input_data )
44+ if path .exists ():
45+ try :
46+ async with aiofiles .open (path , mode = "rb" ) as f :
47+ content = await f .read ()
48+ return content , "application/octet-stream"
49+ except Exception as e :
50+ raise InvalidInputError (f"Failed to read file { input_data } : { str (e )} " )
51+
52+ # Otherwise treat as URL
53+ if not input_data .startswith (("http://" , "https://" )):
54+ raise InvalidInputError (
55+ f"Input must be a URL (http:// or https://) or existing file path: { input_data } "
56+ )
57+
58+ # Use the provided session instead of creating a new one
59+ async with session .get (input_data ) as response :
60+ if not response .ok :
61+ raise InvalidInputError (
62+ f"Failed to fetch file from URL: { response .status } "
63+ )
64+ content = await response .read ()
65+ content_type = response .headers .get ("Content-Type" , "application/octet-stream" )
66+ return content , content_type
67+
68+ from ..types import HasRead
69+ if isinstance (input_data , HasRead ):
70+ # Sync file-like objects (for backwards compatibility)
1471 content = await asyncio .to_thread (input_data .read )
1572 if isinstance (content , str ):
1673 content = content .encode ()
1774 return content , "application/octet-stream"
1875
19- if isinstance (input_data , str ):
20- if not input_data .startswith (("http://" , "https://" )):
21- raise InvalidInputError ("URL must start with http:// or https://" )
22-
23- async with aiohttp .ClientSession () as session :
24- async with session .get (input_data ) as response :
25- if not response .ok :
26- raise InvalidInputError (
27- f"Failed to fetch file from URL: { response .status } "
28- )
29- content = await response .read ()
30- content_type = response .headers .get ("Content-Type" , "application/octet-stream" )
31- return content , content_type
32-
33- raise InvalidInputError ("Invalid file input type" )
76+ raise InvalidInputError (f"Invalid file input type: { type (input_data )} " )
3477
3578
3679async def send_request (
80+ session : aiohttp .ClientSession ,
3781 base_url : str ,
3882 api_key : str ,
3983 model : ModelDefinition ,
@@ -45,28 +89,25 @@ async def send_request(
4589 for key , value in inputs .items ():
4690 if value is not None :
4791 if key in ("data" , "start" , "end" ):
48- content , content_type = await file_input_to_bytes (value )
92+ content , content_type = await file_input_to_bytes (value , session )
4993 form_data .add_field (key , content , content_type = content_type )
5094 else :
5195 form_data .add_field (key , str (value ))
5296
5397 endpoint = f"{ base_url } { model .url_path } "
5498
55- timeout = aiohttp .ClientTimeout (total = 300 )
56-
5799 async def make_request () -> bytes :
58- async with aiohttp .ClientSession (timeout = timeout ) as session :
59- async with session .post (
60- endpoint ,
61- headers = {"X-API-KEY" : api_key },
62- data = form_data ,
63- ) as response :
64- if not response .ok :
65- error_text = await response .text ()
66- raise ProcessingError (
67- f"Processing failed: { response .status } - { error_text } "
68- )
69- return await response .read ()
100+ async with session .post (
101+ endpoint ,
102+ headers = {"X-API-KEY" : api_key },
103+ data = form_data ,
104+ ) as response :
105+ if not response .ok :
106+ error_text = await response .text ()
107+ raise ProcessingError (
108+ f"Processing failed: { response .status } - { error_text } "
109+ )
110+ return await response .read ()
70111
71112 if cancel_token :
72113 request_task = asyncio .create_task (make_request ())
0 commit comments