Skip to content

Commit 8120d36

Browse files
authored
feat: add Style Guides API support (#251)
1 parent 56046cf commit 8120d36

8 files changed

Lines changed: 325 additions & 3 deletions

File tree

crowdin_api/api_resources/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,21 @@
1010
from .languages.resource import LanguagesResource
1111
from .machine_translation_engines.resource import MachineTranslationEnginesResource
1212
from .projects.resource import ProjectsResource
13-
from .reports.resource import ReportsResource, EnterpriseReportsResource
13+
from .reports.resource import EnterpriseReportsResource, ReportsResource
1414
from .screenshots.resource import ScreenshotsResource
1515
from .security_logs.resource import SecurityLogsResource
1616
from .source_files.resource import SourceFilesResource
1717
from .source_strings.resource import SourceStringsResource
1818
from .storages.resource import StoragesResource
1919
from .string_comments.resource import StringCommentsResource
2020
from .string_translations.resource import StringTranslationsResource
21-
from .tasks.resource import TasksResource, EnterpriseTasksResource
21+
from .style_guides.resource import StyleGuidesResource
22+
from .tasks.resource import EnterpriseTasksResource, TasksResource
2223
from .teams.resource import TeamsResource
2324
from .translation_memory.resource import TranslationMemoryResource
2425
from .translation_status.resource import TranslationStatusResource
2526
from .translations.resource import TranslationsResource
26-
from .users.resource import UsersResource, EnterpriseUsersResource
27+
from .users.resource import EnterpriseUsersResource, UsersResource
2728
from .vendors.resource import VendorsResource
2829
from .webhooks.resource import WebhooksResource
2930
from .workflows.resource import WorkflowsResource
@@ -51,6 +52,7 @@
5152
"StoragesResource",
5253
"StringCommentsResource",
5354
"StringTranslationsResource",
55+
"StyleGuidesResource",
5456
"TasksResource",
5557
"EnterpriseTasksResource",
5658
"TeamsResource",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pdoc__ = {"tests": False}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from enum import Enum
2+
3+
4+
class ListStyleGuidesOrderBy(Enum):
5+
ID = "id"
6+
NAME = "name"
7+
USER_ID = "userId"
8+
CREATED_AT = "createdAt"
9+
10+
11+
class StyleGuidePatchPath(Enum):
12+
NAME = "/name"
13+
AI_INSTRUCTIONS = "/aiInstructions"
14+
LANGUAGE_IDS = "/languageIds"
15+
PROJECT_IDS = "/projectIds"
16+
IS_SHARED = "/isShared"
17+
STORAGE_ID = "/storageId"
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from typing import Iterable, Optional
2+
3+
from crowdin_api.api_resources.abstract.resources import BaseResource
4+
from crowdin_api.api_resources.style_guides.types import (
5+
AddStyleGuideRequest,
6+
StyleGuidePatchRequest,
7+
)
8+
from crowdin_api.sorting import Sorting
9+
10+
11+
class StyleGuidesResource(BaseResource):
12+
"""
13+
Resource for Style Guides.
14+
15+
Link to documentation:
16+
https://support.crowdin.com/developer/api/v2/#tag/Style-Guides
17+
"""
18+
19+
def get_style_guides_path(self, style_guide_id: Optional[int] = None):
20+
if style_guide_id is not None:
21+
return f"style-guides/{style_guide_id}"
22+
return "style-guides"
23+
24+
def list_style_guides(
25+
self,
26+
order_by: Optional[Sorting] = None,
27+
user_id: Optional[int] = None,
28+
limit: Optional[int] = None,
29+
offset: Optional[int] = None,
30+
):
31+
"""
32+
List Style Guides
33+
34+
Link to documentation:
35+
https://support.crowdin.com/developer/api/v2/#operation/api.style-guides.getMany
36+
"""
37+
params = {
38+
"orderBy": order_by,
39+
"userId": user_id,
40+
}
41+
params.update(self.get_page_params(limit=limit, offset=offset))
42+
43+
return self._get_entire_data(
44+
method="get",
45+
path=self.get_style_guides_path(),
46+
params=params,
47+
)
48+
49+
def add_style_guide(self, request_data: AddStyleGuideRequest):
50+
"""
51+
Create Style Guide
52+
53+
Link to documentation:
54+
https://support.crowdin.com/developer/api/v2/#operation/api.style-guides.post
55+
"""
56+
return self.requester.request(
57+
method="post",
58+
path=self.get_style_guides_path(),
59+
request_data=request_data,
60+
)
61+
62+
def get_style_guide(self, style_guide_id: int):
63+
"""
64+
Get Style Guide
65+
66+
Link to documentation:
67+
https://support.crowdin.com/developer/api/v2/#operation/api.style-guides.get
68+
"""
69+
return self.requester.request(
70+
method="get",
71+
path=self.get_style_guides_path(style_guide_id),
72+
)
73+
74+
def delete_style_guide(self, style_guide_id: int):
75+
"""
76+
Delete Style Guide
77+
78+
Link to documentation:
79+
https://support.crowdin.com/developer/api/v2/#operation/api.style-guides.delete
80+
"""
81+
return self.requester.request(
82+
method="delete",
83+
path=self.get_style_guides_path(style_guide_id),
84+
)
85+
86+
def edit_style_guide(
87+
self,
88+
style_guide_id: int,
89+
request_data: Iterable[StyleGuidePatchRequest],
90+
):
91+
"""
92+
Edit Style Guide
93+
94+
Link to documentation:
95+
https://support.crowdin.com/developer/api/v2/#operation/api.style-guides.patch
96+
"""
97+
return self.requester.request(
98+
method="patch",
99+
path=self.get_style_guides_path(style_guide_id),
100+
request_data=request_data,
101+
)
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
from unittest import mock
2+
3+
import pytest
4+
from crowdin_api.api_resources.enums import PatchOperation
5+
from crowdin_api.api_resources.style_guides.enums import (
6+
ListStyleGuidesOrderBy,
7+
StyleGuidePatchPath,
8+
)
9+
from crowdin_api.api_resources.style_guides.resource import StyleGuidesResource
10+
from crowdin_api.api_resources.style_guides.types import AddStyleGuideRequest
11+
from crowdin_api.requester import APIRequester
12+
from crowdin_api.sorting import Sorting, SortingOrder, SortingRule
13+
14+
15+
class TestStyleGuidesResource:
16+
resource_class = StyleGuidesResource
17+
18+
def get_resource(self, base_absolut_url):
19+
return self.resource_class(requester=APIRequester(base_url=base_absolut_url))
20+
21+
@pytest.mark.parametrize(
22+
"in_params, path",
23+
(
24+
({}, "style-guides"),
25+
({"style_guide_id": 2}, "style-guides/2"),
26+
),
27+
)
28+
def test_get_style_guides_path(self, in_params, path, base_absolut_url):
29+
resource = self.get_resource(base_absolut_url)
30+
assert resource.get_style_guides_path(**in_params) == path
31+
32+
@pytest.mark.parametrize(
33+
"incoming_data, request_params",
34+
(
35+
(
36+
{},
37+
{
38+
"orderBy": None,
39+
"userId": None,
40+
"limit": 25,
41+
"offset": 0,
42+
},
43+
),
44+
(
45+
{
46+
"order_by": Sorting(
47+
[SortingRule(ListStyleGuidesOrderBy.CREATED_AT, SortingOrder.DESC)]
48+
),
49+
"user_id": 2,
50+
"limit": 25,
51+
"offset": 0,
52+
},
53+
{
54+
"orderBy": Sorting(
55+
[SortingRule(ListStyleGuidesOrderBy.CREATED_AT, SortingOrder.DESC)]
56+
),
57+
"userId": 2,
58+
"limit": 25,
59+
"offset": 0,
60+
},
61+
),
62+
),
63+
)
64+
@mock.patch("crowdin_api.requester.APIRequester.request")
65+
def test_list_style_guides(self, m_request, incoming_data, request_params, base_absolut_url):
66+
m_request.return_value = "response"
67+
68+
resource = self.get_resource(base_absolut_url)
69+
assert resource.list_style_guides(**incoming_data) == "response"
70+
71+
m_request.assert_called_once_with(
72+
method="get",
73+
path="style-guides",
74+
params=request_params,
75+
)
76+
77+
@pytest.mark.parametrize(
78+
"incoming_data, request_data",
79+
(
80+
(
81+
AddStyleGuideRequest(
82+
name="Be My Eyes iOS's Style Guide",
83+
storageId=1,
84+
),
85+
{
86+
"name": "Be My Eyes iOS's Style Guide",
87+
"storageId": 1,
88+
},
89+
),
90+
(
91+
AddStyleGuideRequest(
92+
name="Be My Eyes iOS's Style Guide",
93+
storageId=1,
94+
aiInstructions="Rules to be used by AI models",
95+
languageIds=["uk", "fr", "de"],
96+
projectIds=[1, 2, 3],
97+
isShared=False,
98+
),
99+
{
100+
"name": "Be My Eyes iOS's Style Guide",
101+
"storageId": 1,
102+
"aiInstructions": "Rules to be used by AI models",
103+
"languageIds": ["uk", "fr", "de"],
104+
"projectIds": [1, 2, 3],
105+
"isShared": False,
106+
},
107+
),
108+
),
109+
)
110+
@mock.patch("crowdin_api.requester.APIRequester.request")
111+
def test_add_style_guide(self, m_request, incoming_data, request_data, base_absolut_url):
112+
m_request.return_value = "response"
113+
114+
resource = self.get_resource(base_absolut_url)
115+
assert resource.add_style_guide(incoming_data) == "response"
116+
117+
m_request.assert_called_once_with(
118+
method="post",
119+
path="style-guides",
120+
request_data=request_data,
121+
)
122+
123+
@mock.patch("crowdin_api.requester.APIRequester.request")
124+
def test_get_style_guide(self, m_request, base_absolut_url):
125+
m_request.return_value = "response"
126+
127+
resource = self.get_resource(base_absolut_url)
128+
assert resource.get_style_guide(style_guide_id=2) == "response"
129+
130+
m_request.assert_called_once_with(
131+
method="get",
132+
path="style-guides/2",
133+
)
134+
135+
@mock.patch("crowdin_api.requester.APIRequester.request")
136+
def test_delete_style_guide(self, m_request, base_absolut_url):
137+
m_request.return_value = "response"
138+
139+
resource = self.get_resource(base_absolut_url)
140+
assert resource.delete_style_guide(style_guide_id=2) == "response"
141+
142+
m_request.assert_called_once_with(
143+
method="delete",
144+
path="style-guides/2",
145+
)
146+
147+
@mock.patch("crowdin_api.requester.APIRequester.request")
148+
def test_edit_style_guide(self, m_request, base_absolut_url):
149+
m_request.return_value = "response"
150+
151+
request_data = [
152+
{
153+
"op": PatchOperation.REPLACE,
154+
"path": StyleGuidePatchPath.NAME,
155+
"value": "Be My Eyes iOS's Style Guide",
156+
}
157+
]
158+
159+
resource = self.get_resource(base_absolut_url)
160+
assert resource.edit_style_guide(style_guide_id=2, request_data=request_data) == "response"
161+
162+
m_request.assert_called_once_with(
163+
method="patch",
164+
path="style-guides/2",
165+
request_data=request_data,
166+
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import Any, Iterable, Optional
2+
3+
from crowdin_api.api_resources.enums import PatchOperation
4+
from crowdin_api.api_resources.style_guides.enums import StyleGuidePatchPath
5+
from crowdin_api.typing import TypedDict
6+
7+
8+
class AddStyleGuideRequest(TypedDict):
9+
name: str
10+
storageId: int
11+
aiInstructions: Optional[str]
12+
languageIds: Optional[Iterable[str]]
13+
projectIds: Optional[Iterable[int]]
14+
isShared: Optional[bool]
15+
16+
17+
class StyleGuidePatchRequest(TypedDict):
18+
op: PatchOperation
19+
path: StyleGuidePatchPath
20+
value: Any

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 style_guides(self) -> api_resources.StyleGuidesResource:
365+
if self.PROJECT_ID:
366+
return api_resources.StyleGuidesResource(
367+
requester=self.get_api_requestor(),
368+
project_id=self.PROJECT_ID,
369+
page_size=self.PAGE_SIZE,
370+
)
371+
372+
return api_resources.StyleGuidesResource(
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:

crowdin_api/tests/test_client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ def test_api_requestor_custom_values(self, m_APIRequester):
177177
("storages", "StoragesResource"),
178178
("string_comments", "StringCommentsResource"),
179179
("string_translations", "StringTranslationsResource"),
180+
("style_guides", "StyleGuidesResource"),
180181
("tasks", "TasksResource"),
181182
("translation_memory", "TranslationMemoryResource"),
182183
("translation_status", "TranslationStatusResource"),
@@ -253,6 +254,7 @@ class TestCrowdinClientEnterprise:
253254
("storages", "StoragesResource"),
254255
("string_comments", "StringCommentsResource"),
255256
("string_translations", "StringTranslationsResource"),
257+
("style_guides", "StyleGuidesResource"),
256258
("tasks", "EnterpriseTasksResource"),
257259
("teams", "TeamsResource"),
258260
("translation_memory", "TranslationMemoryResource"),

0 commit comments

Comments
 (0)