Skip to content

Commit e086b99

Browse files
authored
Merge pull request #36 from mondaycom/add-support-for-file-upload
Added support for uploading file into an item's column
2 parents 2a95fec + 7791d8b commit e086b99

File tree

6 files changed

+120
-5
lines changed

6 files changed

+120
-5
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
setup(
1717
name="monday-api-python-sdk", # Required
18-
version="1.5.4", # Required
18+
version="1.6.0", # Required
1919
description="A Python SDK for interacting with Monday's GraphQL API", # Optional
2020
long_description=long_description, # Optional
2121
long_description_content_type="text/markdown", # Optional (see note above)

src/monday_sdk/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
API_URL = "https://api.monday.com/v2"
2+
FILE_UPLOAD_URL = "https://api.monday.com/v2/file"
23
API_VERSION = "2026-01"
34
TOKEN_HEADER = "Authorization"
45
MAX_COMPLEXITY = 10000000 # Monday's API complexity limit per minute

src/monday_sdk/graphql_handler.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import dacite
2+
import mimetypes
3+
import os
24
import requests
35
import json
46
import 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
810
from .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()

src/monday_sdk/modules/items.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
from typing import Union, Dict, Any, List
1+
from typing import Union, Dict, Any, List, Optional
22
import datetime
3+
import os
34

45
from ..graphql_handler import MondayGraphQL
6+
from ..types import FileInput
57
from ..query_templates import create_item_query, get_item_query, change_column_value_query, get_item_by_id_query, update_multiple_column_values_query, create_subitem_query, delete_item_query, archive_item_query, move_item_to_group_query, change_simple_column_value_query
68
from ..types import Item
79

@@ -101,3 +103,51 @@ def archive_item_by_id(self, item_id: Union[str, int]):
101103
def delete_item_by_id(self, item_id: Union[str, int]):
102104
query = delete_item_query(item_id)
103105
return self.client.execute(query)
106+
107+
def upload_file_to_column(
108+
self,
109+
item_id: Union[str, int],
110+
column_id: str,
111+
file_path: str,
112+
mimetype: Optional[str] = None,
113+
) -> dict:
114+
"""
115+
Upload a file to a file column on an item.
116+
117+
Note: Unlike other column methods, this uses a different API endpoint
118+
(https://api.monday.com/v2/file) and does not require board_id.
119+
120+
Args:
121+
item_id: The ID of the item to add the file to
122+
column_id: The ID of the file column
123+
file_path: Path to the file to upload
124+
mimetype: Optional MIME type (e.g., 'audio/mpeg', 'video/mp4').
125+
If not provided, will be auto-detected from the file extension.
126+
127+
Returns:
128+
The API response containing the uploaded asset info
129+
130+
Raises:
131+
FileNotFoundError: If the file doesn't exist
132+
requests.HTTPError: If the API request fails
133+
"""
134+
if not os.path.exists(file_path):
135+
raise FileNotFoundError(f"File not found: {file_path}")
136+
137+
mutation = '''mutation ($file: File!) {
138+
add_file_to_column(item_id: %s, column_id: "%s", file: $file) {
139+
id
140+
name
141+
url
142+
file_extension
143+
file_size
144+
}
145+
}''' % (item_id, column_id)
146+
147+
file_input = FileInput(
148+
name="file",
149+
file_path=file_path,
150+
mimetype=mimetype,
151+
)
152+
153+
return self.client.execute_multipart(mutation, file_input)

src/monday_sdk/types/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@
1616
Document,
1717
)
1818
from .monday_enums import BoardKind, BoardState, BoardsOrderBy, Operator
19+
from .file_input import FileInput

src/monday_sdk/types/file_input.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from dataclasses import dataclass
2+
from typing import Optional
3+
4+
5+
@dataclass
6+
class FileInput:
7+
"""Represents a file to be uploaded via multipart/form-data."""
8+
name: str # The variable name in the GraphQL mutation (e.g., "file")
9+
file_path: str # Path to the file
10+
filename: Optional[str] = None # Override filename (defaults to basename of file_path)
11+
mimetype: Optional[str] = None # MIME type (auto-detected if not provided)
12+

0 commit comments

Comments
 (0)