diff --git a/README.md b/README.md index 9185707..9b86a1c 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,30 @@ print(client.projects.with_fetch_all().list_projects()) print(client.projects.with_fetch_all(1000).list_projects()) ``` +### Sorting + +An optional `orderBy` parameter is used to apply sorting. + +```python +from crowdin_api import CrowdinClient +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule +from crowdin_api.api_resources.projects.enums import ListProjectsOrderBy + +client = CrowdinClient(token='__token__') + +sorting = Sorting( + [ + SortingRule(ListProjectsOrderBy.ID, SortingOrder.ASC), + SortingRule(ListProjectsOrderBy.NAME), + SortingRule(ListProjectsOrderBy.CREATED_AT, SortingOrder.DESC), + ] +) + +print(client.projects.list_projects(orderBy=sorting)) +``` + +Enum `SortingOrder` is also optional (ascending order applied by default). + ### Extended request parameters The `EXTENDED_REQUEST_PARAMS` parameter allows you to set additional parameters for requests. For example, you can configure proxies or certificates. diff --git a/crowdin_api/api_resources/glossaries/enums.py b/crowdin_api/api_resources/glossaries/enums.py index 9fa7ee2..149c170 100644 --- a/crowdin_api/api_resources/glossaries/enums.py +++ b/crowdin_api/api_resources/glossaries/enums.py @@ -73,3 +73,41 @@ class TermGender(Enum): FEMININE = "feminine" NEUTER = "neuter" OTHER = "other" + + +class ListConceptsOrderBy(Enum): + ID = "id" + SUBJECT = "subject" + DEFINITION = "definition" + NOTE = "note" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" + + +class ListGlossariesCrowdinOrderBy(Enum): + ID = "id" + NAME = "name" + USER_ID = "userId" + CREATED_AT = "createdAt" + + +class ListGlossariesEnterpriseOrderBy(Enum): + ID = "id" + NAME = "name" + GROUP_ID = "groupId" + USER_ID = "userId" + CREATED_AT = "createdAt" + + +class ListTermsOrderBy(Enum): + ID = "id" + TEXT = "text" + DESCRIPTION = "description" + PART_OF_SPEECH = "partOfSpeech" + STATUS = "status" + TYPE = "type" + GENDER = "gender" + NOTE = "note" + LEMMA = "lemma" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" diff --git a/crowdin_api/api_resources/glossaries/resource.py b/crowdin_api/api_resources/glossaries/resource.py index e7fd705..133f64a 100644 --- a/crowdin_api/api_resources/glossaries/resource.py +++ b/crowdin_api/api_resources/glossaries/resource.py @@ -13,6 +13,7 @@ LanguagesDetails, GlossarySchemaRequest, ) +from crowdin_api.sorting import Sorting class GlossariesResource(BaseResource): @@ -38,6 +39,7 @@ def get_glossaries_path(self, glossaryId: Optional[int] = None): def list_glossaries( self, + orderBy: Optional[Sorting] = None, groupId: Optional[int] = None, page: Optional[int] = None, offset: Optional[int] = None, @@ -50,7 +52,7 @@ def list_glossaries( https://developer.crowdin.com/api/v2/#operation/api.glossaries.getMany """ - params = {"groupId": groupId} + params = {"orderBy": orderBy, "groupId": groupId} params.update(self.get_page_params(page=page, offset=offset, limit=limit)) return self._get_entire_data( @@ -239,6 +241,7 @@ def get_terms_path(self, glossaryId: int, termId: Optional[int] = None): def list_terms( self, glossaryId: int, + orderBy: Optional[Sorting] = None, userId: Optional[int] = None, languageId: Optional[str] = None, conceptId: Optional[int] = None, @@ -254,6 +257,7 @@ def list_terms( """ params = { + "orderBy": orderBy, "userId": userId, "languageId": languageId, "conceptId": conceptId, @@ -302,7 +306,7 @@ def add_term( "gender": gender, "note": note, "url": url, - "conceptId": conceptId + "conceptId": conceptId, }, ) @@ -377,8 +381,9 @@ def get_concepts_path(self, glossaryId: int, conceptId: Optional[int] = None): def list_concepts( self, glossaryId: int, + orderBy: Optional[Sorting] = None, offset: Optional[int] = None, - limit: Optional[int] = None + limit: Optional[int] = None, ): """ List Concepts. @@ -387,10 +392,13 @@ def list_concepts( https://developer.crowdin.com/api/v2/#operation/api.glossaries.concepts.getMany """ + params = {"orderBy": orderBy} + params.update(self.get_page_params(offset=offset, limit=limit)) + return self._get_entire_data( method="get", path=self.get_concepts_path(glossaryId=glossaryId), - params=self.get_page_params(offset=offset, limit=limit), + params=params, ) def get_concept(self, glossaryId: int, conceptId: int): diff --git a/crowdin_api/api_resources/glossaries/tests/test_glossaries_resources.py b/crowdin_api/api_resources/glossaries/tests/test_glossaries_resources.py index d418ac6..81e0370 100644 --- a/crowdin_api/api_resources/glossaries/tests/test_glossaries_resources.py +++ b/crowdin_api/api_resources/glossaries/tests/test_glossaries_resources.py @@ -4,6 +4,9 @@ from crowdin_api.api_resources.enums import PatchOperation from crowdin_api.api_resources.glossaries.enums import ( GlossaryPatchPath, + ListConceptsOrderBy, + ListGlossariesEnterpriseOrderBy, + ListTermsOrderBy, TermPartOfSpeech, TermPatchPath, TermStatus, @@ -11,9 +14,11 @@ TermGender, GlossaryFormat, GlossaryExportFields, + ListGlossariesCrowdinOrderBy, ) from crowdin_api.api_resources.glossaries.resource import GlossariesResource from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestGlossariesResource: @@ -47,6 +52,7 @@ def test_get_screenshots_path(self, in_params, path, base_absolut_url): ( {}, { + "orderBy": None, "groupId": None, "offset": 0, "limit": 25, @@ -54,14 +60,52 @@ def test_get_screenshots_path(self, in_params, path, base_absolut_url): ), ( { + "orderBy": Sorting( + [ + SortingRule( + ListGlossariesCrowdinOrderBy.NAME, SortingOrder.DESC + ) + ] + ), "groupId": 1, }, { + "orderBy": Sorting( + [ + SortingRule( + ListGlossariesCrowdinOrderBy.NAME, SortingOrder.DESC + ) + ] + ), "groupId": 1, "offset": 0, "limit": 25, }, ), + ( + { + "orderBy": Sorting( + [ + SortingRule( + ListGlossariesEnterpriseOrderBy.NAME, SortingOrder.DESC + ) + ] + ), + "groupId": 1, + }, + { + "orderBy": Sorting( + [ + SortingRule( + ListGlossariesEnterpriseOrderBy.NAME, SortingOrder.DESC + ) + ] + ), + "groupId": 1, + "offset": 0, + "limit": 25, + }, + ) ), ) @mock.patch("crowdin_api.requester.APIRequester.request") @@ -72,8 +116,8 @@ def test_list_glossaries(self, m_request, incoming_data, request_params, base_ab assert resource.list_glossaries(**incoming_data) == "response" m_request.assert_called_once_with( method="get", - params=request_params, path=resource.get_glossaries_path(), + params=request_params, ) @mock.patch("crowdin_api.requester.APIRequester.request") @@ -269,6 +313,7 @@ def test_get_terms_path(self, in_params, path, base_absolut_url): ( {}, { + "orderBy": None, "userId": None, "languageId": None, "conceptId": None, @@ -279,12 +324,18 @@ def test_get_terms_path(self, in_params, path, base_absolut_url): ), ( { + "orderBy": Sorting( + [SortingRule(ListTermsOrderBy.ID, SortingOrder.DESC)] + ), "userId": 1, "languageId": "ua", "conceptId": 2, "croql": "status = 'preferred'", }, { + "orderBy": Sorting( + [SortingRule(ListTermsOrderBy.ID, SortingOrder.DESC)] + ), "userId": 1, "languageId": "ua", "conceptId": 2, @@ -447,10 +498,27 @@ def test_get_concepts_path(self, in_params, path, base_absolut_url): ( {}, { + "orderBy": None, "offset": 0, "limit": 25, }, ), + ( + { + "orderBy": Sorting( + [SortingRule(ListConceptsOrderBy.ID, SortingOrder.DESC)] + ), + "offset": 10, + "limit": 50, + }, + { + "orderBy": Sorting( + [SortingRule(ListConceptsOrderBy.ID, SortingOrder.DESC)] + ), + "offset": 10, + "limit": 50, + }, + ), ), ) @mock.patch("crowdin_api.requester.APIRequester.request") diff --git a/crowdin_api/api_resources/groups/enums.py b/crowdin_api/api_resources/groups/enums.py index 0e12dbd..c2f09b4 100644 --- a/crowdin_api/api_resources/groups/enums.py +++ b/crowdin_api/api_resources/groups/enums.py @@ -5,3 +5,11 @@ class GroupPatchPath(Enum): NAME = "/name" DESCRIPTION = "/description" PARENT_ID = "/parentId" + + +class ListGroupsOrderBy(Enum): + ID = "id" + NAME = "name" + DESCRIPTION = "description" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" diff --git a/crowdin_api/api_resources/groups/resource.py b/crowdin_api/api_resources/groups/resource.py index 9986e50..339e4db 100644 --- a/crowdin_api/api_resources/groups/resource.py +++ b/crowdin_api/api_resources/groups/resource.py @@ -2,6 +2,7 @@ from crowdin_api.api_resources.abstract.resources import BaseResource from crowdin_api.api_resources.groups.types import GroupPatchRequest +from crowdin_api.sorting import Sorting class GroupsResource(BaseResource): @@ -63,6 +64,7 @@ def add_group( def list_groups( self, + orderBy: Optional[Sorting] = None, parentId: Optional[int] = None, limit: Optional[int] = None, offset: Optional[int] = None @@ -74,7 +76,7 @@ def list_groups( https://developer.crowdin.com/enterprise/api/v2/#operation/api.groups.getMany """ - params = {"parentId": parentId} + params = {"orderBy": orderBy, "parentId": parentId} params.update(self.get_page_params(offset=offset, limit=limit)) return self._get_entire_data( diff --git a/crowdin_api/api_resources/groups/tests/test_groups_resources.py b/crowdin_api/api_resources/groups/tests/test_groups_resources.py index 715e871..3ec8adc 100644 --- a/crowdin_api/api_resources/groups/tests/test_groups_resources.py +++ b/crowdin_api/api_resources/groups/tests/test_groups_resources.py @@ -4,8 +4,9 @@ from crowdin_api.api_resources import GroupsResource from crowdin_api.api_resources.enums import PatchOperation -from crowdin_api.api_resources.groups.enums import GroupPatchPath +from crowdin_api.api_resources.groups.enums import GroupPatchPath, ListGroupsOrderBy from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestGroupsResource: @@ -51,6 +52,7 @@ def test_delete_group(self, m_request, base_absolut_url): ( {}, { + "orderBy": None, "parentId": None, "limit": 25, "offset": 0, @@ -58,11 +60,17 @@ def test_delete_group(self, m_request, base_absolut_url): ), ( { + "orderBy": Sorting( + [SortingRule(ListGroupsOrderBy.NAME, SortingOrder.DESC)] + ), "parentId": "test", "limit": 10, "offset": 2, }, { + "orderBy": Sorting( + [SortingRule(ListGroupsOrderBy.NAME, SortingOrder.DESC)] + ), "parentId": "test", "limit": 10, "offset": 2, diff --git a/crowdin_api/api_resources/labels/enums.py b/crowdin_api/api_resources/labels/enums.py index f9c5369..cf64d2e 100644 --- a/crowdin_api/api_resources/labels/enums.py +++ b/crowdin_api/api_resources/labels/enums.py @@ -3,3 +3,8 @@ class LabelsPatchPath(Enum): TITLE = "/title" + + +class ListLabelsOrderBy(Enum): + ID = "id" + TITLE = "title" diff --git a/crowdin_api/api_resources/labels/resource.py b/crowdin_api/api_resources/labels/resource.py index 5f4c3ac..2caef91 100644 --- a/crowdin_api/api_resources/labels/resource.py +++ b/crowdin_api/api_resources/labels/resource.py @@ -2,6 +2,7 @@ from crowdin_api.api_resources.abstract.resources import BaseResource from crowdin_api.api_resources.labels.types import LabelsPatchRequest +from crowdin_api.sorting import Sorting class LabelsResource(BaseResource): @@ -21,6 +22,7 @@ def get_labels_path(self, projectId: int, labelId: Optional[int] = None): def list_labels( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, page: Optional[int] = None, offset: Optional[int] = None, limit: Optional[int] = None, @@ -33,11 +35,13 @@ def list_labels( """ projectId = projectId or self.get_project_id() + params = {"orderBy": orderBy} + params.update(self.get_page_params(page=page, offset=offset, limit=limit)) return self._get_entire_data( method="get", path=self.get_labels_path(projectId=projectId), - params=self.get_page_params(page=page, offset=offset, limit=limit), + params=params, ) def add_label(self, title: str, projectId: Optional[int] = None): diff --git a/crowdin_api/api_resources/labels/tests/test_labels_resources.py b/crowdin_api/api_resources/labels/tests/test_labels_resources.py index 846c0b2..035dfd3 100644 --- a/crowdin_api/api_resources/labels/tests/test_labels_resources.py +++ b/crowdin_api/api_resources/labels/tests/test_labels_resources.py @@ -2,9 +2,10 @@ import pytest from crowdin_api.api_resources.enums import PatchOperation -from crowdin_api.api_resources.labels.enums import LabelsPatchPath +from crowdin_api.api_resources.labels.enums import LabelsPatchPath, ListLabelsOrderBy from crowdin_api.api_resources.labels.resource import LabelsResource from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestLabelsResource: @@ -31,15 +32,46 @@ def test_get_labels_path(self, in_params, path, base_absolut_url): resource = self.get_resource(base_absolut_url) assert resource.get_labels_path(**in_params) == path + @pytest.mark.parametrize( + "incoming_data, request_params", + ( + ( + {}, + { + "orderBy": None, + "limit": 25, + "offset": 0, + }, + ), + ( + { + "orderBy": Sorting( + [SortingRule(ListLabelsOrderBy.ID, SortingOrder.DESC)] + ), + "limit": 25, + "offset": 0, + }, + { + "orderBy": Sorting( + [SortingRule(ListLabelsOrderBy.ID, SortingOrder.DESC)] + ), + "limit": 25, + "offset": 0, + }, + ), + ), + ) @mock.patch("crowdin_api.requester.APIRequester.request") - def test_list_translation_approvals(self, m_request, base_absolut_url): + def test_list_translation_approvals( + self, m_request, incoming_data, request_params, base_absolut_url + ): m_request.return_value = "response" resource = self.get_resource(base_absolut_url) - assert resource.list_labels(projectId=1) == "response" + assert resource.list_labels(projectId=1, **incoming_data) == "response" m_request.assert_called_once_with( method="get", - params={"limit": 25, "offset": 0}, + params=request_params, path=resource.get_labels_path(projectId=1), ) diff --git a/crowdin_api/api_resources/projects/enums.py b/crowdin_api/api_resources/projects/enums.py index 4b2f9cb..ebaf414 100644 --- a/crowdin_api/api_resources/projects/enums.py +++ b/crowdin_api/api_resources/projects/enums.py @@ -93,3 +93,13 @@ class EscapeSpecialCharacters(Enum): class ProjectFilePatchPath(Enum): FORMAT = "/format" SETTINGS = "/settings" + + +class ListProjectsOrderBy(Enum): + ID = "id" + NAME = "name" + IDENTIFIER = "identifier" + DESCRIPTION = "description" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" + LAST_ACTIVITY = "lastActivity" diff --git a/crowdin_api/api_resources/projects/resource.py b/crowdin_api/api_resources/projects/resource.py index 9238b17..d512ea6 100644 --- a/crowdin_api/api_resources/projects/resource.py +++ b/crowdin_api/api_resources/projects/resource.py @@ -25,6 +25,7 @@ XliffStringsExporterSettings, QAChecksIgnorableCategories ) +from crowdin_api.sorting import Sorting class ProjectsResource(BaseResource): @@ -47,6 +48,7 @@ def get_projects_path(self, projectId: Optional[int] = None): def list_projects( self, + orderBy: Optional[Sorting] = None, page: Optional[int] = None, offset: Optional[int] = None, limit: Optional[int] = None, @@ -61,7 +63,7 @@ def list_projects( https://developer.crowdin.com/api/v2/#operation/api.projects.getMany """ - params = {"userId": userId, "hasManagerAccess": hasManagerAccess, "groupId": groupId} + params = {"orderBy": orderBy, "userId": userId, "hasManagerAccess": hasManagerAccess, "groupId": groupId} params.update(self.get_page_params(page=page, offset=offset, limit=limit)) return self._get_entire_data(method="get", path=self.get_projects_path(), params=params) diff --git a/crowdin_api/api_resources/projects/tests/test_projects_resources.py b/crowdin_api/api_resources/projects/tests/test_projects_resources.py index 8d66d1c..d9509d4 100644 --- a/crowdin_api/api_resources/projects/tests/test_projects_resources.py +++ b/crowdin_api/api_resources/projects/tests/test_projects_resources.py @@ -5,6 +5,7 @@ from crowdin_api.api_resources.enums import PatchOperation from crowdin_api.api_resources.projects.enums import ( HasManagerAccess, + ListProjectsOrderBy, ProjectLanguageAccessPolicy, ProjectPatchPath, ProjectTranslateDuplicates, @@ -18,6 +19,7 @@ QAChecksIgnorableCategories ) from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestProjectsResource: @@ -50,6 +52,9 @@ def test_get_projects_path(self, projectId, path, base_absolut_url): ( ( { + "orderBy": Sorting( + [SortingRule(ListProjectsOrderBy.ID, SortingOrder.DESC)] + ), "offset": 0, "limit": 10, "userId": 1, @@ -57,6 +62,9 @@ def test_get_projects_path(self, projectId, path, base_absolut_url): "hasManagerAccess": HasManagerAccess.TRUE, }, { + "orderBy": Sorting( + [SortingRule(ListProjectsOrderBy.ID, SortingOrder.DESC)] + ), "offset": 0, "limit": 10, "userId": 1, @@ -67,6 +75,7 @@ def test_get_projects_path(self, projectId, path, base_absolut_url): ( {"offset": 0, "limit": 10}, { + "orderBy": None, "offset": 0, "limit": 10, "userId": None, diff --git a/crowdin_api/api_resources/screenshots/enums.py b/crowdin_api/api_resources/screenshots/enums.py index d428628..27a3a22 100644 --- a/crowdin_api/api_resources/screenshots/enums.py +++ b/crowdin_api/api_resources/screenshots/enums.py @@ -8,3 +8,11 @@ class ScreenshotPatchPath(Enum): class TagPatchPath(Enum): STRING_ID = "/stringId" POSITION = "/position" + + +class ListScreenshotsOrderBy(Enum): + ID = "id" + NAME = "name" + TAGS_COUNT = "tagsCount" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" diff --git a/crowdin_api/api_resources/screenshots/resource.py b/crowdin_api/api_resources/screenshots/resource.py index 62dcb70..0685378 100644 --- a/crowdin_api/api_resources/screenshots/resource.py +++ b/crowdin_api/api_resources/screenshots/resource.py @@ -7,6 +7,7 @@ ScreenshotPatchRequest, TagPatchRequest, ) +from crowdin_api.sorting import Sorting class ScreenshotsResource(BaseResource): @@ -31,7 +32,7 @@ def get_screenshots_path(self, projectId: int, screenshotId: Optional[int] = Non def list_screenshots( self, - orderBy: Optional[str] = None, + orderBy: Optional[Sorting] = None, projectId: Optional[int] = None, stringId: Optional[int] = None, stringIds: Optional[Iterable[int]] = None, diff --git a/crowdin_api/api_resources/screenshots/tests/test_screenshots_resources.py b/crowdin_api/api_resources/screenshots/tests/test_screenshots_resources.py index 3202f03..962aae6 100644 --- a/crowdin_api/api_resources/screenshots/tests/test_screenshots_resources.py +++ b/crowdin_api/api_resources/screenshots/tests/test_screenshots_resources.py @@ -2,9 +2,10 @@ import pytest from crowdin_api.api_resources.enums import PatchOperation -from crowdin_api.api_resources.screenshots.enums import ScreenshotPatchPath, TagPatchPath +from crowdin_api.api_resources.screenshots.enums import ListScreenshotsOrderBy, ScreenshotPatchPath, TagPatchPath from crowdin_api.api_resources.screenshots.resource import ScreenshotsResource from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestSourceFilesResource: @@ -32,25 +33,58 @@ def test_get_screenshots_path(self, in_params, path, base_absolut_url): resource = self.get_resource(base_absolut_url) assert resource.get_screenshots_path(**in_params) == path + @pytest.mark.parametrize( + "incoming_data, request_params", + ( + ( + {}, + { + "orderBy": None, + "stringIds": None, + "labelIds": None, + "excludeLabelIds": None, + "offset": 0, + "limit": 25, + }, + ), + ( + { + "orderBy": Sorting( + [SortingRule(ListScreenshotsOrderBy.ID, SortingOrder.DESC)] + ), + "stringIds": [1, 2, 3], + "labelIds": [4, 5, 6], + "excludeLabelIds": [7, 8, 9], + "limit": 10, + "offset": 0, + }, + { + "orderBy": Sorting( + [SortingRule(ListScreenshotsOrderBy.ID, SortingOrder.DESC)] + ), + "stringIds": [1, 2, 3], + "labelIds": [4, 5, 6], + "excludeLabelIds": [7, 8, 9], + "limit": 10, + "offset": 0, + }, + ), + ), + ) @mock.patch("crowdin_api.requester.APIRequester.request") - def test_list_screenshots(self, m_request, base_absolut_url): + def test_list_screenshots( + self, m_request, incoming_data, request_params, base_absolut_url + ): m_request.return_value = "response" resource = self.get_resource(base_absolut_url) assert ( - resource.list_screenshots(projectId=1, **{"offset": 0, "limit": 10}) + resource.list_screenshots(projectId=1, **incoming_data) == "response" ) m_request.assert_called_once_with( method="get", path=resource.get_screenshots_path(projectId=1), - params={ - "orderBy": None, - "stringIds": None, - "labelIds": None, - "excludeLabelIds": None, - "offset": 0, - "limit": 10, - }, + params=request_params, ) @pytest.mark.parametrize( diff --git a/crowdin_api/api_resources/source_files/enums.py b/crowdin_api/api_resources/source_files/enums.py index 5bbafbc..7dcfa2d 100644 --- a/crowdin_api/api_resources/source_files/enums.py +++ b/crowdin_api/api_resources/source_files/enums.py @@ -116,3 +116,34 @@ class FilePatchPath(Enum): EXCLUDED_TARGET_LANGUAGES = "/excludedTargetLanguages" ATTACH_LABEL_IDS = "/attachLabelIds" DETACH_LABEL_IDS = "/detachLabelIds" + + +class ListProjectBranchesOrderBy(Enum): + ID = "id" + NAME = "name" + TITLE = "title" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" + EXPORT_PATTERN = "exportPattern" + PRIORITY = "priority" + + +class ListDirectoriesOrderBy(Enum): + ID = "id" + NAME = "name" + TITLE = "title" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" + EXPORT_PATTERN = "exportPattern" + PRIORITY = "priority" + + +class ListFilesOrderBy(Enum): + ID = "id" + NAME = "name" + TITLE = "title" + STATUS = "status" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" + EXPORT_PATTERN = "exportPattern" + PRIORITY = "priority" diff --git a/crowdin_api/api_resources/source_files/resource.py b/crowdin_api/api_resources/source_files/resource.py index 2e23e77..4b26c47 100644 --- a/crowdin_api/api_resources/source_files/resource.py +++ b/crowdin_api/api_resources/source_files/resource.py @@ -22,6 +22,7 @@ XmlImportOptions, DocxFileImportOptions, ) +from crowdin_api.sorting import Sorting class SourceFilesResource(BaseResource): @@ -51,6 +52,7 @@ def get_branch_path(self, projectId: int, branchId: Optional[int] = None): def list_project_branches( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, name: Optional[str] = None, page: Optional[int] = None, offset: Optional[int] = None, @@ -64,7 +66,7 @@ def list_project_branches( """ projectId = projectId or self.get_project_id() - params = {"name": name} + params = {"orderBy": orderBy, "name": name} params.update(self.get_page_params(page=page, offset=offset, limit=limit)) return self._get_entire_data( @@ -160,6 +162,7 @@ def get_directory_path(self, projectId: int, directoryId: Optional[int] = None): def list_directories( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, branchId: Optional[int] = None, directoryId: Optional[int] = None, filter: Optional[str] = None, @@ -177,6 +180,7 @@ def list_directories( projectId = projectId or self.get_project_id() params = { + "orderBy": orderBy, "branchId": branchId, "directoryId": directoryId, "filter": filter, @@ -283,6 +287,7 @@ def get_file_path(self, projectId: int, fileId: Optional[int] = None): def list_files( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, branchId: Optional[int] = None, directoryId: Optional[int] = None, filter: Optional[str] = None, @@ -300,6 +305,7 @@ def list_files( projectId = projectId or self.get_project_id() params = { + "orderBy": orderBy, "branchId": branchId, "directoryId": directoryId, "filter": filter, diff --git a/crowdin_api/api_resources/source_files/tests/test_source_files_resources.py b/crowdin_api/api_resources/source_files/tests/test_source_files_resources.py index 36f19e6..d5d7b38 100644 --- a/crowdin_api/api_resources/source_files/tests/test_source_files_resources.py +++ b/crowdin_api/api_resources/source_files/tests/test_source_files_resources.py @@ -7,10 +7,14 @@ DirectoryPatchPath, FilePatchPath, FileType, + ListDirectoriesOrderBy, + ListFilesOrderBy, + ListProjectBranchesOrderBy, Priority, ) from crowdin_api.api_resources.source_files.resource import SourceFilesResource from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestSourceFilesResource: @@ -43,11 +47,25 @@ def test_get_branch_path(self, in_params, path, base_absolut_url): ( ( {"offset": 0, "limit": 10}, - {"offset": 0, "limit": 10, "name": None}, + {"orderBy": None, "offset": 0, "limit": 10, "name": None}, ), ( - {"offset": 0, "limit": 10, "name": "test"}, - {"offset": 0, "limit": 10, "name": "test"}, + { + "orderBy": Sorting( + [SortingRule(ListProjectBranchesOrderBy.ID, SortingOrder.DESC)] + ), + "offset": 0, + "limit": 10, + "name": "test", + }, + { + "orderBy": Sorting( + [SortingRule(ListProjectBranchesOrderBy.ID, SortingOrder.DESC)] + ), + "offset": 0, + "limit": 10, + "name": "test", + }, ), ), ) @@ -167,6 +185,7 @@ def test_get_directory_path(self, in_params, path, base_absolut_url): "limit": 10, }, { + "orderBy": None, "offset": 0, "limit": 10, "branchId": None, @@ -177,6 +196,9 @@ def test_get_directory_path(self, in_params, path, base_absolut_url): ), ( { + "orderBy": Sorting( + [SortingRule(ListDirectoriesOrderBy.ID, SortingOrder.DESC)] + ), "offset": 0, "limit": 10, "branchId": 1, @@ -185,6 +207,9 @@ def test_get_directory_path(self, in_params, path, base_absolut_url): "recursion": "recursion", }, { + "orderBy": Sorting( + [SortingRule(ListDirectoriesOrderBy.ID, SortingOrder.DESC)] + ), "offset": 0, "limit": 10, "branchId": 1, @@ -314,6 +339,7 @@ def test_get_file_path(self, in_params, path, base_absolut_url): ( {"offset": 0, "limit": 10}, { + "orderBy": None, "offset": 0, "limit": 10, "branchId": None, @@ -324,6 +350,9 @@ def test_get_file_path(self, in_params, path, base_absolut_url): ), ( { + "orderBy": Sorting( + [SortingRule(ListFilesOrderBy.ID, SortingOrder.DESC)] + ), "offset": 0, "limit": 10, "branchId": 1, @@ -332,6 +361,9 @@ def test_get_file_path(self, in_params, path, base_absolut_url): "recursion": "recursion", }, { + "orderBy": Sorting( + [SortingRule(ListFilesOrderBy.ID, SortingOrder.DESC)] + ), "offset": 0, "limit": 10, "branchId": 1, diff --git a/crowdin_api/api_resources/source_strings/enums.py b/crowdin_api/api_resources/source_strings/enums.py index fbe0081..a4f851f 100644 --- a/crowdin_api/api_resources/source_strings/enums.py +++ b/crowdin_api/api_resources/source_strings/enums.py @@ -28,3 +28,13 @@ class StringBatchOperationsPath(Enum): IS_HIDDEN = "/{stringId}/isHidden" MAX_LENGTH = "/{stringId}/maxLength" LABEL_IDS = "/{stringId}/labelIds" + + +class ListStringsOrderBy(Enum): + ID = "id" + TEXT = "text" + IDENTIFIER = "identifier" + CONTEXT = "context" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" + TYPE = "type" diff --git a/crowdin_api/api_resources/source_strings/resource.py b/crowdin_api/api_resources/source_strings/resource.py index 4222513..c624f9f 100644 --- a/crowdin_api/api_resources/source_strings/resource.py +++ b/crowdin_api/api_resources/source_strings/resource.py @@ -7,6 +7,7 @@ SourceStringsPatchRequest, StringBatchOperationPatchRequest, ) +from crowdin_api.sorting import Sorting class SourceStringsResource(BaseResource): @@ -33,6 +34,7 @@ def get_source_strings_path(self, projectId: int, stringId: Optional[int] = None def list_strings( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, fileId: Optional[int] = None, branchId: Optional[int] = None, denormalizePlaceholders: Optional[DenormalizePlaceholders] = None, @@ -54,6 +56,7 @@ def list_strings( projectId = projectId or self.get_project_id() params = { + "orderBy": orderBy, "branchId": branchId, "fileId": fileId, "denormalizePlaceholders": denormalizePlaceholders, diff --git a/crowdin_api/api_resources/source_strings/tests/test_source_strings_resources.py b/crowdin_api/api_resources/source_strings/tests/test_source_strings_resources.py index 247d96e..6fcb768 100644 --- a/crowdin_api/api_resources/source_strings/tests/test_source_strings_resources.py +++ b/crowdin_api/api_resources/source_strings/tests/test_source_strings_resources.py @@ -3,6 +3,7 @@ import pytest from crowdin_api.api_resources.enums import DenormalizePlaceholders, PatchOperation from crowdin_api.api_resources.source_strings.enums import ( + ListStringsOrderBy, ScopeFilter, SourceStringsPatchPath, StringBatchOperationsPath, @@ -10,6 +11,7 @@ ) from crowdin_api.api_resources.source_strings.resource import SourceStringsResource from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestSourceFilesResource: @@ -42,6 +44,7 @@ def test_get_source_strings_path(self, in_params, path, base_absolut_url): ( {"offset": 0, "limit": 10}, { + "orderBy": None, "offset": 0, "limit": 10, "fileId": None, @@ -56,6 +59,9 @@ def test_get_source_strings_path(self, in_params, path, base_absolut_url): ), ( { + "orderBy": Sorting( + [SortingRule(ListStringsOrderBy.ID, SortingOrder.DESC)] + ), "offset": 0, "limit": 10, "fileId": 1, @@ -68,6 +74,9 @@ def test_get_source_strings_path(self, in_params, path, base_absolut_url): "taskId": 5, }, { + "orderBy": Sorting( + [SortingRule(ListStringsOrderBy.ID, SortingOrder.DESC)] + ), "offset": 0, "limit": 10, "fileId": 1, diff --git a/crowdin_api/api_resources/string_comments/enums.py b/crowdin_api/api_resources/string_comments/enums.py index da997e5..f728aeb 100644 --- a/crowdin_api/api_resources/string_comments/enums.py +++ b/crowdin_api/api_resources/string_comments/enums.py @@ -21,3 +21,13 @@ class StringCommentIssueStatus(Enum): class StringCommentPatchPath(Enum): TEXT = "/text" ISSUE_STATUS = "/issueStatus" + + +class ListStringCommentsOrderBy(Enum): + ID = "id" + TEXT = "text" + TYPE = "type" + CREATED_AT = "createdAt" + RESOLVED_AT = "resolvedAt" + ISSUE_STATUS = "issueStatus" + ISSUE_TYPE = "issueType" diff --git a/crowdin_api/api_resources/string_comments/resource.py b/crowdin_api/api_resources/string_comments/resource.py index feeebb1..fc36abf 100644 --- a/crowdin_api/api_resources/string_comments/resource.py +++ b/crowdin_api/api_resources/string_comments/resource.py @@ -7,6 +7,7 @@ StringCommentType, ) from crowdin_api.api_resources.string_comments.types import StringCommentPatchRequest +from crowdin_api.sorting import Sorting class StringCommentsResource(BaseResource): @@ -28,6 +29,7 @@ def get_string_comments_path(self, projectId: int, stringCommentId: Optional[int def list_string_comments( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, stringId: Optional[int] = None, type: Optional[StringCommentType] = None, issueType: Optional[Iterable[StringCommentIssueType]] = None, @@ -45,6 +47,7 @@ def list_string_comments( projectId = projectId or self.get_project_id() params = { + "orderBy": orderBy, "stringId": stringId, "type": type, "issueType": None if issueType is None else ",".join(item.value for item in issueType), diff --git a/crowdin_api/api_resources/string_comments/tests/test_string_comments_resources.py b/crowdin_api/api_resources/string_comments/tests/test_string_comments_resources.py index f480de2..b821362 100644 --- a/crowdin_api/api_resources/string_comments/tests/test_string_comments_resources.py +++ b/crowdin_api/api_resources/string_comments/tests/test_string_comments_resources.py @@ -3,6 +3,7 @@ import pytest from crowdin_api.api_resources.enums import PatchOperation from crowdin_api.api_resources.string_comments.enums import ( + ListStringCommentsOrderBy, StringCommentIssueStatus, StringCommentIssueType, StringCommentPatchPath, @@ -10,6 +11,7 @@ ) from crowdin_api.api_resources.string_comments.resource import StringCommentsResource from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestSourceFilesResource: @@ -42,6 +44,7 @@ def test_get_string_comments_path(self, in_params, path, base_absolut_url): ( {"offset": 0, "limit": 10}, { + "orderBy": None, "offset": 0, "limit": 10, "stringId": None, @@ -52,6 +55,9 @@ def test_get_string_comments_path(self, in_params, path, base_absolut_url): ), ( { + "orderBy": Sorting( + [SortingRule(ListStringCommentsOrderBy.ID, SortingOrder.DESC)] + ), "offset": 0, "limit": 10, "stringId": 1, @@ -63,6 +69,9 @@ def test_get_string_comments_path(self, in_params, path, base_absolut_url): "issueStatus": StringCommentIssueStatus.UNRESOLVED, }, { + "orderBy": Sorting( + [SortingRule(ListStringCommentsOrderBy.ID, SortingOrder.DESC)] + ), "offset": 0, "limit": 10, "stringId": 1, diff --git a/crowdin_api/api_resources/string_translations/enums.py b/crowdin_api/api_resources/string_translations/enums.py index 13a2d34..2e63e0c 100644 --- a/crowdin_api/api_resources/string_translations/enums.py +++ b/crowdin_api/api_resources/string_translations/enums.py @@ -9,3 +9,22 @@ class PreTranslationApplyMethod(Enum): class VoteMark(Enum): UP = "up" DOWN = "down" + + +class ListTranslationApprovalsOrderBy(Enum): + ID = "id" + CREATED_AT = "createdAt" + + +class ListLanguageTranslationsOrderBy(Enum): + TEXT = "text" + STRING_ID = "stringId" + TRANSLATION_ID = "translationId" + CREATED_AT = "createdAt" + + +class ListStringTranslationsOrderBy(Enum): + ID = "id" + TEXT = "text" + RATING = "rating" + CREATED_AT = "createdAt" diff --git a/crowdin_api/api_resources/string_translations/resource.py b/crowdin_api/api_resources/string_translations/resource.py index ccc3f99..0a31dc5 100644 --- a/crowdin_api/api_resources/string_translations/resource.py +++ b/crowdin_api/api_resources/string_translations/resource.py @@ -3,6 +3,7 @@ from crowdin_api.api_resources.abstract.resources import BaseResource from crowdin_api.api_resources.enums import DenormalizePlaceholders, PluralCategoryName from crowdin_api.api_resources.string_translations.enums import VoteMark +from crowdin_api.sorting import Sorting class StringTranslationsResource(BaseResource): @@ -25,6 +26,7 @@ def get_approvals_path(self, projectId: int, approvalId: Optional[int] = None): def list_translation_approvals( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, fileId: Optional[int] = None, labelIds: Optional[str] = None, excludeLabelIds: Optional[str] = None, @@ -44,6 +46,7 @@ def list_translation_approvals( projectId = projectId or self.get_project_id() params = { + "orderBy": orderBy, "fileId": fileId, "labelIds": labelIds, "excludeLabelIds": excludeLabelIds, @@ -130,6 +133,7 @@ def list_language_translations( self, languageId: str, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, stringIds: Optional[Iterable[int]] = None, labelIds: Optional[Iterable[int]] = None, fileId: Optional[int] = None, @@ -150,6 +154,7 @@ def list_language_translations( projectId = projectId or self.get_project_id() params = { + "orderBy": orderBy, "stringIds": None if stringIds is None else ",".join(str(stringId) for stringId in stringIds), @@ -207,6 +212,7 @@ def get_translations_path(self, projectId: int, translationId: Optional[int] = N def list_string_translations( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, stringId: Optional[int] = None, languageId: Optional[str] = None, denormalizePlaceholders: Optional[DenormalizePlaceholders] = None, @@ -223,6 +229,7 @@ def list_string_translations( projectId = projectId or self.get_project_id() params = { + "orderBy": orderBy, "stringId": stringId, "languageId": languageId, "denormalizePlaceholders": denormalizePlaceholders, diff --git a/crowdin_api/api_resources/string_translations/tests/test_string_translations_resources.py b/crowdin_api/api_resources/string_translations/tests/test_string_translations_resources.py index 309a02c..35872b2 100644 --- a/crowdin_api/api_resources/string_translations/tests/test_string_translations_resources.py +++ b/crowdin_api/api_resources/string_translations/tests/test_string_translations_resources.py @@ -2,9 +2,15 @@ import pytest from crowdin_api.api_resources.enums import DenormalizePlaceholders -from crowdin_api.api_resources.string_translations.enums import VoteMark +from crowdin_api.api_resources.string_translations.enums import ( + ListLanguageTranslationsOrderBy, + ListStringTranslationsOrderBy, + ListTranslationApprovalsOrderBy, + VoteMark, +) from crowdin_api.api_resources.string_translations.resource import StringTranslationsResource from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestStringTranslationsResource: @@ -38,6 +44,7 @@ def test_get_approvals_path(self, in_params, path, base_absolut_url): ( {"offset": 0, "limit": 10}, { + "orderBy": None, "offset": 0, "limit": 10, "fileId": None, @@ -50,6 +57,13 @@ def test_get_approvals_path(self, in_params, path, base_absolut_url): ), ( { + "orderBy": Sorting( + [ + SortingRule( + ListTranslationApprovalsOrderBy.ID, SortingOrder.DESC + ) + ] + ), "offset": 0, "limit": 10, "fileId": 1, @@ -60,6 +74,13 @@ def test_get_approvals_path(self, in_params, path, base_absolut_url): "translationId": 3, }, { + "orderBy": Sorting( + [ + SortingRule( + ListTranslationApprovalsOrderBy.ID, SortingOrder.DESC + ) + ] + ), "offset": 0, "limit": 10, "fileId": 1, @@ -142,6 +163,7 @@ def test_remove_approval(self, m_request, base_absolut_url): ( {"offset": 0, "limit": 10}, { + "orderBy": None, "offset": 0, "limit": 10, "stringIds": None, @@ -155,6 +177,14 @@ def test_remove_approval(self, m_request, base_absolut_url): ), ( { + "orderBy": Sorting( + [ + SortingRule( + ListLanguageTranslationsOrderBy.TRANSLATION_ID, + SortingOrder.DESC, + ) + ] + ), "offset": 0, "limit": 10, "stringIds": [1, 2, 3], @@ -166,6 +196,14 @@ def test_remove_approval(self, m_request, base_absolut_url): "denormalizePlaceholders": DenormalizePlaceholders.ENABLE, }, { + "orderBy": Sorting( + [ + SortingRule( + ListLanguageTranslationsOrderBy.TRANSLATION_ID, + SortingOrder.DESC, + ) + ] + ), "offset": 0, "limit": 10, "stringIds": "1,2,3", @@ -233,6 +271,7 @@ def test_get_translations_path(self, in_params, path, base_absolut_url): ( {"offset": 0, "limit": 10}, { + "orderBy": None, "offset": 0, "limit": 10, "stringId": None, @@ -242,6 +281,14 @@ def test_get_translations_path(self, in_params, path, base_absolut_url): ), ( { + "orderBy": Sorting( + [ + SortingRule( + ListStringTranslationsOrderBy.ID, + SortingOrder.DESC, + ) + ] + ), "offset": 0, "limit": 10, "stringId": 1, @@ -249,6 +296,14 @@ def test_get_translations_path(self, in_params, path, base_absolut_url): "denormalizePlaceholders": DenormalizePlaceholders.ENABLE, }, { + "orderBy": Sorting( + [ + SortingRule( + ListStringTranslationsOrderBy.ID, + SortingOrder.DESC, + ) + ] + ), "offset": 0, "limit": 10, "stringId": 1, diff --git a/crowdin_api/api_resources/tasks/enums.py b/crowdin_api/api_resources/tasks/enums.py index 7b9070f..1ba8c25 100644 --- a/crowdin_api/api_resources/tasks/enums.py +++ b/crowdin_api/api_resources/tasks/enums.py @@ -173,3 +173,27 @@ class ManualCrowdinVendors(Enum): GTE_LOCALIZE = "gte_localize" KETTU_SOLUTIONS = "kettu_solutions" LANGUAGELINE_TRANSLATION_SOLUTIONS = "languageline_solutions" + + +class ListTasksOrderBy(Enum): + ID = "id" + TYPE = "type" + TITLE = "title" + STATUS = "status" + DESCRIPTION = "description" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" + DEADLINE = "deadline" + STARTED_AT = "startedAt" + RESOLVED_AT = "resolvedAt" + + +class ListUserTasksOrderBy(Enum): + ID = "id" + TITLE = "title" + DESCRIPTION = "description" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" + DEADLINE = "deadline" + STARTED_AT = "startedAt" + RESOLVED_AT = "resolvedAt" diff --git a/crowdin_api/api_resources/tasks/resource.py b/crowdin_api/api_resources/tasks/resource.py index 677e437..589b3bb 100644 --- a/crowdin_api/api_resources/tasks/resource.py +++ b/crowdin_api/api_resources/tasks/resource.py @@ -26,6 +26,7 @@ EnterpriseTaskSettingsTemplateLanguages, TaskSettingsTemplateLanguages, ) +from crowdin_api.sorting import Sorting class TasksResource(BaseResource): @@ -177,6 +178,7 @@ def get_tasks_path(self, projectId: int, taskId: Optional[int] = None): def list_tasks( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, assigneeId: Optional[int] = None, status: Optional[CrowdinTaskStatus] = None, page: Optional[int] = None, @@ -191,7 +193,7 @@ def list_tasks( """ projectId = projectId or self.get_project_id() - params = {"assigneeId": assigneeId, "status": status} + params = {"orderBy": orderBy, "assigneeId": assigneeId, "status": status} params.update(self.get_page_params(page=page, offset=offset, limit=limit)) return self._get_entire_data( @@ -909,6 +911,7 @@ def edit_task( def list_user_tasks( self, + orderBy: Optional[Sorting] = None, status: Optional[CrowdinTaskStatus] = None, isArchived: Optional[bool] = None, page: Optional[int] = None, @@ -922,7 +925,7 @@ def list_user_tasks( https://developer.crowdin.com/api/v2/#operation/api.projects.tasks.getMany """ - params = {"status": status} + params = {"orderBy": orderBy, "status": status} if isArchived is not None: params["isArchived"] = 1 if isArchived else 0 diff --git a/crowdin_api/api_resources/tasks/tests/test_tasks_resources.py b/crowdin_api/api_resources/tasks/tests/test_tasks_resources.py index eddcb3c..68a181e 100644 --- a/crowdin_api/api_resources/tasks/tests/test_tasks_resources.py +++ b/crowdin_api/api_resources/tasks/tests/test_tasks_resources.py @@ -10,6 +10,8 @@ GengoCrowdinTaskExpertise, GengoCrowdinTaskPurpose, GengoCrowdinTaskTone, + ListTasksOrderBy, + ListUserTasksOrderBy, OhtCrowdinTaskExpertise, OhtCrowdinTaskType, TaskOperationPatchPath, @@ -22,6 +24,7 @@ ) from crowdin_api.api_resources.tasks.resource import TasksResource, EnterpriseTasksResource from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestTasksResource: @@ -158,19 +161,56 @@ def test_get_tasks_path(self, incoming_data, path, base_absolut_url): @pytest.mark.parametrize( "incoming_data, request_params", ( - ({}, {"assigneeId": None, "status": None, "offset": 0, "limit": 25}), ( - {"assigneeId": 1}, - {"assigneeId": 1, "status": None, "offset": 0, "limit": 25}, + {}, + { + "orderBy": None, + "assigneeId": None, + "status": None, + "offset": 0, + "limit": 25, + }, + ), + ( + { + "orderBy": Sorting( + [SortingRule(ListTasksOrderBy.ID, SortingOrder.DESC)] + ), + "assigneeId": 1, + }, + { + "orderBy": Sorting( + [SortingRule(ListTasksOrderBy.ID, SortingOrder.DESC)] + ), + "assigneeId": 1, + "status": None, + "offset": 0, + "limit": 25, + }, ), ( - {"status": CrowdinTaskStatus.DONE}, - {"assigneeId": None, "status": CrowdinTaskStatus.DONE, "offset": 0, "limit": 25}, + { + "orderBy": Sorting( + [SortingRule(ListTasksOrderBy.ID, SortingOrder.DESC)] + ), + "status": CrowdinTaskStatus.DONE, + }, + { + "orderBy": Sorting( + [SortingRule(ListTasksOrderBy.ID, SortingOrder.DESC)] + ), + "assigneeId": None, + "status": CrowdinTaskStatus.DONE, + "offset": 0, + "limit": 25, + }, ), ), ) @mock.patch("crowdin_api.requester.APIRequester.request") - def test_list_tasks(self, m_request, incoming_data, request_params, base_absolut_url): + def test_list_tasks( + self, m_request, incoming_data, request_params, base_absolut_url + ): m_request.return_value = "response" resource = self.get_resource(base_absolut_url) @@ -1226,10 +1266,29 @@ def test_edit_task(self, m_request, base_absolut_url): @pytest.mark.parametrize( "incoming_data, request_params", ( - ({}, {"status": None, "offset": 0, "limit": 25}), + ({}, {"orderBy": None, "status": None, "offset": 0, "limit": 25}), ( - {"status": CrowdinTaskStatus.TODO, "isArchived": False}, { + "orderBy": Sorting( + [ + SortingRule( + ListUserTasksOrderBy.ID, + SortingOrder.DESC, + ) + ] + ), + "status": CrowdinTaskStatus.TODO, + "isArchived": False, + }, + { + "orderBy": Sorting( + [ + SortingRule( + ListUserTasksOrderBy.ID, + SortingOrder.DESC, + ) + ] + ), "status": CrowdinTaskStatus.TODO, "isArchived": False, "offset": 0, @@ -1239,7 +1298,9 @@ def test_edit_task(self, m_request, base_absolut_url): ), ) @mock.patch("crowdin_api.requester.APIRequester.request") - def test_list_user_tasks(self, m_request, incoming_data, request_params, base_absolut_url): + def test_list_user_tasks( + self, m_request, incoming_data, request_params, base_absolut_url + ): m_request.return_value = "response" resource = self.get_resource(base_absolut_url) diff --git a/crowdin_api/api_resources/teams/enums.py b/crowdin_api/api_resources/teams/enums.py index 6f1e3d3..9597223 100644 --- a/crowdin_api/api_resources/teams/enums.py +++ b/crowdin_api/api_resources/teams/enums.py @@ -8,3 +8,10 @@ class TeamPatchPath(Enum): class TeamRole(Enum): TRANSLATOR = "translator" PROOFREADER = "proofreader" + + +class ListTeamsOrderBy(Enum): + ID = "id" + NAME = "name" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" diff --git a/crowdin_api/api_resources/teams/resource.py b/crowdin_api/api_resources/teams/resource.py index 6e7a777..ddc6e1c 100644 --- a/crowdin_api/api_resources/teams/resource.py +++ b/crowdin_api/api_resources/teams/resource.py @@ -2,6 +2,7 @@ from crowdin_api.api_resources.abstract.resources import BaseResource from crowdin_api.api_resources.teams.types import Permissions, TeamPatchRequest, TeamByProjectRole +from crowdin_api.sorting import Sorting class TeamsResource(BaseResource): @@ -58,7 +59,12 @@ def add_team_to_project( }, ) - def list_teams(self, offset: Optional[int] = None, limit: Optional[int] = None): + def list_teams( + self, + orderBy: Optional[Sorting] = None, + offset: Optional[int] = None, + limit: Optional[int] = None + ): """ List Teams. @@ -66,10 +72,13 @@ def list_teams(self, offset: Optional[int] = None, limit: Optional[int] = None): https://developer.crowdin.com/enterprise/api/v2/#operation/api.teams.getMany """ + params = {"orderBy": orderBy} + params.update(self.get_page_params(offset=offset, limit=limit)) + return self._get_entire_data( method="get", path=self.get_teams_path(), - params=self.get_page_params(offset=offset, limit=limit), + params=params, ) def add_team(self, name: str): diff --git a/crowdin_api/api_resources/teams/tests/test_teams_resources.py b/crowdin_api/api_resources/teams/tests/test_teams_resources.py index 66c58e6..216a517 100644 --- a/crowdin_api/api_resources/teams/tests/test_teams_resources.py +++ b/crowdin_api/api_resources/teams/tests/test_teams_resources.py @@ -4,8 +4,9 @@ from crowdin_api.api_resources import TeamsResource from crowdin_api.api_resources.enums import PatchOperation -from crowdin_api.api_resources.teams.enums import TeamPatchPath +from crowdin_api.api_resources.teams.enums import ListTeamsOrderBy, TeamPatchPath from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestTeamsResources: @@ -187,16 +188,23 @@ def test_add_team_to_project(self, m_request, incoming_data, request_data, base_ ( {}, { + "orderBy": None, "limit": 25, "offset": 0, }, ), ( { + "orderBy": Sorting( + [SortingRule(ListTeamsOrderBy.ID, SortingOrder.DESC)] + ), "limit": 10, "offset": 2, }, { + "orderBy": Sorting( + [SortingRule(ListTeamsOrderBy.ID, SortingOrder.DESC)] + ), "limit": 10, "offset": 2, }, diff --git a/crowdin_api/api_resources/translation_memory/enums.py b/crowdin_api/api_resources/translation_memory/enums.py index 063a7c5..f6fcfd1 100644 --- a/crowdin_api/api_resources/translation_memory/enums.py +++ b/crowdin_api/api_resources/translation_memory/enums.py @@ -15,3 +15,14 @@ class TranslationMemorySegmentRecordOperationPath(Enum): ADD = "/records/-" REPLACE = "/records/{recordId}/text" REMOVE = "/records/{recordId}" + + +class ListTmsOrderBy(Enum): + ID = "id" + NAME = "name" + USER_ID = "userId" + CREATED_AT = "createdAt" + + +class ListTmSegmentsOrderBy(Enum): + ID = "id" diff --git a/crowdin_api/api_resources/translation_memory/resource.py b/crowdin_api/api_resources/translation_memory/resource.py index 0e65f04..c47df49 100644 --- a/crowdin_api/api_resources/translation_memory/resource.py +++ b/crowdin_api/api_resources/translation_memory/resource.py @@ -9,6 +9,7 @@ TranslationMemorySegmentRecordOperationReplace, TranslationMemorySegmentRecordOperationRemove, ) +from crowdin_api.sorting import Sorting class TranslationMemoryResource(BaseResource): @@ -34,6 +35,7 @@ def get_tms_path(self, tmId: Optional[int] = None): def list_tms( self, + orderBy: Optional[Sorting] = None, page: Optional[int] = None, offset: Optional[int] = None, limit: Optional[int] = None, @@ -45,10 +47,13 @@ def list_tms( https://developer.crowdin.com/api/v2/#operation/api.tms.getMany """ + params = {"orderBy": orderBy} + params.update(self.get_page_params(page=page, offset=offset, limit=limit)) + return self._get_entire_data( method="get", path=self.get_tms_path(), - params=self.get_page_params(page=page, offset=offset, limit=limit), + params=params, ) def add_tm(self, name: str, languageId: str): @@ -118,6 +123,7 @@ def get_tm_segments_path(self, tmId: int, segmentId: Optional[int] = None): def list_tm_segments( self, tmId: int, + orderBy: Optional[Sorting] = None, page: Optional[int] = None, offset: Optional[int] = None, limit: Optional[int] = None, @@ -128,10 +134,13 @@ def list_tm_segments( Link to documentation: https://developer.crowdin.com/api/v2/#operation/api.tms.segments.getMany """ + params = {"orderBy": orderBy} + params.update(self.get_page_params(page=page, offset=offset, limit=limit)) + return self._get_entire_data( method="get", path=self.get_tm_segments_path(tmId=tmId), - params=self.get_page_params(page=page, offset=offset, limit=limit), + params=params, ) def create_tm_segment( diff --git a/crowdin_api/api_resources/translation_memory/tests/test_translation_memory_resources.py b/crowdin_api/api_resources/translation_memory/tests/test_translation_memory_resources.py index 73753e5..ec4037f 100644 --- a/crowdin_api/api_resources/translation_memory/tests/test_translation_memory_resources.py +++ b/crowdin_api/api_resources/translation_memory/tests/test_translation_memory_resources.py @@ -3,6 +3,8 @@ import pytest from crowdin_api.api_resources.enums import ExportFormat, PatchOperation from crowdin_api.api_resources.translation_memory.enums import ( + ListTmSegmentsOrderBy, + ListTmsOrderBy, TranslationMemoryPatchPath, TranslationMemorySegmentRecordOperation, TranslationMemorySegmentRecordOperationPath, @@ -11,6 +13,7 @@ TranslationMemoryResource, ) from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestTranslationMemoryResource: @@ -38,15 +41,46 @@ def test_get_screenshots_path(self, in_params, path, base_absolut_url): resource = self.get_resource(base_absolut_url) assert resource.get_tms_path(**in_params) == path + @pytest.mark.parametrize( + "incoming_data, request_params", + ( + ( + {}, + { + "orderBy": None, + "limit": 25, + "offset": 0, + }, + ), + ( + { + "orderBy": Sorting( + [SortingRule(ListTmsOrderBy.ID, SortingOrder.DESC)] + ), + "limit": 25, + "offset": 0, + }, + { + "orderBy": Sorting( + [SortingRule(ListTmsOrderBy.ID, SortingOrder.DESC)] + ), + "limit": 25, + "offset": 0, + }, + ), + ), + ) @mock.patch("crowdin_api.requester.APIRequester.request") - def test_list_tms(self, m_request, base_absolut_url): + def test_list_tms( + self, m_request, incoming_data, request_params, base_absolut_url + ): m_request.return_value = "response" resource = self.get_resource(base_absolut_url) - assert resource.list_tms() == "response" + assert resource.list_tms(**incoming_data) == "response" m_request.assert_called_once_with( method="get", - params={"offset": 0, "limit": 25}, + params=request_params, path=resource.get_tms_path(), ) @@ -125,16 +159,47 @@ def test_get_tm_segments_path(self, in_params, path, base_absolut_url): resource = self.get_resource(base_absolut_url) assert resource.get_tm_segments_path(**in_params) == path + @pytest.mark.parametrize( + "incoming_data, request_params", + ( + ( + {}, + { + "orderBy": None, + "limit": 25, + "offset": 0, + }, + ), + ( + { + "orderBy": Sorting( + [SortingRule(ListTmSegmentsOrderBy.ID, SortingOrder.DESC)] + ), + "limit": 25, + "offset": 0, + }, + { + "orderBy": Sorting( + [SortingRule(ListTmSegmentsOrderBy.ID, SortingOrder.DESC)] + ), + "limit": 25, + "offset": 0, + }, + ), + ), + ) @mock.patch("crowdin_api.requester.APIRequester.request") - def test_list_tm_segments(self, m_request, base_absolut_url): + def test_list_tm_segments( + self, m_request, incoming_data, request_params, base_absolut_url + ): m_request.return_value = "response" resource = self.get_resource(base_absolut_url) - assert resource.list_tm_segments(1) == "response" + assert resource.list_tm_segments(tmId=1, **incoming_data) == "response" m_request.assert_called_once_with( method="get", path=resource.get_tm_segments_path(1), - params={"offset": 0, "limit": 25}, + params=request_params, ) @mock.patch("crowdin_api.requester.APIRequester.request") diff --git a/crowdin_api/api_resources/users/enums.py b/crowdin_api/api_resources/users/enums.py index f7a751f..78db94b 100644 --- a/crowdin_api/api_resources/users/enums.py +++ b/crowdin_api/api_resources/users/enums.py @@ -19,3 +19,16 @@ class UserPatchPath(Enum): class ProjectRole(Enum): TRANSLATOR = "translator" PROOFREADER = "proofreader" + + +class ListProjectMembersCrowdinOrderBy(Enum): + ID = "id" + USERNAME = "username" + FULL_NAME = "fullName" + + +class ListProjectMembersEnterpriseOrderBy(Enum): + ID = "id" + USERNAME = "username" + FIRST_NAME = "firstName" + LAST_NAME = "lastName" diff --git a/crowdin_api/api_resources/users/resource.py b/crowdin_api/api_resources/users/resource.py index 5499d4b..1bb0da8 100644 --- a/crowdin_api/api_resources/users/resource.py +++ b/crowdin_api/api_resources/users/resource.py @@ -3,6 +3,7 @@ from crowdin_api.api_resources.abstract.resources import BaseResource from crowdin_api.api_resources.users.enums import UserRole from crowdin_api.api_resources.users.types import UserPatchRequest, ProjectMemberRole +from crowdin_api.sorting import Sorting class BaseUsersResource(BaseResource): @@ -19,6 +20,7 @@ def get_members_path(self, projectId: int, memberId: Optional[int] = None): def _list_project_members( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, search: Optional[str] = None, languageId: Optional[str] = None, page: Optional[int] = None, @@ -34,7 +36,7 @@ def _list_project_members( """ projectId = projectId or self.get_project_id() - params = {"search": search, "languageId": languageId} + params = {"orderBy": orderBy, "search": search, "languageId": languageId} if extraParams: params.update(extraParams) @@ -61,6 +63,7 @@ class UsersResource(BaseUsersResource): def list_project_members( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, search: Optional[str] = None, role: Optional[UserRole] = None, languageId: Optional[str] = None, @@ -76,6 +79,7 @@ def list_project_members( """ return self._list_project_members( projectId=projectId, + orderBy=orderBy, search=search, languageId=languageId, page=page, @@ -119,6 +123,7 @@ def get_users_path(self, userId: Optional[int] = None): def list_project_members( self, projectId: Optional[int] = None, + orderBy: Optional[Sorting] = None, search: Optional[str] = None, workflowStepId: Optional[int] = None, languageId: Optional[str] = None, @@ -135,6 +140,7 @@ def list_project_members( return self._list_project_members( projectId=projectId, + orderBy=orderBy, search=search, languageId=languageId, page=page, diff --git a/crowdin_api/api_resources/users/tests/test_users_resources.py b/crowdin_api/api_resources/users/tests/test_users_resources.py index c7f4f2e..8da1052 100644 --- a/crowdin_api/api_resources/users/tests/test_users_resources.py +++ b/crowdin_api/api_resources/users/tests/test_users_resources.py @@ -3,13 +3,19 @@ import pytest from crowdin_api.api_resources.enums import PatchOperation -from crowdin_api.api_resources.users.enums import UserRole, UserPatchPath +from crowdin_api.api_resources.users.enums import ( + ListProjectMembersCrowdinOrderBy, + ListProjectMembersEnterpriseOrderBy, + UserRole, + UserPatchPath, +) from crowdin_api.api_resources.users.resource import ( UsersResource, BaseUsersResource, EnterpriseUsersResource, ) from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestBaseUsersResource: @@ -78,6 +84,7 @@ def test_get_member_info(self, m_request, base_absolut_url): ( {}, { + "orderBy": None, "search": None, "role": None, "languageId": None, @@ -87,6 +94,13 @@ def test_get_member_info(self, m_request, base_absolut_url): ), ( { + "orderBy": Sorting( + [ + SortingRule( + ListProjectMembersCrowdinOrderBy.ID, SortingOrder.DESC + ) + ] + ), "search": "search", "role": UserRole.BLOCKED, "languageId": "ua", @@ -94,6 +108,13 @@ def test_get_member_info(self, m_request, base_absolut_url): "limit": 25, }, { + "orderBy": Sorting( + [ + SortingRule( + ListProjectMembersCrowdinOrderBy.ID, SortingOrder.DESC + ) + ] + ), "search": "search", "role": UserRole.BLOCKED, "languageId": "ua", @@ -159,6 +180,7 @@ def test_get_users_path(self, userId, path, base_absolut_url): ( {}, { + "orderBy": None, "search": None, "workflowStepId": None, "languageId": None, @@ -168,6 +190,14 @@ def test_get_users_path(self, userId, path, base_absolut_url): ), ( { + "orderBy": Sorting( + [ + SortingRule( + ListProjectMembersEnterpriseOrderBy.ID, + SortingOrder.DESC, + ) + ] + ), "search": "search", "workflowStepId": 72, "languageId": "ua", @@ -175,6 +205,14 @@ def test_get_users_path(self, userId, path, base_absolut_url): "limit": 25, }, { + "orderBy": Sorting( + [ + SortingRule( + ListProjectMembersEnterpriseOrderBy.ID, + SortingOrder.DESC, + ) + ] + ), "search": "search", "workflowStepId": 72, "languageId": "ua", diff --git a/crowdin_api/api_resources/workflows/enums.py b/crowdin_api/api_resources/workflows/enums.py new file mode 100644 index 0000000..288eaf7 --- /dev/null +++ b/crowdin_api/api_resources/workflows/enums.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class ListWorkflowStepStringsOrderBy(Enum): + ID = "id" + TEXT = "text" + IDENTIFIER = "identifier" + CONTEXT = "context" + CREATED_AT = "createdAt" + UPDATED_AT = "updatedAt" diff --git a/crowdin_api/api_resources/workflows/resource.py b/crowdin_api/api_resources/workflows/resource.py index 8191532..227737f 100644 --- a/crowdin_api/api_resources/workflows/resource.py +++ b/crowdin_api/api_resources/workflows/resource.py @@ -1,6 +1,7 @@ from typing import Optional from crowdin_api.api_resources.abstract.resources import BaseResource +from crowdin_api.sorting import Sorting class WorkflowsResource(BaseResource): @@ -99,7 +100,7 @@ def list_workflow_step_strings( projectId: Optional[int], stepId: int, languageIds: Optional[str] = None, - orderBy: Optional[str] = None, + orderBy: Optional[Sorting] = None, status: Optional[str] = None, limit: Optional[int] = None, offset: Optional[int] = None diff --git a/crowdin_api/api_resources/workflows/tests/test_workflows_resources.py b/crowdin_api/api_resources/workflows/tests/test_workflows_resources.py index f4cea33..5ff8c33 100644 --- a/crowdin_api/api_resources/workflows/tests/test_workflows_resources.py +++ b/crowdin_api/api_resources/workflows/tests/test_workflows_resources.py @@ -3,7 +3,9 @@ import pytest from crowdin_api.api_resources import WorkflowsResource +from crowdin_api.api_resources.workflows.enums import ListWorkflowStepStringsOrderBy from crowdin_api.requester import APIRequester +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule class TestWorkflowsResource: @@ -123,27 +125,39 @@ def test_get_workflow_template(self, m_request, base_absolut_url): "status": None, "offset": 0, "limit": 25, - } + }, ), ( { + "orderBy": Sorting( + [ + SortingRule( + ListWorkflowStepStringsOrderBy.ID, SortingOrder.DESC + ) + ] + ), "projectId": 1, "stepId": 2, "languageIds": "es,fr", - "orderBy": "createdAt", "status": "done", "offset": 10, - "limit": 50 + "limit": 50, }, { + "orderBy": Sorting( + [ + SortingRule( + ListWorkflowStepStringsOrderBy.ID, SortingOrder.DESC + ) + ] + ), "languageIds": "es,fr", - "orderBy": "createdAt", "status": "done", "offset": 10, - "limit": 50 - } + "limit": 50, + }, ), - ) + ), ) @mock.patch("crowdin_api.requester.APIRequester.request") def test_list_workflow_step_strings(self, m_request, in_params, request_params, base_absolut_url): diff --git a/crowdin_api/parser.py b/crowdin_api/parser.py index b1d453d..9d88795 100644 --- a/crowdin_api/parser.py +++ b/crowdin_api/parser.py @@ -3,6 +3,8 @@ import re from enum import Enum +from crowdin_api.sorting import Sorting + class CrowdinJSONEncoder(json.JSONEncoder): def default(self, obj): @@ -31,6 +33,9 @@ def default(self, obj): except TypeError: return obj.value + if isinstance(obj, Sorting): + return str(obj) + return super().default(obj) diff --git a/crowdin_api/sorting.py b/crowdin_api/sorting.py new file mode 100644 index 0000000..e3cc13d --- /dev/null +++ b/crowdin_api/sorting.py @@ -0,0 +1,51 @@ +from enum import Enum +from typing import Optional, List + + +class SortingOrder(Enum): + ASC = "asc" + DESC = "desc" + + def __str__(self): + return self.value + + def __eq__(self, other): + if isinstance(other, SortingOrder): + return self.value == other.value + return False + + +class SortingRule: + def __init__(self, rule: Enum, order: Optional[SortingOrder] = None): + if not isinstance(rule, Enum): + raise ValueError("Rule must be of type Enum.") + if not rule.value: + raise ValueError("Rule value cannot be empty.") + if isinstance(rule, SortingOrder): + raise ValueError("Rule cannot be of type SortingOrder.") + if order and not isinstance(order, SortingOrder): + raise ValueError("Order must be of type SortingOrder.") + + self.rule = rule.value + self.order = order + + def __str__(self): + return f"{self.rule} {self.order}" if self.order else self.rule + + def __eq__(self, other): + if not isinstance(other, SortingRule): + return False + return self.rule == other.rule and self.order == other.order + + +class Sorting: + def __init__(self, rules: List[SortingRule]): + self.rules = rules + + def __str__(self): + return ",".join([str(rule) for rule in self.rules]) + + def __eq__(self, other): + if not isinstance(other, Sorting): + return False + return self.rules == other.rules diff --git a/crowdin_api/tests/test_parser.py b/crowdin_api/tests/test_parser.py index 7b4f551..22724bf 100644 --- a/crowdin_api/tests/test_parser.py +++ b/crowdin_api/tests/test_parser.py @@ -1,7 +1,9 @@ import datetime +from enum import Enum import pytest from crowdin_api.parser import dumps, loads +from crowdin_api.sorting import Sorting, SortingOrder, SortingRule @pytest.mark.parametrize( @@ -74,3 +76,37 @@ def test_parser(in_value, out_value): dumps_result = dumps(load_result) assert dumps_result == in_value + + +class TestEnum(Enum): + ID = "id" + CREATED_AT = "createdAt" + + +def test_sorting_serialization(): + sorting = Sorting( + [ + SortingRule(TestEnum.ID, SortingOrder.DESC), + SortingRule(TestEnum.CREATED_AT, SortingOrder.ASC), + ] + ) + + result = dumps({"orderBy": sorting}) + expected = '{"orderBy": "id desc,createdAt asc"}' + + assert result == expected + + +def test_enum_serialization(): + enum_value = TestEnum.ID + result = dumps(enum_value) + expected = '"id"' + assert result == expected + + +def test_parser_default_fallback(): + class UnserializableObject: + pass + + with pytest.raises(TypeError): + dumps({"test": UnserializableObject()}) diff --git a/crowdin_api/tests/test_sorting.py b/crowdin_api/tests/test_sorting.py new file mode 100644 index 0000000..ced6001 --- /dev/null +++ b/crowdin_api/tests/test_sorting.py @@ -0,0 +1,82 @@ +import pytest +from crowdin_api.sorting import SortingOrder, SortingRule, Sorting +from enum import Enum + + +class TestOrderBy(Enum): + TEST = "test" + OTHER = "other" + LAST = "last" + + +class TestSorting: + def test_sorting_order_str(self): + assert str(SortingOrder.ASC) == "asc" + assert str(SortingOrder.DESC) == "desc" + + def test_sorting_order_equality(self): + assert SortingOrder.ASC == SortingOrder.ASC + assert SortingOrder.ASC != SortingOrder.DESC + assert SortingOrder.ASC != "asc" + + def test_sorting_rule_creation(self): + rule = SortingRule(TestOrderBy.TEST) + assert str(rule) == "test" + + rule_with_order = SortingRule(TestOrderBy.TEST, SortingOrder.ASC) + assert str(rule_with_order) == "test asc" + + def test_sorting_rule_validation(self): + with pytest.raises(ValueError, match="Rule must be of type Enum"): + SortingRule("not_enum") + + with pytest.raises(ValueError, match="Rule cannot be of type SortingOrder"): + SortingRule(SortingOrder.ASC) + + with pytest.raises(ValueError, match="Order must be of type SortingOrder"): + SortingRule(TestOrderBy.TEST, "asc") + + class EmptyEnum(Enum): + EMPTY = "" + + with pytest.raises(ValueError, match="Rule value cannot be empty"): + SortingRule(EmptyEnum.EMPTY) + + def test_sorting_rule_equality(self): + rule1 = SortingRule(TestOrderBy.TEST, SortingOrder.ASC) + rule2 = SortingRule(TestOrderBy.TEST, SortingOrder.ASC) + rule3 = SortingRule(TestOrderBy.OTHER, SortingOrder.ASC) + + assert rule1 == rule2 + assert rule1 != rule3 + assert rule1 != "test asc" + + def test_sorting_multiple_rules(self): + rules = [ + SortingRule(TestOrderBy.TEST, SortingOrder.ASC), + SortingRule(TestOrderBy.OTHER, SortingOrder.DESC), + ] + sorting = Sorting(rules) + assert str(sorting) == "test asc,other desc" + + def test_sorting_multiple_rules_two(self): + rules = [ + SortingRule(TestOrderBy.TEST, SortingOrder.ASC), + SortingRule(TestOrderBy.OTHER), + SortingRule(TestOrderBy.LAST, SortingOrder.DESC), + ] + sorting = Sorting(rules) + assert str(sorting) == "test asc,other,last desc" + + def test_sorting_equality(self): + rules1 = [SortingRule(TestOrderBy.TEST)] + rules2 = [SortingRule(TestOrderBy.TEST)] + rules3 = [SortingRule(TestOrderBy.OTHER)] + + sort1 = Sorting(rules1) + sort2 = Sorting(rules2) + sort3 = Sorting(rules3) + + assert sort1 == sort2 + assert sort1 != sort3 + assert sort1 != "test"