11import json
2- from io import BytesIO
32from typing import Any , AsyncGenerator , Dict , Generic , List , TypedDict , Union , cast
4-
3+ from io import BytesIO
54import aiohttp
65from typing_extensions import Literal , TypeVar
76
@@ -28,6 +27,7 @@ def __init__(
2827 headers : Dict [str , str ] = None ,
2928 data : Union [bytes , None ] = None ,
3029 stream : Union [bool , None ] = False ,
30+ files : Union [Dict [str , Any ], None ] = None , # Add files parameter
3131 ):
3232 self .path = path
3333 self .params = params
@@ -38,6 +38,7 @@ def __init__(
3838 self .headers = headers or {"Content-Type" : "application/json" }
3939 self .disable_request_logging = config .get ("disable_request_logging" )
4040 self .stream = stream
41+ self .files = files # Store files for multipart requests
4142
4243 def __convert_params (
4344 self , params : Union [Dict [Any , Any ], List [Dict [Any , Any ]]]
@@ -171,15 +172,23 @@ def __get_headers(self) -> Dict[str, str]:
171172 Dict[str, str]: Configured HTTP Headers
172173 """
173174 h = {
174- "Content-Type" : "application/json" ,
175175 "Accept" : "application/json" ,
176176 "x-api-key" : f"{ self .api_key } " ,
177177 }
178178
179+ # only add Content-Type if not using multipart (files)
180+ if not self .files and not self .data :
181+ h ["Content-Type" ] = "application/json"
182+
179183 if self .disable_request_logging :
180184 h ["x-jigsaw-no-request-log" ] = "true"
181185
182186 _headers = h .copy ()
187+
188+ #don't override Content-Type if using multipart
189+ if self .files and "Content-Type" in self .headers :
190+ self .headers .pop ("Content-Type" )
191+
183192 _headers .update (self .headers )
184193
185194 return _headers
@@ -231,50 +240,84 @@ async def make_request(
231240 self , session : aiohttp .ClientSession , url : str
232241 ) -> aiohttp .ClientResponse :
233242 headers = self .__get_headers ()
243+ params = self .params
234244 verb = self .verb
235245 data = self .data
246+ files = self .files
236247
237- # Convert params to string values for URL encoding
238- converted_params = self .__convert_params (self .params )
248+ _params = None
249+ _json = None
250+ _data = None
251+ _form_data = None
239252
240253 if verb .lower () in ["get" , "delete" ]:
254+ #convert params for URL encoding if needed
255+ _params = self .__convert_params (params )
256+ elif files :
257+ # for multipart requests - matches request.py behavior
258+ _form_data = aiohttp .FormData ()
259+
260+ # add file(s) to form data
261+ for field_name , file_data in files .items ():
262+ if isinstance (file_data , bytes ):
263+ # just pass the blob without filename
264+ _form_data .add_field (
265+ field_name ,
266+ BytesIO (file_data ),
267+ content_type = "application/octet-stream"
268+ )
269+ elif isinstance (file_data , tuple ):
270+ # if tuple format (filename, data, content_type)
271+ filename , content , content_type = file_data
272+ _form_data .add_field (
273+ field_name ,
274+ content ,
275+ filename = filename ,
276+ content_type = content_type
277+ )
278+
279+ # add params as 'body' field in multipart form (JSON stringified)
280+ if params and isinstance (params , dict ):
281+ _form_data .add_field (
282+ "body" ,
283+ json .dumps (params ),
284+ content_type = "application/json"
285+ )
286+ elif data :
287+ # for binary data without multipart
288+ _data = data
289+ # pass params as query parameters for binary uploads
290+ if params and isinstance (params , dict ):
291+ _params = self .__convert_params (params )
292+ else :
293+ # for JSON requests
294+ _json = params
295+
296+ # m,ake the request based on the data type
297+ if _form_data :
298+ return await session .request (
299+ verb ,
300+ url ,
301+ params = _params ,
302+ data = _form_data ,
303+ headers = headers ,
304+ )
305+ elif _json is not None :
241306 return await session .request (
242307 verb ,
243308 url ,
244- params = converted_params ,
309+ params = _params ,
310+ json = _json ,
245311 headers = headers ,
246312 )
247313 else :
248- if data is not None :
249- form_data = aiohttp .FormData ()
250- form_data .add_field (
251- "file" ,
252- BytesIO (data ),
253- content_type = headers .get ("Content-Type" , "application/octet-stream" ),
254- filename = "file" ,
255- )
256-
257- if self .params and isinstance (self .params , dict ):
258- form_data .add_field (
259- "body" , json .dumps (self .params ), content_type = "application/json"
260- )
261-
262- multipart_headers = headers .copy ()
263- multipart_headers .pop ("Content-Type" , None )
264-
265- return await session .request (
266- verb ,
267- url ,
268- data = form_data ,
269- headers = multipart_headers ,
270- )
271- else :
272- return await session .request (
273- verb ,
274- url ,
275- json = self .params , # Keep JSON body as original
276- headers = headers ,
277- )
314+ return await session .request (
315+ verb ,
316+ url ,
317+ params = _params ,
318+ data = _data ,
319+ headers = headers ,
320+ )
278321
279322 def __get_session (self ) -> aiohttp .ClientSession :
280323 """
0 commit comments