Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
python -m pip install --upgrade pip
pip install pipenv
pipenv install --dev --deploy --system
pip install requests
pip install urllib3

- name: Run Tests
run: |
Expand Down
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ pylint = "*"
pytest = "*"

[packages]
requests = "*"
urllib3 = "*"

[requires]
599 changes: 239 additions & 360 deletions Pipfile.lock

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ For an overview of the Monday API, [click here](https://developer.monday.com/api

#### Requirements
- Python >= 3.11
- urllib3 >= 2.6.0

#### Getting started
`pip install monday`
Expand Down Expand Up @@ -54,6 +55,3 @@ monday.items.create_item(board_id='12345678', group_id='today', item_name='Do a
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

### Bug Reports
TBD
31 changes: 18 additions & 13 deletions docs/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ monday

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->

|All Contributors| A monday.com Python Client Library
|All Contributors|

For an overview of the Monday API, `click
here <https://developer.monday.com/api-reference/docs>`__.
A monday.com Python Client Library

For an overview of the Monday API, `click here <https://developer.monday.com/api-reference/docs>`__.

Requirements
^^^^^^^^^^^^
============

- Python >= 3.11
- Python >= 3.11
- urllib3 >= 2.6.0

Getting started
^^^^^^^^^^^^^^^
===============

``pip install monday``

Expand All @@ -30,8 +32,17 @@ Getting started

monday.items.create_item(board_id='12345678', group_id='today', item_name='Do a thing')

Custom Timeout
^^^^^^^^^^^^^^

To specify a custom timeout (default: 60 seconds), you can pass the ``timeout`` parameter:

.. code:: python

monday = MondayClient('your token', timeout=120)

Available methods
^^^^^^^^^^^^^^^^^
=================

Items Resource (monday.items)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -369,11 +380,5 @@ Taylor Cochran💻

<!-- ALL-CONTRIBUTORS-LIST:END -->

Bug Reports
~~~~~~~~~~~

TBD

.. |All Contributors| image:: https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square
:target: #contributors-

41 changes: 28 additions & 13 deletions monday/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,26 @@
"""

from .__version__ import __version__
from .resources import CustomResource, ItemResource, ColumnsResource, UpdateResource, TagResource, BoardResource, \
UserResource, GroupResource, ComplexityResource, WorkspaceResource, NotificationResource, MeResource
from .resources import (
BoardResource,
ColumnsResource,
ComplexityResource,
CustomResource,
GroupResource,
ItemResource,
MeResource,
NotificationResource,
TagResource,
UpdateResource,
UserResource,
WorkspaceResource,
)

_DEFAULT_HEADERS = {"API-Version": "2026-01"}

_DEFAULT_HEADERS = {
"API-Version": "2026-01"
}

DEFAULT_TIMEOUT = 60

class MondayClient:
def __init__(self, token, headers=None, timeout=DEFAULT_TIMEOUT):
def __init__(self, token, headers=None, timeout=None):
"""
:param token: API token for the new :class:`BaseResource` object.
:param headers: (optional) headers for the new :class:`BaseResource` object.
Expand All @@ -34,13 +43,19 @@ def __init__(self, token, headers=None, timeout=DEFAULT_TIMEOUT):
self.boards = BoardResource(token=token, headers=headers, timeout=timeout)
self.users = UserResource(token=token, headers=headers, timeout=timeout)
self.groups = GroupResource(token=token, headers=headers, timeout=timeout)
self.complexity = ComplexityResource(token=token, headers=headers, timeout=timeout)
self.workspaces = WorkspaceResource(token=token, headers=headers, timeout=timeout)
self.notifications = NotificationResource(token=token, headers=headers, timeout=timeout)
self.complexity = ComplexityResource(
token=token, headers=headers, timeout=timeout
)
self.workspaces = WorkspaceResource(
token=token, headers=headers, timeout=timeout
)
self.notifications = NotificationResource(
token=token, headers=headers, timeout=timeout
)
self.me = MeResource(token=token, headers=headers, timeout=timeout)

def __str__(self):
return f'MondayClient {__version__}'
return f"MondayClient {__version__}"

def __repr__(self):
return f'MondayClient {__version__}'
return f"MondayClient {__version__}"
5 changes: 5 additions & 0 deletions monday/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
TOKEN_HEADER = "Authorization"

DEFAULT_TIMEOUT = 60

DEFAULT_PAGE_LIMIT_ITEMS = 500
71 changes: 40 additions & 31 deletions monday/graphqlclient/client.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import json
import requests

from monday.exceptions import MondayQueryError

TOKEN_HEADER = 'Authorization'
import urllib3

DEFAULT_TIMEOUT = 60

DEFAULT_PAGE_LIMIT_ITEMS = 500
from monday.constants import DEFAULT_TIMEOUT, TOKEN_HEADER
from monday.exceptions import MondayQueryError


class GraphQLClient:
Expand All @@ -16,6 +12,7 @@ def __init__(self, endpoint, timeout=DEFAULT_TIMEOUT):
self.timeout = timeout
self.token = None
self.headers = {}
self._http = urllib3.PoolManager()

def execute(self, query, variables=None):
return self._send(query, variables)
Expand All @@ -27,34 +24,46 @@ def inject_headers(self, headers):
self.headers = headers

def _send(self, query, variables):
payload = {'query': query}
headers = self.headers.copy()
files = None

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

if variables is None:
headers.setdefault('Content-Type', 'application/json')

payload = json.dumps({'query': query}).encode('utf-8')

elif variables.get('file', None) is not None:
headers.setdefault('content', 'multipart/form-data')

files = [
('variables[file]', (variables['file'], open(variables['file'], 'rb')))
]

try:
response = requests.request("POST", self.endpoint, headers=headers, data=payload, files=files, timeout=self.timeout)
response.raise_for_status()
response_data = response.json()
self._throw_on_error(response_data)
return response_data
except (requests.HTTPError, json.JSONDecodeError, MondayQueryError) as e:
raise e
if variables is not None and variables.get("file") is not None:
file_path = variables["file"]
with open(file_path, "rb") as f:
file_data = f.read()

response = self._http.request(
"POST",
self.endpoint,
headers=headers,
fields={"query": query, "variables[file]": (file_path, file_data)},
timeout=self.timeout,
)
else:
headers.setdefault("Content-Type", "application/json")
body = json.dumps({"query": query}).encode("utf-8")

response = self._http.request(
"POST",
self.endpoint,
headers=headers,
body=body,
timeout=self.timeout,
)

if response.status >= 400:
raise urllib3.exceptions.HTTPError(
f"HTTP {response.status}: {response.data.decode('utf-8')}"
)

response_data = json.loads(response.data.decode("utf-8"))
self._throw_on_error(response_data)
return response_data

def _throw_on_error(self, response_data):
if 'errors' in response_data:
raise MondayQueryError(response_data['errors'][0]['message'], response_data['errors'])
if "errors" in response_data:
raise MondayQueryError(
response_data["errors"][0]["message"], response_data["errors"]
)
9 changes: 6 additions & 3 deletions monday/resources/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@


class BaseResource:
def __init__(self, token, headers, timeout):
def __init__(self, token, headers, timeout=None):
self._token = token
self.client = GraphQLClient(_URLS['prod'], timeout=timeout)
self.file_upload_client = GraphQLClient(_URLS['file'], timeout=timeout)
kwargs = {}
if timeout is not None:
kwargs['timeout'] = timeout
self.client = GraphQLClient(_URLS['prod'], **kwargs)
self.file_upload_client = GraphQLClient(_URLS['file'], **kwargs)
self.client.inject_token(token)
self.client.inject_headers(headers)
self.file_upload_client.inject_token(token)
Expand Down
86 changes: 64 additions & 22 deletions monday/resources/boards.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,61 @@
from typing import List, Optional, Union, Any, Mapping
from typing import Any, List, Mapping, Optional, Union

from monday.query_joins import get_boards_query, get_boards_by_id_query, get_board_items_query, \
get_next_items_query, get_columns_by_board_query, create_board_by_workspace_query, \
duplicate_board_query
from monday.constants import DEFAULT_PAGE_LIMIT_ITEMS
from monday.query_joins import (
create_board_by_workspace_query,
duplicate_board_query,
get_board_items_query,
get_boards_by_id_query,
get_boards_query,
get_columns_by_board_query,
get_next_items_query,
)
from monday.resources.base import BaseResource
from monday.resources.types import BoardKind, BoardState, BoardsOrderBy, DuplicateType
from monday.graphqlclient.client import DEFAULT_PAGE_LIMIT_ITEMS
from monday.resources.types import BoardKind, BoardsOrderBy, BoardState, DuplicateType


class BoardResource(BaseResource):
def fetch_boards(self, limit: Optional[int] = None, page: Optional[int] = None, ids: Optional[List[int]] = None,
board_kind: Optional[BoardKind] = None, state: Optional[BoardState] = None,
order_by: Optional[BoardsOrderBy] = None, workspace_ids: Optional[List[int]] = None):
query = get_boards_query(limit, page, ids, board_kind, state, order_by, workspace_ids)
def fetch_boards(
self,
limit: Optional[int] = None,
page: Optional[int] = None,
ids: Optional[List[int]] = None,
board_kind: Optional[BoardKind] = None,
state: Optional[BoardState] = None,
order_by: Optional[BoardsOrderBy] = None,
workspace_ids: Optional[List[int]] = None,
):
query = get_boards_query(
limit, page, ids, board_kind, state, order_by, workspace_ids
)
return self.client.execute(query)

def fetch_boards_by_id(self, board_ids: Union[int, str]):
query = get_boards_by_id_query(board_ids)
return self.client.execute(query)

def fetch_items_by_board_id(self, board_ids: Union[int, str], query_params: Optional[Mapping[str, Any]] = None,
limit: Optional[int] = DEFAULT_PAGE_LIMIT_ITEMS, cursor: Optional[str] = None):
query = get_board_items_query(board_ids, query_params=query_params, limit=limit, cursor=cursor)
def fetch_items_by_board_id(
self,
board_ids: Union[int, str],
query_params: Optional[Mapping[str, Any]] = None,
limit: Optional[int] = DEFAULT_PAGE_LIMIT_ITEMS,
cursor: Optional[str] = None,
):
query = get_board_items_query(
board_ids, query_params=query_params, limit=limit, cursor=cursor
)
return self.client.execute(query)

def fetch_next_items_by_cursor(self, cursor: str, limit: Optional[int] = None):
query = get_next_items_query(limit=limit, cursor=cursor)
return self.client.execute(query)

def fetch_all_items_by_board_id(self, board_ids: Union[int, str], query_params: Optional[Mapping[str, Any]] = None,
limit: Optional[int] = DEFAULT_PAGE_LIMIT_ITEMS) -> List[dict]:
def fetch_all_items_by_board_id(
self,
board_ids: Union[int, str],
query_params: Optional[Mapping[str, Any]] = None,
limit: Optional[int] = DEFAULT_PAGE_LIMIT_ITEMS,
) -> List[dict]:
items: List[dict] = []
cursor: Optional[str] = None

Expand All @@ -38,8 +64,9 @@ def fetch_all_items_by_board_id(self, board_ids: Union[int, str], query_params:
response = self.fetch_next_items_by_cursor(cursor=cursor, limit=limit)
items_page = response["data"]["next_items_page"]
else:
response = self.fetch_items_by_board_id(board_ids, query_params=query_params,
limit=limit, cursor=cursor)
response = self.fetch_items_by_board_id(
board_ids, query_params=query_params, limit=limit, cursor=cursor
)
items_page = response["data"]["boards"][0]["items_page"]

items.extend(items_page.get("items", []))
Expand All @@ -54,12 +81,27 @@ def fetch_columns_by_board_id(self, board_ids: Union[int, str]):
query = get_columns_by_board_query(board_ids)
return self.client.execute(query)

def create_board(self, board_name: str, board_kind: BoardKind, workspace_id: Optional[int] = None):
def create_board(
self, board_name: str, board_kind: BoardKind, workspace_id: Optional[int] = None
):
query = create_board_by_workspace_query(board_name, board_kind, workspace_id)
return self.client.execute(query)

def duplicate_board(self, board_id: int, duplicate_type: DuplicateType, board_name: Optional[str] = None,
folder_id: Optional[int] = None, keep_subscribers: Optional[bool] = None,
workspace_id: Optional[int] = None):
query = duplicate_board_query(board_id, duplicate_type, board_name, folder_id, keep_subscribers, workspace_id)
def duplicate_board(
self,
board_id: int,
duplicate_type: DuplicateType,
board_name: Optional[str] = None,
folder_id: Optional[int] = None,
keep_subscribers: Optional[bool] = None,
workspace_id: Optional[int] = None,
):
query = duplicate_board_query(
board_id,
duplicate_type,
board_name,
folder_id,
keep_subscribers,
workspace_id,
)
return self.client.execute(query)
Loading
Loading