Skip to content

Commit f5cbddc

Browse files
committed
add TaskCommentsResource with list/get/add/delete/edit endpoints; wire into client and exports; add unit tests
1 parent 6a84e71 commit f5cbddc

7 files changed

Lines changed: 283 additions & 0 deletions

File tree

‎crowdin_api/api_resources/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .source_strings.resource import SourceStringsResource
1818
from .storages.resource import StoragesResource
1919
from .string_comments.resource import StringCommentsResource
20+
from .task_comments.resource import TaskCommentsResource
2021
from .string_translations.resource import StringTranslationsResource
2122
from .tasks.resource import TasksResource, EnterpriseTasksResource
2223
from .teams.resource import TeamsResource
@@ -51,6 +52,7 @@
5152
"StoragesResource",
5253
"StringCommentsResource",
5354
"StringTranslationsResource",
55+
"TaskCommentsResource",
5456
"TasksResource",
5557
"EnterpriseTasksResource",
5658
"TeamsResource",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .resource import TaskCommentsResource
2+
3+
__all__ = [
4+
"TaskCommentsResource",
5+
]
6+
7+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from enum import Enum
2+
3+
4+
class TaskCommentPatchPath(Enum):
5+
TEXT = "/text"
6+
7+
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from typing import Iterable, Optional
2+
3+
from crowdin_api.api_resources.abstract.resources import BaseResource
4+
from crowdin_api.api_resources.task_comments.types import TaskCommentPatchRequest
5+
from crowdin_api.sorting import Sorting
6+
7+
8+
class TaskCommentsResource(BaseResource):
9+
"""
10+
Resource for Task Comments.
11+
12+
Link to documentation:
13+
https://developer.crowdin.com/api/v2/#tag/Tasks
14+
"""
15+
16+
def get_task_comments_path(
17+
self,
18+
projectId: int,
19+
taskId: int,
20+
taskCommentId: Optional[int] = None,
21+
):
22+
if taskCommentId is not None:
23+
return f"projects/{projectId}/tasks/{taskId}/comments/{taskCommentId}"
24+
25+
return f"projects/{projectId}/tasks/{taskId}/comments"
26+
27+
def list_task_comments(
28+
self,
29+
taskId: int,
30+
projectId: Optional[int] = None,
31+
orderBy: Optional[Sorting] = None,
32+
page: Optional[int] = None,
33+
offset: Optional[int] = None,
34+
limit: Optional[int] = None,
35+
):
36+
"""
37+
List Task Comments.
38+
"""
39+
40+
projectId = projectId or self.get_project_id()
41+
params = {"orderBy": orderBy}
42+
params.update(self.get_page_params(page=page, offset=offset, limit=limit))
43+
44+
return self._get_entire_data(
45+
method="get",
46+
path=self.get_task_comments_path(projectId=projectId, taskId=taskId),
47+
params=params,
48+
)
49+
50+
def add_task_comment(
51+
self,
52+
text: str,
53+
taskId: int,
54+
projectId: Optional[int] = None,
55+
):
56+
"""
57+
Add Task Comment.
58+
"""
59+
60+
projectId = projectId or self.get_project_id()
61+
62+
return self.requester.request(
63+
method="post",
64+
path=self.get_task_comments_path(projectId=projectId, taskId=taskId),
65+
request_data={"text": text},
66+
)
67+
68+
def get_task_comment(
69+
self,
70+
taskCommentId: int,
71+
taskId: int,
72+
projectId: Optional[int] = None,
73+
):
74+
"""
75+
Get Task Comment.
76+
"""
77+
78+
projectId = projectId or self.get_project_id()
79+
80+
return self.requester.request(
81+
method="get",
82+
path=self.get_task_comments_path(
83+
projectId=projectId, taskId=taskId, taskCommentId=taskCommentId
84+
),
85+
)
86+
87+
def delete_task_comment(
88+
self,
89+
taskCommentId: int,
90+
taskId: int,
91+
projectId: Optional[int] = None,
92+
):
93+
"""
94+
Delete Task Comment.
95+
"""
96+
97+
projectId = projectId or self.get_project_id()
98+
99+
return self.requester.request(
100+
method="delete",
101+
path=self.get_task_comments_path(
102+
projectId=projectId, taskId=taskId, taskCommentId=taskCommentId
103+
),
104+
)
105+
106+
def edit_task_comment(
107+
self,
108+
taskCommentId: int,
109+
data: Iterable[TaskCommentPatchRequest],
110+
taskId: int,
111+
projectId: Optional[int] = None,
112+
):
113+
"""
114+
Edit Task Comment.
115+
"""
116+
117+
projectId = projectId or self.get_project_id()
118+
119+
return self.requester.request(
120+
method="patch",
121+
path=self.get_task_comments_path(
122+
projectId=projectId, taskId=taskId, taskCommentId=taskCommentId
123+
),
124+
request_data=data,
125+
)
126+
127+
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
from unittest import mock
2+
3+
import pytest
4+
from crowdin_api.api_resources.enums import PatchOperation
5+
from crowdin_api.api_resources.task_comments.enums import TaskCommentPatchPath
6+
from crowdin_api.api_resources.task_comments.resource import TaskCommentsResource
7+
from crowdin_api.requester import APIRequester
8+
from crowdin_api.sorting import Sorting
9+
10+
11+
class TestTaskCommentsResource:
12+
resource_class = TaskCommentsResource
13+
14+
def get_resource(self, base_absolut_url):
15+
return self.resource_class(requester=APIRequester(base_url=base_absolut_url))
16+
17+
def test_resource_with_id(self, base_absolut_url):
18+
project_id = 1
19+
resource = self.resource_class(
20+
requester=APIRequester(base_url=base_absolut_url), project_id=project_id
21+
)
22+
assert resource.get_project_id() == project_id
23+
24+
@pytest.mark.parametrize(
25+
"incoming_data, path",
26+
(
27+
({"projectId": 1, "taskId": 2}, "projects/1/tasks/2/comments"),
28+
(
29+
{"projectId": 1, "taskId": 2, "taskCommentId": 3},
30+
"projects/1/tasks/2/comments/3",
31+
),
32+
),
33+
)
34+
def test_get_task_comments_path(self, incoming_data, path, base_absolut_url):
35+
resource = self.get_resource(base_absolut_url)
36+
assert resource.get_task_comments_path(**incoming_data) == path
37+
38+
@pytest.mark.parametrize(
39+
"incoming_data, request_params",
40+
(
41+
({}, {"orderBy": None, "offset": 0, "limit": 25}),
42+
({"orderBy": Sorting([])}, {"orderBy": Sorting([]), "offset": 0, "limit": 25}),
43+
),
44+
)
45+
@mock.patch("crowdin_api.requester.APIRequester.request")
46+
def test_list_task_comments(self, m_request, incoming_data, request_params, base_absolut_url):
47+
m_request.return_value = "response"
48+
49+
resource = self.get_resource(base_absolut_url)
50+
assert resource.list_task_comments(projectId=1, taskId=2, **incoming_data) == "response"
51+
m_request.assert_called_once_with(
52+
method="get",
53+
params=request_params,
54+
path=resource.get_task_comments_path(projectId=1, taskId=2),
55+
)
56+
57+
@mock.patch("crowdin_api.requester.APIRequester.request")
58+
def test_add_task_comment(self, m_request, base_absolut_url):
59+
m_request.return_value = "response"
60+
61+
resource = self.get_resource(base_absolut_url)
62+
assert resource.add_task_comment(projectId=1, taskId=2, text="hello") == "response"
63+
m_request.assert_called_once_with(
64+
method="post",
65+
path=resource.get_task_comments_path(projectId=1, taskId=2),
66+
request_data={"text": "hello"},
67+
)
68+
69+
@mock.patch("crowdin_api.requester.APIRequester.request")
70+
def test_get_task_comment(self, m_request, base_absolut_url):
71+
m_request.return_value = "response"
72+
73+
resource = self.get_resource(base_absolut_url)
74+
assert resource.get_task_comment(projectId=1, taskId=2, taskCommentId=3) == "response"
75+
m_request.assert_called_once_with(
76+
method="get",
77+
path=resource.get_task_comments_path(projectId=1, taskId=2, taskCommentId=3),
78+
)
79+
80+
@mock.patch("crowdin_api.requester.APIRequester.request")
81+
def test_delete_task_comment(self, m_request, base_absolut_url):
82+
m_request.return_value = "response"
83+
84+
resource = self.get_resource(base_absolut_url)
85+
assert resource.delete_task_comment(projectId=1, taskId=2, taskCommentId=3) == "response"
86+
m_request.assert_called_once_with(
87+
method="delete",
88+
path=resource.get_task_comments_path(projectId=1, taskId=2, taskCommentId=3),
89+
)
90+
91+
@mock.patch("crowdin_api.requester.APIRequester.request")
92+
def test_edit_task_comment(self, m_request, base_absolut_url):
93+
m_request.return_value = "response"
94+
95+
data = [
96+
{
97+
"value": "new text",
98+
"op": PatchOperation.REPLACE,
99+
"path": TaskCommentPatchPath.TEXT,
100+
}
101+
]
102+
103+
resource = self.get_resource(base_absolut_url)
104+
assert (
105+
resource.edit_task_comment(projectId=1, taskId=2, taskCommentId=3, data=data)
106+
== "response"
107+
)
108+
m_request.assert_called_once_with(
109+
method="patch",
110+
path=resource.get_task_comments_path(projectId=1, taskId=2, taskCommentId=3),
111+
request_data=data,
112+
)
113+
114+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import Any
2+
3+
from crowdin_api.api_resources.enums import PatchOperation
4+
from crowdin_api.api_resources.task_comments.enums import TaskCommentPatchPath
5+
from crowdin_api.typing import TypedDict
6+
7+
8+
class TaskCommentPatchRequest(TypedDict):
9+
value: Any
10+
op: PatchOperation
11+
path: TaskCommentPatchPath
12+
13+

‎crowdin_api/client.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,19 @@ def string_translations(self) -> api_resources.StringTranslationsResource:
360360
requester=self.get_api_requestor(), page_size=self.PAGE_SIZE
361361
)
362362

363+
@property
364+
def task_comments(self) -> api_resources.TaskCommentsResource:
365+
if self.PROJECT_ID:
366+
return api_resources.TaskCommentsResource(
367+
requester=self.get_api_requestor(),
368+
project_id=self.PROJECT_ID,
369+
page_size=self.PAGE_SIZE,
370+
)
371+
372+
return api_resources.TaskCommentsResource(
373+
requester=self.get_api_requestor(), page_size=self.PAGE_SIZE
374+
)
375+
363376
@property
364377
def tasks(self) -> Union[api_resources.TasksResource, api_resources.EnterpriseTasksResource]:
365378
if self._is_enterprise_platform:

0 commit comments

Comments
 (0)