diff --git a/monday/__version__.py b/monday/__version__.py index 9a63720..a3cbf64 100644 --- a/monday/__version__.py +++ b/monday/__version__.py @@ -1,3 +1,3 @@ -__version__ = '2.0.0rc2' +__version__ = '2.0.0rc3' __author__ = 'Christina D\'Astolfo' __email__ = 'chdastolfo@gmail.com, lemi@prodperfect.com, pevner@prodperfect.com' diff --git a/monday/query_joins.py b/monday/query_joins.py index 024aac8..8cfb8c2 100644 --- a/monday/query_joins.py +++ b/monday/query_joins.py @@ -1,6 +1,6 @@ import json from enum import Enum -from typing import List, Union, Optional, Mapping +from typing import List, Union, Optional, Mapping, Any from monday.resources.types import BoardKind, BoardState, BoardsOrderBy, DuplicateType, ColumnType from monday.utils import monday_json_stringify, gather_params @@ -58,13 +58,15 @@ def mutate_subitem_query(parent_item_id, subitem_name, column_values, str(create_labels_if_missing).lower()) -def get_item_query(board_id, column_id, value): +def get_item_query(board_id, column_id, value, limit=None, cursor=None): + columns = [{"column_id": str(column_id), "column_values": [str(value)]}] if not cursor else None + + raw_params = locals().items() + items_page_params = gather_params(raw_params, excluded_params=["column_id", "value"]) + query = '''query { - items_page_by_column_values( - board_id: %s, - columns: [{column_id: "%s", column_values: ["%s"]}] - ) { + items_page_by_column_values (%s) { cursor items { id @@ -84,7 +86,7 @@ def get_item_query(board_id, column_id, value): } } } - }''' % (board_id, column_id, value) + }''' % items_page_params return query @@ -116,7 +118,7 @@ def update_item_query(board_id, item_id, column_id, value): change_column_value( board_id: %s, item_id: %s, - column_id: %s, + column_id: "%s", value: %s ) { id @@ -136,7 +138,7 @@ def move_item_to_group_query(item_id, group_id): query = ''' mutation { - move_item_to_group (item_id: %s, group_id: %s) + move_item_to_group (item_id: %s, group_id: "%s") { id } @@ -250,7 +252,7 @@ def add_file_to_column_query(item_id, column_id): add_file_to_column ( file: $file, item_id: %s, - column_id: %s + column_id: "%s" ) { id } @@ -354,16 +356,18 @@ def get_tags_query(tags): # BOARD RESOURCE QUERIES -def get_board_items_query(board_id: Union[str, int], limit: Optional[int] = None, page: Optional[int] = None) -> str: +def get_board_items_query(board_id: Union[str, int], query_params: Optional[Mapping[str, Any]] = None, + limit: Optional[int] = None, cursor: Optional[str] = None) -> str: raw_params = locals().items() - item_params = gather_params(raw_params, exclusion_list=["board_id", "item_ids"]) - joined_params = f"({', '.join(item_params)})" if item_params else "" + items_page_params = gather_params(raw_params, excluded_params=["board_id"]) + wrapped_params = f"({items_page_params})" if items_page_params else "" query = '''query{ boards(ids: %s){ name - items_page { - items %s { + items_page %s { + cursor + items { group { id title @@ -379,7 +383,7 @@ def get_board_items_query(board_id: Union[str, int], limit: Optional[int] = None } } } - }''' % (board_id, joined_params) + }''' % (board_id, wrapped_params) return query @@ -520,14 +524,20 @@ def get_groups_by_board_query(board_ids): return query -def get_items_by_group_query(board_id, group_id): +def get_items_by_group_query(board_id: Union[int, str], group_id: str, + limit: Optional[int] = None, cursor: Optional[str] = None): + raw_params = locals().items() + items_page_params = gather_params(raw_params, excluded_params=["board_id", "group_id"]) + wrapped_params = f"({items_page_params})" if items_page_params else "" + query = '''query { boards(ids: %s) { groups(ids: "%s") { id title - items_page { + items_page %s { + cursor items { id name @@ -535,7 +545,7 @@ def get_items_by_group_query(board_id, group_id): } } } - }''' % (board_id, group_id) + }''' % (board_id, group_id, wrapped_params) return query diff --git a/monday/resources/boards.py b/monday/resources/boards.py index ed49d88..6d96696 100644 --- a/monday/resources/boards.py +++ b/monday/resources/boards.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Optional, Union, Any, Mapping from monday.query_joins import get_boards_query, get_boards_by_id_query, get_board_items_query, \ get_columns_by_board_query, create_board_by_workspace_query, duplicate_board_query @@ -13,15 +13,16 @@ def fetch_boards(self, limit: Optional[int] = None, page: Optional[int] = None, query = get_boards_query(limit, page, ids, board_kind, state, order_by) return self.client.execute(query) - def fetch_boards_by_id(self, board_ids): + 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, limit: Optional[int] = None, page: Optional[int] = None): - query = get_board_items_query(board_ids, limit=limit, page=page) + def fetch_items_by_board_id(self, board_ids: Union[int, str], query_params: Optional[Mapping[str, Any]] = None, + limit: Optional[int] = None, 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_columns_by_board_id(self, board_ids): + 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) diff --git a/monday/resources/groups.py b/monday/resources/groups.py index d61c5e6..e500146 100644 --- a/monday/resources/groups.py +++ b/monday/resources/groups.py @@ -8,8 +8,8 @@ def get_groups_by_board(self, board_ids): query = get_groups_by_board_query(board_ids=board_ids) return self.client.execute(query) - def get_items_by_group(self, board_id, group_id): - query = get_items_by_group_query(board_id=board_id, group_id=group_id) + def get_items_by_group(self, board_id, group_id, limit=None, cursor=None): + query = get_items_by_group_query(board_id=board_id, group_id=group_id, limit=limit, cursor=cursor) return self.client.execute(query) def create_group(self, board_id, group_name): diff --git a/monday/resources/items.py b/monday/resources/items.py index f833d9e..136c1f6 100644 --- a/monday/resources/items.py +++ b/monday/resources/items.py @@ -17,8 +17,8 @@ def create_subitem(self, parent_item_id, subitem_name, column_values=None, create_labels_if_missing) return self.client.execute(query) - def fetch_items_by_column_value(self, board_id, column_id, value): - query = get_item_query(board_id, column_id, value) + def fetch_items_by_column_value(self, board_id, column_id, value, limit=None, cursor=None): + query = get_item_query(board_id, column_id, value, limit, cursor) return self.client.execute(query) def fetch_items_by_id(self, ids): diff --git a/monday/resources/types.py b/monday/resources/types.py index 7ade90e..7a25ba5 100644 --- a/monday/resources/types.py +++ b/monday/resources/types.py @@ -63,3 +63,38 @@ class BoardsOrderBy(Enum): CREATED_AT = "created_at" USED_AT = "used_at" + + +class ItemsQueryOperator(Enum): + """Conditions between query rules""" + + AND = "and" + OR = "or" + + +class ItemsOrderByDirection(Enum): + """Direction to sort items in""" + + ASC = "asc" + DESC = "desc" + + +class ItemsQueryRuleOperator(Enum): + """Condition for value comparison""" + + ANY_OF = "any_of" + NOT_ANY_OF = "not_any_of" + IS_EMPTY = "is_empty" + IS_NOT_EMPTY = "is_not_empty" + GREATER_THAN = "greater_than" + GREATER_THAN_OR_EQUALS = "greater_than_or_equals" + LOWER_THAN = "lower_than" + LOWER_THAN_OR_EQUAL = "lower_than_or_equal" + BETWEEN = "between" + NOT_CONTAINS_TEXT = "not_contains_text" + CONTAINS_TEXT = "contains_text" + CONTAINS_TERMS = "contains_terms" + STARTS_WITH = "starts_with" + ENDS_WITH = "ends_with" + WITHIN_THE_NEXT = "within_the_next" + WITHIN_THE_LAST = "within_the_last" diff --git a/monday/tests/test_board_resource.py b/monday/tests/test_board_resource.py index c49766a..a822a42 100644 --- a/monday/tests/test_board_resource.py +++ b/monday/tests/test_board_resource.py @@ -1,5 +1,6 @@ from monday.query_joins import duplicate_board_query, create_board_by_workspace_query, get_boards_query, \ get_boards_by_id_query, get_board_items_query, get_columns_by_board_query +from monday.resources.types import ItemsQueryRuleOperator from monday.tests.test_case_resource import BaseTestCase @@ -41,12 +42,28 @@ def test_get_board_items_query(self): items_line = 'items' self.assertIn(items_line, query) - def test_get_board_items_query_with_limit_and_pages(self): + def test_get_board_items_query_with_limit_and_cursor(self): limit = 100 - page = 1 - query = get_board_items_query(board_id=self.board_id, limit=limit, page=page) - items_line = f'items (limit: {limit}, page: {page})' - self.assertIn(items_line, query) + cursor = 'MSw0NTc5ODYzMTkyLFRWX2ljOWt2MVpnT...' + query = get_board_items_query(board_id=self.board_id, limit=limit, cursor=cursor) + items_page_line = f'items_page (limit: {limit}, cursor: "{cursor}")' + self.assertIn(items_page_line, query) + + def test_get_board_items_query_with_query_params(self): + query_params = { + 'rules': { + 'column_id': 'timeline', + 'compare_value': ['2023-06-30', '2023-07-01'], + 'compare_attribute': 'START_DATE', + 'operator': ItemsQueryRuleOperator.BETWEEN + }} + query = get_board_items_query(board_id=self.board_id, query_params=query_params) + items_page_line = ('items_page (query_params: {rules: {' + 'column_id: "timeline", ' + 'compare_value: ["2023-06-30", "2023-07-01"], ' + 'compare_attribute: "START_DATE", ' + 'operator: between}') + self.assertIn(items_page_line, query) def test_get_columns_by_board_query(self): query = get_columns_by_board_query(board_ids=self.board_id) diff --git a/monday/tests/test_group_resource.py b/monday/tests/test_group_resource.py index bedaecb..d044254 100644 --- a/monday/tests/test_group_resource.py +++ b/monday/tests/test_group_resource.py @@ -21,6 +21,13 @@ def test_get_items_by_group_query(self): self.assertIn(str(self.board_id), query) self.assertIn(str(self.group_id), query) + def test_get_items_by_group_query_with_limit_and_cursor(self): + limit = 25 + cursor = 'MSw5NzI4MDA5MDAsaV9YcmxJb0p1VEdY...' + query = get_items_by_group_query(board_id=self.board_id, group_id=self.group_id, limit=limit, cursor=cursor) + items_page_line = f'items_page (limit: {limit}, cursor: "{cursor}")' + self.assertIn(items_page_line, query) + def test_duplicate_group_query(self): query = duplicate_group_query(board_id=self.board_id, group_id=self.group_id) self.assertIn(str(self.board_id), query) diff --git a/monday/tests/test_item_resource.py b/monday/tests/test_item_resource.py index 5623781..1fff9f7 100644 --- a/monday/tests/test_item_resource.py +++ b/monday/tests/test_item_resource.py @@ -25,6 +25,15 @@ def test_get_item_query(self): self.assertIn(self.column_id, query) self.assertIn("foo", query) + def test_get_item_query_with_limit_and_cursor(self): + limit = 10 + cursor = "MSw0NTc5ODYzMTkyLFRWX2ljOW..." + query = get_item_query(board_id=self.board_id, column_id=None, value="foo", limit=limit, cursor=cursor) + items_page_line = f'items_page_by_column_values (board_id: {self.board_id}, limit: {limit}, cursor: "{cursor}")' + self.assertIn(items_page_line, query) + self.assertNotIn(self.column_id, query) + self.assertNotIn("foo", query) + def test_update_item_query(self): query = update_item_query( board_id=self.board_id, item_id=self.item_id, column_id=self.column_id, value="foo") diff --git a/monday/utils.py b/monday/utils.py index afac464..ef95127 100644 --- a/monday/utils.py +++ b/monday/utils.py @@ -1,6 +1,6 @@ import json from enum import Enum -from typing import List +from typing import List, Iterable, Tuple, Any, Optional def monday_json_stringify(value): @@ -13,13 +13,22 @@ def monday_json_stringify(value): return json.dumps(json.dumps(value)) -def gather_params(params, exclusion_list: List[str]) -> List[str]: - valid_params: List[str] = [] - for param, value in params: - if value is None or param in exclusion_list: - continue - if isinstance(value, Enum): - valid_params.append(f"{param}: {value.value}") - continue - valid_params.append(f"{param}: {value}") - return valid_params +def gather_params(params: Iterable[Tuple[str, Any]], excluded_params: Optional[List[str]] = None, + exclude_none: bool = True) -> str: + valid_params = [f"{param}: {format_param_value(value)}" for param, value in params + if not ((excluded_params and param in excluded_params) or (value is None and exclude_none))] + return ', '.join(valid_params) + + +def format_param_value(value: Any) -> str: + if value is None: + return 'null' + if isinstance(value, str): + return f'"{value}"' + if isinstance(value, Enum): + return str(value.value) + if isinstance(value, dict): + return f"{{{gather_params(value.items(), exclude_none=False)}}}" + if isinstance(value, list): + return f"[{', '.join(format_param_value(val) for val in value)}]" + return str(value)