From 87161ad11d434f508dc2aef8ab057ffdb77f18a6 Mon Sep 17 00:00:00 2001 From: Gaurav Mishra Date: Sun, 16 Aug 2026 21:16:44 +0530 Subject: [PATCH 1/3] feat(moderation): update API calls to v20 style Signed-off-by: Gaurav Mishra --- sw360/moderationrequests.py | 68 ++++++++++++++++---------- sw360/sorting.py | 10 ++++ tests/test_sw360_moderationrequests.py | 9 ++-- 3 files changed, 56 insertions(+), 31 deletions(-) diff --git a/sw360/moderationrequests.py b/sw360/moderationrequests.py index 4027d9d..79c2aa7 100644 --- a/sw360/moderationrequests.py +++ b/sw360/moderationrequests.py @@ -12,55 +12,66 @@ from sw360.sw360error import SW360Error from .base import BaseMixin +from .sorting import ModerationSortColumn, SortParam class ModerationRequestMixin(BaseMixin): - def get_all_moderation_requests(self, page: int = -1, page_size: int = -1, - sort: str = "") -> Optional[Dict[str, Any]]: + def get_all_moderation_requests( + self, page: int = -1, page_size: int = -1, + sort: Optional[SortParam] = None + ) -> Optional[Dict[str, Any]]: """Get information of about all moderation requests API endpoint: GET /moderationrequest :param page: page to retrieve :type page: int - :param page_size: page size to use + :param page_size: page size to use, `-1` to get all :type page_size: int - :param sort: sort order for the packages ("name,desc"; "name,asc") - :type sort: str + :param sort: sort order for the components (Sort by request date if + `None`) + :type sort: SortParam :return: list of moderation requests :rtype: list of JSON moderation requests objects :raises SW360Error: if there is a negative HTTP response """ fullbase_url = self.url + "resource/api/moderationrequest" - params = {} + params = {"luceneSearch": "true"} - if page > -1: - params["page"] = str(page) - params["page_entries"] = str(page_size) - - if sort: - params["sort"] = sort + if sort is None: + sort = ModerationSortColumn.REQUEST_DATE.desc() full_url = self._add_params(fullbase_url, params) - resp = self.api_get(full_url) + if page > -1 and page_size > -1: + full_url = self._add_pagination(full_url, page, page_size, sort) + + if page_size == -1: + resp = self.api_get_all(full_url, sort) + else: + resp = self.api_get(full_url) + return resp - def get_moderation_requests_by_state(self, state: str, all_details: bool = False, - page: int = -1, page_size: int = -1, - sort: str = "") -> Optional[Dict[str, Any]]: + def get_moderation_requests_by_state( + self, state: str, all_details: bool = False, page: int = -1, + page_size: int = -1, sort: Optional[SortParam] = None + ) -> Optional[Dict[str, Any]]: """Get information of about all moderation requests API endpoint: GET /moderationrequest/byState - :param all_details: retrieve all package details (optional)) + :param state: State of the moderation request. One of "open", "closed" + :type state: string + :param all_details: retrieve all moderation details (optional) :type all_details: bool :param page: page to retrieve :type page: int - :param page_size: page size to use + :param page_size: page size to use, `-1` to get all :type page_size: int - :param sort: sort order for the packages ("name,desc"; "name,asc") - :type sort: str + :param sort: sort order for the components (Sort by request date if + `None`) + :type sort: SortParam :return: list of moderation requests :rtype: list of JSON moderation requests objects :raises SW360Error: if there is a negative HTTP response @@ -74,15 +85,18 @@ def get_moderation_requests_by_state(self, state: str, all_details: bool = False if all_details: params["allDetails"] = "true" - if page > -1: - params["page"] = str(page) - params["page_entries"] = str(page_size) - - if sort: - params["sort"] = sort + if sort is None: + sort = ModerationSortColumn.REQUEST_DATE.desc() full_url = self._add_params(fullbase_url, params) - resp = self.api_get(full_url) + if page > -1 and page_size > -1: + full_url = self._add_pagination(full_url, page, page_size, sort) + + if page_size == -1: + resp = self.api_get_all(full_url, sort) + else: + resp = self.api_get(full_url) + return resp def get_moderation_request(self, mr_id: str) -> Optional[Dict[str, Any]]: diff --git a/sw360/sorting.py b/sw360/sorting.py index fc68177..a3383d0 100644 --- a/sw360/sorting.py +++ b/sw360/sorting.py @@ -57,3 +57,13 @@ class LicenseSortColumn(BaseSortMixin, Enum): SCORE = "score" FULL_NAME = "fullName" SHORT_NAME = "shortName" + +class ModerationSortColumn(BaseSortMixin, Enum): + SCORE = "score" + DOCUMENT_NAME = "documentName" + DOCUMENT_TYPE = "documentType" + COMPONENT_TYPE = "componentType" + MODERATION_STATE = "moderationState" + REQUEST_DATE = "requestDate" + REQUESTING_USER = "requestingUser" + REQUESTING_USER_DEPARTMENT = "requestingUserDepartment" diff --git a/tests/test_sw360_moderationrequests.py b/tests/test_sw360_moderationrequests.py index b53f472..44b4cbd 100644 --- a/tests/test_sw360_moderationrequests.py +++ b/tests/test_sw360_moderationrequests.py @@ -14,6 +14,7 @@ import responses from sw360 import SW360 +from sw360.sorting import ModerationSortColumn sys.path.insert(1, "..") @@ -50,7 +51,7 @@ def test_get_all_moderation_requests(self) -> None: responses.add( method=responses.GET, - url=self.MYURL + "resource/api/moderationrequest?page=2&page_entries=8&sort=timestamp%2Cdesc", + url=self.MYURL + "resource/api/moderationrequest?luceneSearch=true&page=2&page_entries=8&sort=requestDate%2Cdesc", body='''{ "_embedded": { "sw360:moderationRequests": [ @@ -104,7 +105,7 @@ def test_get_all_moderation_requests(self) -> None: adding_headers={"Authorization": "Token " + self.MYTOKEN}, ) - mrs = lib.get_all_moderation_requests(page=2, page_size=8, sort="timestamp,desc") + mrs = lib.get_all_moderation_requests(page=2, page_size=8, sort=ModerationSortColumn.REQUEST_DATE.desc()) self.assertIsNotNone(mrs) if mrs: self.assertTrue(len(mrs) > 0) @@ -120,7 +121,7 @@ def test_get_moderation_requests_by_state(self) -> None: responses.add( method=responses.GET, - url=self.MYURL + "resource/api/moderationrequest/byState?state=open&allDetails=true&page=2&page_entries=8&sort=timestamp%2Cdesc", # noqa + url=self.MYURL + "resource/api/moderationrequest/byState?state=open&allDetails=true&page=2&page_entries=8&sort=requestDate%2Cdesc", # noqa body='''{ "_embedded": { "sw360:moderationRequests": [ @@ -174,7 +175,7 @@ def test_get_moderation_requests_by_state(self) -> None: adding_headers={"Authorization": "Token " + self.MYTOKEN}, ) - mrs = lib.get_moderation_requests_by_state("open", True, page=2, page_size=8, sort="timestamp,desc") + mrs = lib.get_moderation_requests_by_state("open", True, page=2, page_size=8, sort=ModerationSortColumn.REQUEST_DATE.desc()) self.assertIsNotNone(mrs) if mrs: self.assertTrue(len(mrs) > 0) From 6864478331e1c764fca1ae77e19f9d1d2c4b8140 Mon Sep 17 00:00:00 2001 From: Gaurav Mishra Date: Mon, 17 Aug 2026 00:35:10 +0530 Subject: [PATCH 2/3] fix(api): URL creation in project and component Signed-off-by: Gaurav Mishra --- sw360/components.py | 2 +- sw360/project.py | 2 +- tests/test_sw360_components.py | 2 +- tests/test_sw360_projects.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sw360/components.py b/sw360/components.py index 59d945e..794faa4 100644 --- a/sw360/components.py +++ b/sw360/components.py @@ -44,7 +44,7 @@ def __get_components_filtered( full_url = self._add_params(url, {"luceneSearch": "true"}) if page > -1 and page_size > -1: - full_url = self._add_pagination(url, page, page_size, sort) + full_url = self._add_pagination(full_url, page, page_size, sort) if page_size == -1: resp = self.api_get_all(full_url, sort) diff --git a/sw360/project.py b/sw360/project.py index 2d5e40e..130c77c 100644 --- a/sw360/project.py +++ b/sw360/project.py @@ -99,7 +99,7 @@ def __get_projects_filtered( full_url = self._add_params(url, {"luceneSearch": "true"}) if page > -1 and page_size > -1: - full_url = self._add_pagination(url, page, page_size, sort) + full_url = self._add_pagination(full_url, page, page_size, sort) if page_size == -1: resp = self.api_get_all(full_url, sort) diff --git a/tests/test_sw360_components.py b/tests/test_sw360_components.py index b9fde6c..029a1cf 100644 --- a/tests/test_sw360_components.py +++ b/tests/test_sw360_components.py @@ -128,7 +128,7 @@ def test_get_all_components_with_fields_and_paging(self) -> None: self.assertTrue(actual) responses.add( method=responses.GET, - url=self.MYURL + "resource/api/components?fields=ownerCountry&page=1&page_entries=2&sort=name,asc", # noqa + url=self.MYURL + "resource/api/components?fields=ownerCountry&luceneSearch=true&page=1&page_entries=2&sort=name,asc", # noqa body='{"_embedded": {"sw360:components": [{"name": "Tethys.Logging", "ownerCountry": "DE", "componentType": "OSS", "externalIds": {"package-url": "pkg:nuget/Tethys.Logging"}}]}}', # noqa status=200, content_type="application/json", diff --git a/tests/test_sw360_projects.py b/tests/test_sw360_projects.py index 38b4d53..7d4e3ca 100644 --- a/tests/test_sw360_projects.py +++ b/tests/test_sw360_projects.py @@ -222,7 +222,7 @@ def test_get_projects_with_paging(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects?page=1&page_entries=2&sort=name,asc", + url=self.MYURL + "resource/api/projects?luceneSearch=true&page=1&page_entries=2&sort=name,asc", body='{"_embedded": {"sw360:projects": [{"name": "My Testproject"}]}}', status=200, content_type="application/json", @@ -240,7 +240,7 @@ def test_get_projects_with_paging_and_details(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects?allDetails=true&page=3&page_entries=4&sort=name%2Cdesc", # noqa + url=self.MYURL + "resource/api/projects?allDetails=true&luceneSearch=true&page=3&page_entries=4&sort=name%2Cdesc", # noqa body='{"_embedded": {"sw360:projects": [{"name": "My Testproject"}]}}', status=200, content_type="application/json", From d85b26de0d4982b76ba00817d46ae8fcfe26a62b Mon Sep 17 00:00:00 2001 From: Thomas Graf Date: Tue, 18 Aug 2026 18:08:56 +0200 Subject: [PATCH 3/3] style: fix issues --- sw360/components.py | 8 ++--- sw360/sorting.py | 3 ++ tests/test_sw360_components.py | 5 ++-- tests/test_sw360_moderationrequests.py | 13 ++++---- tests/test_sw360_projects.py | 41 +++++++++++++++++--------- 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/sw360/components.py b/sw360/components.py index 794faa4..a33a150 100644 --- a/sw360/components.py +++ b/sw360/components.py @@ -3,7 +3,7 @@ # Copyright (c) 2022 BMW CarIT GmbH # All Rights Reserved. # Authors: thomas.graf@siemens.com, gernot.hillier@siemens.com -# Authors: helio.chissini-de-castro@bmw.de +# Authors: helio.chissini-de-castro@bmw.de, mishra.gaurav@siemens.com # # Licensed under the MIT license. # SPDX-License-Identifier: MIT @@ -53,7 +53,7 @@ def __get_components_filtered( if (resp and "_embedded" in resp and - "sw360:components" in resp["_embedded"]): + "sw360:components" in resp["_embedded"]): return resp["_embedded"]["sw360:components"] return [] @@ -407,8 +407,8 @@ def upload_attachment_to_component( raise SW360Error(message="No component id provided!") attachment_content = self._upload_resource_file(upload_file, upload_type, upload_comment) - attachment_content['attachmentType'] = upload_type # Make sure the type is correct - attachment_content['createdComment'] = upload_comment # Override + attachment_content['attachmentType'] = upload_type # Make sure the type is correct + attachment_content['createdComment'] = upload_comment # Override current_component = self.get_component(component_id) attachments = self._get_attachments(current_component) diff --git a/sw360/sorting.py b/sw360/sorting.py index a3383d0..be81272 100644 --- a/sw360/sorting.py +++ b/sw360/sorting.py @@ -45,6 +45,7 @@ class ReleaseSortColumn(BaseSortMixin, Enum): MAINLINE_STATE = "mainlineState" SCORE = "score" + class ComponentSortColumn(BaseSortMixin, Enum): SCORE = "score" CREATED_ON = "createdOn" @@ -53,11 +54,13 @@ class ComponentSortColumn(BaseSortMixin, Enum): MAIN_LICENSE_IDS = "mainLicenseIds" TYPE = "type" + class LicenseSortColumn(BaseSortMixin, Enum): SCORE = "score" FULL_NAME = "fullName" SHORT_NAME = "shortName" + class ModerationSortColumn(BaseSortMixin, Enum): SCORE = "score" DOCUMENT_NAME = "documentName" diff --git a/tests/test_sw360_components.py b/tests/test_sw360_components.py index 029a1cf..4895919 100644 --- a/tests/test_sw360_components.py +++ b/tests/test_sw360_components.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------------- # Copyright (c) 2020-2026 Siemens # All Rights Reserved. -# Author: thomas.graf@siemens.com +# Author: thomas.graf@siemens.com, mishra.gaurav@siemens.com # # Licensed under the MIT license. # SPDX-License-Identifier: MIT @@ -366,7 +366,8 @@ def test_get_component_by_name(self) -> None: responses.add( method=responses.GET, - url=self.MYURL + "resource/api/components?name=MyComponent&luceneSearch=true&page=0&page_entries=50&sort=score,asc", + url=self.MYURL + + "resource/api/components?name=MyComponent&luceneSearch=true&page=0&page_entries=50&sort=score,asc", body='{"name": "MyComponent"}', status=200, content_type="application/json", diff --git a/tests/test_sw360_moderationrequests.py b/tests/test_sw360_moderationrequests.py index 44b4cbd..2b8e487 100644 --- a/tests/test_sw360_moderationrequests.py +++ b/tests/test_sw360_moderationrequests.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2024 Siemens +# Copyright (c) 2024-2026 Siemens # All Rights Reserved. -# Author: thomas.graf@siemens.com +# Author: thomas.graf@siemens.com, mishra.gaurav@siemens.com # # Licensed under the MIT license. # SPDX-License-Identifier: MIT @@ -51,7 +51,8 @@ def test_get_all_moderation_requests(self) -> None: responses.add( method=responses.GET, - url=self.MYURL + "resource/api/moderationrequest?luceneSearch=true&page=2&page_entries=8&sort=requestDate%2Cdesc", + url=self.MYURL + + "resource/api/moderationrequest?luceneSearch=true&page=2&page_entries=8&sort=requestDate%2Cdesc", body='''{ "_embedded": { "sw360:moderationRequests": [ @@ -121,7 +122,8 @@ def test_get_moderation_requests_by_state(self) -> None: responses.add( method=responses.GET, - url=self.MYURL + "resource/api/moderationrequest/byState?state=open&allDetails=true&page=2&page_entries=8&sort=requestDate%2Cdesc", # noqa + url=self.MYURL + + "resource/api/moderationrequest/byState?state=open&allDetails=true&page=2&page_entries=8&sort=requestDate%2Cdesc", # noqa body='''{ "_embedded": { "sw360:moderationRequests": [ @@ -175,7 +177,8 @@ def test_get_moderation_requests_by_state(self) -> None: adding_headers={"Authorization": "Token " + self.MYTOKEN}, ) - mrs = lib.get_moderation_requests_by_state("open", True, page=2, page_size=8, sort=ModerationSortColumn.REQUEST_DATE.desc()) + mrs = lib.get_moderation_requests_by_state("open", True, page=2, page_size=8, + sort=ModerationSortColumn.REQUEST_DATE.desc()) self.assertIsNotNone(mrs) if mrs: self.assertTrue(len(mrs) > 0) diff --git a/tests/test_sw360_projects.py b/tests/test_sw360_projects.py index 7d4e3ca..ce0b3d4 100644 --- a/tests/test_sw360_projects.py +++ b/tests/test_sw360_projects.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------------- # Copyright (c) 2019-2026 Siemens # All Rights Reserved. -# Author: thomas.graf@siemens.com +# Author: thomas.graf@siemens.com, mishra.gaurav@siemens.com # # Licensed under the MIT license. # SPDX-License-Identifier: MIT @@ -141,7 +141,8 @@ def test_get_project_releases(self) -> None: lib = self.get_logged_in_lib() responses.add( responses.GET, - url=self.MYURL + "resource/api/projects/123/releases?transitive=false&page=0&page_entries=50&sort=createdOn,desc", + url=self.MYURL + + "resource/api/projects/123/releases?transitive=false&page=0&page_entries=50&sort=createdOn,desc", body='{"name": "My Testproject"}', status=200, content_type="application/json", @@ -153,7 +154,8 @@ def test_get_project_releases(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects/123/releases?transitive=true&page=0&page_entries=50&sort=createdOn,desc", + url=self.MYURL + + "resource/api/projects/123/releases?transitive=true&page=0&page_entries=50&sort=createdOn,desc", body='{"name": "My Testproject"}', status=200, content_type="application/json", @@ -204,7 +206,8 @@ def test_get_projects_with_details(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects?allDetails=true&luceneSearch=true&page=0&page_entries=50&sort=name,asc", + url=self.MYURL + + "resource/api/projects?allDetails=true&luceneSearch=true&page=0&page_entries=50&sort=name,asc", body='{"_embedded": {"sw360:projects": [{"name": "My Testproject"}]}}', status=200, content_type="application/json", @@ -222,7 +225,8 @@ def test_get_projects_with_paging(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects?luceneSearch=true&page=1&page_entries=2&sort=name,asc", + url=self.MYURL + + "resource/api/projects?luceneSearch=true&page=1&page_entries=2&sort=name,asc", body='{"_embedded": {"sw360:projects": [{"name": "My Testproject"}]}}', status=200, content_type="application/json", @@ -240,7 +244,8 @@ def test_get_projects_with_paging_and_details(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects?allDetails=true&luceneSearch=true&page=3&page_entries=4&sort=name%2Cdesc", # noqa + url=self.MYURL + + "resource/api/projects?allDetails=true&luceneSearch=true&page=3&page_entries=4&sort=name%2Cdesc", # noqa body='{"_embedded": {"sw360:projects": [{"name": "My Testproject"}]}}', status=200, content_type="application/json", @@ -258,7 +263,8 @@ def test_get_projects_by_type(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects?type=SERVICE&luceneSearch=true&page=0&page_entries=50&sort=name,asc", + url=self.MYURL + + "resource/api/projects?type=SERVICE&luceneSearch=true&page=0&page_entries=50&sort=name,asc", body='{"_embedded": {"sw360:projects": [{"name": "My Testproject", "projectType": "SERVICE"}]}}', # noqa status=200, content_type="application/json", @@ -277,7 +283,8 @@ def test_get_projects_by_type_no_reply(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects?type=SERVICE&luceneSearch=true&page=0&page_entries=50&sort=name,asc", + url=self.MYURL + + "resource/api/projects?type=SERVICE&luceneSearch=true&page=0&page_entries=50&sort=name,asc", body='{}', status=200, content_type="application/json", @@ -294,7 +301,8 @@ def test_get_projects_by_type_invalid_reply(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects?type=SERVICE&luceneSearch=true&page=0&page_entries=50&sort=name,asc", + url=self.MYURL + + "resource/api/projects?type=SERVICE&luceneSearch=true&page=0&page_entries=50&sort=name,asc", body='{"_xxembedded": {"sw360:projects": [{"name": "My Testproject", "projectType": "SERVICE"}]}}', # noqa status=200, content_type="application/json", @@ -311,7 +319,8 @@ def test_get_projects_by_type_invalid_reply2(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects?type=SERVICE&luceneSearch=true&page=0&page_entries=50&sort=name,asc", + url=self.MYURL + + "resource/api/projects?type=SERVICE&luceneSearch=true&page=0&page_entries=50&sort=name,asc", body='{"_embedded": {"xxsw360:projects": [{"name": "My Testproject", "projectType": "SERVICE"}]}}', # noqa status=200, content_type="application/json", @@ -1004,7 +1013,8 @@ def test_update_project_releases_fresh_prj(self) -> None: lib = self.get_logged_in_lib() responses.add( responses.GET, - url=self.MYURL + "resource/api/projects/123/releases?transitive=false&page=0&page_entries=50&sort=createdOn,desc", + url=self.MYURL + + "resource/api/projects/123/releases?transitive=false&page=0&page_entries=50&sort=createdOn,desc", json={}, ) responses.add( @@ -1016,7 +1026,8 @@ def test_update_project_releases_fresh_prj(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects/124/releases?transitive=false&page=0&page_entries=50&sort=createdOn,desc", + url=self.MYURL + + "resource/api/projects/124/releases?transitive=false&page=0&page_entries=50&sort=createdOn,desc", json={'_embedded': {'sw360:projects': []}}, ) responses.add( @@ -1032,7 +1043,8 @@ def test_update_project_releases(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects/123/releases?transitive=false&page=0&page_entries=50&sort=createdOn,desc", + url=self.MYURL + + "resource/api/projects/123/releases?transitive=false&page=0&page_entries=50&sort=createdOn,desc", body='{"_embedded": {"sw360:releases": [{"name": "ngx-device-detector ", "version": "1.3.20","_links": {"self": {"href": "https://sw360.siemens.com/resource/api/releases/3a4865e453873ee00d924469ff40f391" }}}]}}', # noqa status=200, content_type="application/json", @@ -1111,7 +1123,8 @@ def test_update_project_releases_failed(self) -> None: responses.add( responses.GET, - url=self.MYURL + "resource/api/projects/123/releases?transitive=false&page=0&page_entries=50&sort=createdOn,desc", + url=self.MYURL + + "resource/api/projects/123/releases?transitive=false&page=0&page_entries=50&sort=createdOn,desc", body='{"_embedded": {"sw360:releases": [{"name": "ngx-device-detector ", "version": "1.3.20","_links": {"self": {"href": "https://sw360.siemens.com/resource/api/releases/3a4865e453873ee00d924469ff40f391" }}}]}}', # noqa status=200, content_type="application/json",