Skip to content

Commit 81cdaad

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/feature/michaelim/monday-docs-fetcher' into feature/tomersh/allow_non_ascii_updates
# Conflicts: # setup.py
2 parents e7c9b2f + 552ff38 commit 81cdaad

File tree

15 files changed

+227
-59
lines changed

15 files changed

+227
-59
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.4.6", # Required
18+
version="1.4.7", # 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/client.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1-
from .types import MondayClientSettings
2-
from .modules import BoardModule, ItemModule, UpdateModule, CustomModule, ActivityLogModule
1+
from .modules import BoardModule, ItemModule, UpdateModule, CustomModule, ActivityLogModule, DocsModule
32
from .constants import API_VERSION, DEFAULT_MAX_RETRY_ATTEMPTS
3+
from .graphql_handler import MondayGraphQL
44

55
BASE_HEADERS = {"API-Version": API_VERSION}
66

77

88
class MondayClient:
99
def __init__(self, token, headers=None, debug_mode=False, max_retry_attempts=DEFAULT_MAX_RETRY_ATTEMPTS):
10-
1110
headers = headers or BASE_HEADERS.copy()
12-
self.settings = MondayClientSettings(token, headers, debug_mode, max_retry_attempts)
11+
12+
# Create a single GraphQL client instance
13+
self._graphql_client = MondayGraphQL(
14+
token=token,
15+
headers=headers,
16+
debug_mode=debug_mode,
17+
max_retry_attempts=max_retry_attempts
18+
)
1319

14-
self.boards = BoardModule(self.settings)
15-
self.items = ItemModule(self.settings)
16-
self.updates = UpdateModule(self.settings)
17-
self.activity_logs = ActivityLogModule(self.settings)
18-
self.custom = CustomModule(self.settings)
20+
# Pass the GraphQL client to each module
21+
self.boards = BoardModule(self._graphql_client)
22+
self.items = ItemModule(self._graphql_client)
23+
self.updates = UpdateModule(self._graphql_client)
24+
self.activity_logs = ActivityLogModule(self._graphql_client)
25+
self.custom = CustomModule(self._graphql_client)
26+
self.docs = DocsModule(self._graphql_client)

src/monday_sdk/constants.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
API_URL = "https://api.monday.com/v2"
2-
API_VERSION = "2023-10"
2+
API_VERSION = "2025-07"
33
TOKEN_HEADER = "Authorization"
44
MAX_COMPLEXITY = 10000000 # Monday's API complexity limit per minute
5-
BASE_HEADERS = {"API-Version": API_VERSION}
65

7-
DEFAULT_MAX_RETRY_ATTEMPTS = 3
6+
DEFAULT_MAX_RETRY_ATTEMPTS = 4
87
DEFAULT_PAGE_LIMIT_ITEMS = 500
98
DEFAULT_PAGE_LIMIT_UPDATES = 1000
109
DEFAULT_PAGE_LIMIT_BOARDS = 50

src/monday_sdk/graphql_handler.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@
33
import json
44
import time
55

6+
from .constants import API_URL, TOKEN_HEADER, DEFAULT_MAX_RETRY_ATTEMPTS
7+
from .types import MondayApiResponse
68
from .exceptions import MondayQueryError
7-
from .constants import API_URL, TOKEN_HEADER
8-
from .types import MondayApiResponse, MondayClientSettings
99

1010

1111
class MondayGraphQL:
1212
"""
1313
GraphQL client that handles API interactions, response serialization, and error handling.
1414
"""
1515

16-
def __init__(self, settings: MondayClientSettings):
16+
def __init__(self, token: str, headers: dict, debug_mode: bool = False, max_retry_attempts: int = DEFAULT_MAX_RETRY_ATTEMPTS):
1717
self.endpoint = API_URL
18-
self.token = settings.token
19-
self.headers = settings.headers
20-
self.debug_mode = settings.debug_mode
21-
self.max_retry_attempts = settings.max_retry_attempts
18+
self.token = token
19+
self.headers = headers
20+
self.debug_mode = debug_mode
21+
self.max_retry_attempts = max_retry_attempts
2222

2323
def execute(self, query: str) -> MondayApiResponse:
2424
"""
@@ -31,6 +31,8 @@ def execute(self, query: str) -> MondayApiResponse:
3131
MondayApiResponse: The deserialized response from the Monday API.
3232
"""
3333
current_attempt = 0
34+
last_error = None
35+
last_status_code = None
3436

3537
while current_attempt < self.max_retry_attempts:
3638

@@ -71,13 +73,35 @@ def execute(self, query: str) -> MondayApiResponse:
7173

7274
except (requests.HTTPError, json.JSONDecodeError, MondayQueryError) as e:
7375
print(f"Error while executing query: {e}")
76+
last_error = e
77+
if hasattr(e, 'response') and e.response is not None:
78+
last_status_code = e.response.status_code
7479
current_attempt += 1
7580

81+
# All retries exhausted - raise appropriate error based on the last failure
82+
if last_status_code == 504:
83+
raise Exception(
84+
f"Monday API server encountered an internal error (HTTP 504 Gateway Timeout) "
85+
f"for {self.max_retry_attempts} consecutive attempts. The server was unable to process "
86+
f"the request within the timeout period. Please try again later or contact support "
87+
f"if the issue persists."
88+
)
89+
elif last_status_code is not None:
90+
raise Exception(
91+
f"Monday API request failed with HTTP {last_status_code} after {self.max_retry_attempts} attempts. "
92+
f"Error: {str(last_error)}"
93+
)
94+
else:
95+
raise Exception(
96+
f"Monday API request failed after {self.max_retry_attempts} attempts. "
97+
f"Error: {str(last_error)}"
98+
)
99+
76100
def _send(self, query: str):
77101
payload = {"query": query}
78102
headers = self.headers.copy()
79103

80104
if self.token is not None:
81105
headers[TOKEN_HEADER] = self.token
82106

83-
return requests.request("POST", self.endpoint, headers=headers, json=payload)
107+
return requests.request("POST", self.endpoint, headers=headers, json=payload)

src/monday_sdk/modules/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
from .updates import UpdateModule
44
from .custom import CustomModule
55
from .activity_logs import ActivityLogModule
6+
from .docs import DocsModule

src/monday_sdk/modules/activity_logs.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
from typing import Optional, Union, List
66

77

8-
class ActivityLogModule(MondayGraphQL):
8+
class ActivityLogModule:
9+
def __init__(self, graphql_client: MondayGraphQL):
10+
self.client = graphql_client
911

1012
def fetch_activity_logs_from_board(
1113
self,
@@ -16,7 +18,7 @@ def fetch_activity_logs_from_board(
1618
to_date: Optional[str] = None,
1719
) -> MondayApiResponse:
1820
query = get_activity_logs_query(board_ids, limit, page, from_date, to_date)
19-
return self.execute(query)
21+
return self.client.execute(query)
2022

2123
def fetch_all_activity_logs_from_board(
2224
self,

src/monday_sdk/modules/boards.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
from ..constants import DEFAULT_PAGE_LIMIT_BOARDS, DEFAULT_PAGE_LIMIT_ITEMS
88

99

10-
class BoardModule(MondayGraphQL):
10+
class BoardModule:
11+
def __init__(self, graphql_client: MondayGraphQL):
12+
self.client = graphql_client
1113
def fetch_boards(
1214
self,
1315
limit: Optional[int] = DEFAULT_PAGE_LIMIT_BOARDS,
@@ -18,11 +20,11 @@ def fetch_boards(
1820
order_by: Optional[BoardsOrderBy] = None,
1921
) -> MondayApiResponse:
2022
query = get_boards_query(ids=ids, limit=limit, page=page, board_kind=board_kind, state=state, order_by=order_by)
21-
return self.execute(query)
23+
return self.client.execute(query)
2224

2325
def fetch_boards_by_id(self, board_id: Union[int, str]) -> MondayApiResponse:
2426
query = get_board_by_id_query(board_id)
25-
return self.execute(query)
27+
return self.client.execute(query)
2628

2729
def fetch_all_items_by_board_id(
2830
self,
@@ -39,7 +41,7 @@ def fetch_all_items_by_board_id(
3941

4042
while True:
4143
query = get_board_items_query(board_id, query_params=query_params, cursor=cursor, limit=limit)
42-
response = self.execute(query)
44+
response = self.client.execute(query)
4345
items_page = response.data.boards[0].items_page if cursor is None else response.data.next_items_page
4446

4547
items.extend(items_page.items)
@@ -73,4 +75,4 @@ def fetch_item_by_board_id_by_update_date(
7375

7476
def fetch_columns_by_board_id(self, board_id: Union[int, str]) -> MondayApiResponse:
7577
query = get_columns_by_board_query(board_id)
76-
return self.execute(query)
78+
return self.client.execute(query)

src/monday_sdk/modules/custom.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from ..graphql_handler import MondayGraphQL
22

33

4-
class CustomModule(MondayGraphQL):
4+
class CustomModule:
5+
def __init__(self, graphql_client: MondayGraphQL):
6+
self.client = graphql_client
57
def execute_custom_query(self, custom_query):
6-
return self.execute(custom_query)
8+
return self.client.execute(custom_query)

src/monday_sdk/modules/docs.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from typing import Optional
2+
3+
from ..query_templates import get_docs_query
4+
from ..types import MondayApiResponse, Document
5+
from ..graphql_handler import MondayGraphQL
6+
7+
8+
class DocsModule:
9+
def __init__(self, graphql_client: MondayGraphQL):
10+
self.client = graphql_client
11+
def get_document_with_blocks(self, doc_id: str) -> Optional[Document]:
12+
"""
13+
Get document with all its blocks for a specific document ID.
14+
15+
Args:
16+
doc_id: The document ID to fetch
17+
18+
Returns:
19+
Document object with all blocks, or None if not found
20+
"""
21+
all_blocks = []
22+
document = None
23+
page = 1
24+
25+
while True:
26+
query = get_docs_query(doc_id, page=page)
27+
response: MondayApiResponse = self.client.execute(query)
28+
29+
# Check if we have docs in the response
30+
if not response.data.docs:
31+
break
32+
33+
# Get the first (and should be only) document
34+
doc = response.data.docs[0]
35+
36+
# Store the document metadata on first iteration
37+
if document is None:
38+
document = doc
39+
40+
# If no blocks, we're done
41+
if not doc.blocks:
42+
break
43+
44+
all_blocks.extend(doc.blocks)
45+
46+
page += 1
47+
48+
# Update the document with all collected blocks
49+
if document:
50+
document.blocks = all_blocks
51+
52+
return document

src/monday_sdk/modules/items.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from ..types import Item
77

88

9-
class ItemModule(MondayGraphQL):
9+
class ItemModule:
10+
def __init__(self, graphql_client: MondayGraphQL):
11+
self.client = graphql_client
1012
""" "
1113
todo: add types for this module
1214
"""
@@ -21,13 +23,13 @@ def change_custom_column_value(
2123
for other columns, use this method, for example, for checkbox columns pass {'checked': True}
2224
"""
2325
query = change_column_value_query(board_id, item_id, column_id, value)
24-
return self.execute(query)
26+
return self.client.execute(query)
2527

2628
def change_simple_column_value(
2729
self, board_id: Union[str, int], item_id: Union[str, int], column_id: str, value: str
2830
):
2931
query = change_simple_column_value_query(board_id, item_id, column_id, value)
30-
return self.execute(query)
32+
return self.client.execute(query)
3133

3234
def change_status_column_value(
3335
self, board_id: Union[str, int], item_id: Union[str, int], column_id: str, value: str
@@ -50,7 +52,7 @@ def create_item(
5052
create_labels_if_missing=False,
5153
):
5254
query = create_item_query(board_id, group_id, item_name, column_values, create_labels_if_missing)
53-
return self.execute(query)
55+
return self.client.execute(query)
5456

5557
def create_subitem(
5658
self,
@@ -60,13 +62,13 @@ def create_subitem(
6062
create_labels_if_missing=False,
6163
):
6264
query = create_subitem_query(parent_item_id, subitem_name, column_values, create_labels_if_missing)
63-
return self.execute(query)
65+
return self.client.execute(query)
6466

6567
def fetch_items_by_column_value(
6668
self, board_id: Union[str, int], column_id: str, value: str, limit: int = None, cursor: str = None
6769
):
6870
query = get_item_query(board_id, column_id, value, limit, cursor)
69-
return self.execute(query)
71+
return self.client.execute(query)
7072

7173
def fetch_items_by_id(self, ids: Union[str, int, List[Union[str, int]]]) -> List[Item]:
7274
if isinstance(ids, (list, set)):
@@ -75,7 +77,7 @@ def fetch_items_by_id(self, ids: Union[str, int, List[Union[str, int]]]) -> List
7577
else:
7678
ids_str = str(ids)
7779
query = get_item_by_id_query(ids_str)
78-
response = self.execute(query)
80+
response = self.client.execute(query)
7981
return response.data.items
8082

8183
def change_multiple_column_values(
@@ -86,16 +88,16 @@ def change_multiple_column_values(
8688
create_labels_if_missing: bool = False,
8789
):
8890
query = update_multiple_column_values_query(board_id, item_id, column_values, create_labels_if_missing)
89-
return self.execute(query)
91+
return self.client.execute(query)
9092

9193
def move_item_to_group(self, item_id: Union[str, int], group_id: Union[str, int]):
9294
query = move_item_to_group_query(item_id, group_id)
93-
return self.execute(query)
95+
return self.client.execute(query)
9496

9597
def archive_item_by_id(self, item_id: Union[str, int]):
9698
query = archive_item_query(item_id)
97-
return self.execute(query)
99+
return self.client.execute(query)
98100

99101
def delete_item_by_id(self, item_id: Union[str, int]):
100102
query = delete_item_query(item_id)
101-
return self.execute(query)
103+
return self.client.execute(query)

0 commit comments

Comments
 (0)