11import dacite
2+ import mimetypes
3+ import os
24import requests
35import json
46import time
57
6- from .constants import API_URL , TOKEN_HEADER , DEFAULT_MAX_RETRY_ATTEMPTS
7- from .types import MondayApiResponse
8+ from .constants import API_URL , FILE_UPLOAD_URL , TOKEN_HEADER , DEFAULT_MAX_RETRY_ATTEMPTS
9+ from .types import MondayApiResponse , FileInput
810from .exceptions import MondayQueryError
911
1012
@@ -106,4 +108,53 @@ def _send(self, query: str):
106108 if self .token is not None :
107109 headers [TOKEN_HEADER ] = self .token
108110
109- return requests .request ("POST" , self .endpoint , headers = headers , json = payload )
111+ return requests .request ("POST" , self .endpoint , headers = headers , json = payload )
112+
113+ def execute_multipart (self , query : str , file : FileInput ) -> dict :
114+ """
115+ Execute a GraphQL mutation using multipart/form-data.
116+
117+ Used for mutations that require file uploads (e.g., add_file_to_column,
118+ add_file_to_update). This follows the GraphQL multipart request specification.
119+
120+ Args:
121+ query: The GraphQL mutation string (must accept a File! variable)
122+ file: FileInput containing file details (name, path, optional filename/mimetype)
123+
124+ Returns:
125+ The API response as a dictionary
126+
127+ Raises:
128+ requests.HTTPError: If the API request fails
129+ """
130+ filename = file .filename or os .path .basename (file .file_path )
131+ file_mimetype = file .mimetype
132+ if file_mimetype is None :
133+ file_mimetype , _ = mimetypes .guess_type (file .file_path )
134+ file_mimetype = file_mimetype or 'application/octet-stream'
135+
136+ if self .debug_mode :
137+ print (f"[debug_mode] executing multipart mutation with file: { filename } , mimetype: { file_mimetype } " )
138+
139+ with open (file .file_path , 'rb' ) as f :
140+ # Following the GraphQL multipart request spec
141+ payload = {
142+ 'query' : query ,
143+ 'map' : json .dumps ({file .name : [f"variables.{ file .name } " ]})
144+ }
145+ files = [
146+ (file .name , (filename , f , file_mimetype ))
147+ ]
148+
149+ response = requests .post (
150+ FILE_UPLOAD_URL ,
151+ headers = {TOKEN_HEADER : self .token },
152+ data = payload ,
153+ files = files ,
154+ )
155+
156+ if self .debug_mode :
157+ print (f"[debug_mode] multipart response: { response .status_code } , { response .text } " )
158+
159+ response .raise_for_status ()
160+ return response .json ()
0 commit comments