Skip to content

Commit 7fd58d3

Browse files
committed
feat: add_subprojects parameter for update_project()
1 parent e04a266 commit 7fd58d3

3 files changed

Lines changed: 231 additions & 9 deletions

File tree

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## NEXT
44
* new method `update_project_release_relationship`.
55
* original get_health_status() endpoint URL has been restored by the SW360 team.
6+
* `update_project` has a new parameter `add_subprojects` to only **add** the new
7+
sub-projects and not to overwrite all existing sub-projects.
68

79
## 1.1.0
810
* New method `duplicate_project` to create a copy of an existing project.

sw360/sw360_api.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ def create_new_project(self, name, project_type, visibility,
446446

447447
raise SW360Error(response, url)
448448

449-
def update_project(self, project, project_id):
449+
def update_project(self, project: dict, project_id: str, add_subprojects=False):
450450
"""Update an existing project
451451
452452
API endpoint: PATCH /projects
@@ -459,8 +459,21 @@ def update_project(self, project, project_id):
459459
:rtype: JSON SW360 result object
460460
:raises SW360Error: if there is a negative HTTP response
461461
"""
462-
# 2019-04-03: error 405 - method not allowed
462+
if not project_id:
463+
raise SW360Error(message="No project id provided!")
464+
463465
url = self.url + "resource/api/projects/" + project_id
466+
467+
if add_subprojects:
468+
current = self.get_project(project_id)
469+
if (current is not None and "linkedProjects" in current):
470+
for sp in current["linkedProjects"]:
471+
pid = self.get_id_from_href(sp["project"])
472+
if pid not in project["linkedProjects"]:
473+
nsp = {}
474+
nsp["projectRelationship"] = sp.get("relation", "CONTAINED")
475+
project["linkedProjects"][pid] = nsp
476+
464477
response = requests.patch(url, json=project, headers=self.api_headers)
465478

466479
if response.ok:
@@ -608,7 +621,8 @@ def duplicate_project(self, project_id: str, new_version: str):
608621

609622
raise SW360Error(response, url)
610623

611-
def update_project_release_relationship(self, project_id: str, release_id: str, new_state: str,
624+
def update_project_release_relationship(
625+
self, project_id: str, release_id: str, new_state: str,
612626
new_relation: str, comment: str):
613627
"""Update the relationship for a specific release of a project
614628
@@ -618,12 +632,12 @@ def update_project_release_relationship(self, project_id: str, release_id: str,
618632
:type project_id: string
619633
:param release_id: the id of the release to be requested
620634
:type release_id: string
621-
:param new_state: the new mainline state of the release, one of
635+
:param new_state: the new mainline state of the release, one of
622636
(OPEN, MAINLINE, SPECIFIC, PHASEOUT, DENIED)
623637
:type new_state: string
624-
:param new_relation: the new relation of the release, one of
625-
(CONTAINED, REFERRED, UNKNOWN, DYNAMICALLY_LINKED, STATICALLY_LINKED, SIDE_BY_SIDE, STANDALONE,
626-
INTERNAL_USE, OPTIONAL, TO_BE_REPLACED, CODE_SNIPPET)
638+
:param new_relation: the new relation of the release, one of
639+
(CONTAINED, REFERRED, UNKNOWN, DYNAMICALLY_LINKED, STATICALLY_LINKED, SIDE_BY_SIDE,
640+
STANDALONE, INTERNAL_USE, OPTIONAL, TO_BE_REPLACED, CODE_SNIPPET)
627641
:type new_relation: string
628642
:param comment: a comment
629643
:type comment: string

tests/test_sw360_projects.py

Lines changed: 208 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -------------------------------------------------------------------------------
2-
# (c) 2019-2021 Siemens AG
2+
# (c) 2019-2022 Siemens AG
33
# All Rights Reserved.
44
# Author: thomas.graf@siemens.com
55
#
@@ -572,7 +572,127 @@ def test_update_project(self):
572572
project["version"] = "9.99"
573573
project["projectType"] = "PRODUCT"
574574

575-
lib.update_project(project, "123")
575+
sub_projects = {}
576+
sub_proj = {}
577+
sub_proj["projectRelationship"] = "CONTAINED"
578+
579+
sub_projects["12345"] = sub_proj
580+
project["linkedProjects"] = sub_projects
581+
582+
lib.update_project(project, "123", False)
583+
584+
@responses.activate
585+
def test_update_project_sub_projects_no_add(self):
586+
lib = self.get_logged_in_lib()
587+
588+
responses.add(
589+
responses.GET,
590+
url=self.MYURL + "resource/api/projects/123",
591+
body='{"name": "My Testproject", "linkedProjects": []}',
592+
status=200,
593+
content_type="application/json",
594+
adding_headers={"Authorization": "Token " + self.MYTOKEN},
595+
)
596+
597+
responses.add(
598+
responses.PATCH,
599+
url=self.MYURL + "resource/api/projects/123",
600+
body="4",
601+
status=202,
602+
match=[
603+
responses.json_params_matcher({
604+
"name": "NewComponent",
605+
"version": "9.99",
606+
"projectType": "PRODUCT",
607+
"linkedProjects": {
608+
"12345": {"projectRelationship": "CONTAINED"}
609+
},
610+
})
611+
]
612+
)
613+
614+
project = {}
615+
project["name"] = "NewComponent"
616+
project["version"] = "9.99"
617+
project["projectType"] = "PRODUCT"
618+
619+
sub_projects = {}
620+
sub_proj = {}
621+
sub_proj["projectRelationship"] = "CONTAINED"
622+
623+
sub_projects["12345"] = sub_proj
624+
project["linkedProjects"] = sub_projects
625+
626+
lib.update_project(project, "123", True)
627+
628+
@responses.activate
629+
def test_update_project_sub_projects_with_add(self):
630+
lib = self.get_logged_in_lib()
631+
632+
responses.add(
633+
responses.GET,
634+
url=self.MYURL + "resource/api/projects/123",
635+
body='{"name": "My Testproject", ' +
636+
'"linkedProjects": [' +
637+
'{ "project": "998877" }' +
638+
']}',
639+
status=200,
640+
content_type="application/json",
641+
adding_headers={"Authorization": "Token " + self.MYTOKEN},
642+
)
643+
644+
responses.add(
645+
responses.PATCH,
646+
url=self.MYURL + "resource/api/projects/123",
647+
body="4",
648+
status=202,
649+
match=[
650+
responses.json_params_matcher({
651+
"name": "NewComponent",
652+
"version": "9.99",
653+
"projectType": "PRODUCT",
654+
"linkedProjects": {
655+
"12345": {"projectRelationship": "CONTAINED"},
656+
"998877": {"projectRelationship": "CONTAINED"}
657+
},
658+
})
659+
]
660+
)
661+
662+
project = {}
663+
project["name"] = "NewComponent"
664+
project["version"] = "9.99"
665+
project["projectType"] = "PRODUCT"
666+
667+
sub_projects = {}
668+
sub_proj = {}
669+
sub_proj["projectRelationship"] = "CONTAINED"
670+
671+
sub_projects["12345"] = sub_proj
672+
project["linkedProjects"] = sub_projects
673+
674+
lib.update_project(project, "123", True)
675+
676+
@responses.activate
677+
def test_update_project_no_id(self):
678+
lib = self.get_logged_in_lib()
679+
680+
responses.add(
681+
responses.PATCH,
682+
url=self.MYURL + "resource/api/projects/123",
683+
body="4",
684+
status=202,
685+
)
686+
687+
project = {}
688+
project["name"] = "NewComponent"
689+
project["version"] = "9.99"
690+
project["projectType"] = "PRODUCT"
691+
692+
with self.assertRaises(SW360Error) as context:
693+
lib.update_project(project, None)
694+
695+
self.assertEqual("No project id provided!", context.exception.message)
576696

577697
@responses.activate
578698
def test_update_project_failed(self):
@@ -822,6 +942,92 @@ def test_duplicate_project_failed(self):
822942
with self.assertRaises(SW360Error) as context:
823943
lib.duplicate_project("123", "42")
824944

945+
print(context.exception)
946+
self.assertEqual(404, context.exception.response.status_code)
947+
948+
@responses.activate
949+
def test_update_project_release_relationship_no_project_id(self):
950+
lib = self.get_logged_in_lib()
951+
952+
responses.add(
953+
responses.PATCH,
954+
url=self.MYURL + "resource/api/projects/123",
955+
body="4",
956+
status=202,
957+
)
958+
959+
project = {}
960+
project["name"] = "NewComponent"
961+
project["version"] = "9.99"
962+
project["projectType"] = "PRODUCT"
963+
964+
with self.assertRaises(SW360Error) as context:
965+
lib.update_project_release_relationship(None, "22", "state", "rel", "cmt")
966+
967+
self.assertEqual("No project id provided!", context.exception.message)
968+
969+
@responses.activate
970+
def test_update_project_release_relationship_no_release_id(self):
971+
lib = self.get_logged_in_lib()
972+
973+
responses.add(
974+
responses.PATCH,
975+
url=self.MYURL + "resource/api/projects/123",
976+
body="4",
977+
status=202,
978+
)
979+
980+
project = {}
981+
project["name"] = "NewComponent"
982+
project["version"] = "9.99"
983+
project["projectType"] = "PRODUCT"
984+
985+
with self.assertRaises(SW360Error) as context:
986+
lib.update_project_release_relationship("123", None, "state", "rel", "cmt")
987+
988+
self.assertEqual("No release id provided!", context.exception.message)
989+
990+
@responses.activate
991+
def test_update_project_release_relationship_failed(self):
992+
lib = self.get_logged_in_lib()
993+
994+
responses.add(
995+
responses.PATCH,
996+
url=self.MYURL + "resource/api/projects/123/release/9988",
997+
body="4",
998+
status=404,
999+
)
1000+
1001+
project = {}
1002+
project["name"] = "NewComponent"
1003+
project["version"] = "9.99"
1004+
project["projectType"] = "PRODUCT"
1005+
1006+
with self.assertRaises(SW360Error) as context:
1007+
lib.update_project_release_relationship("123", "9988", "state", "rel", "cmt")
1008+
1009+
self.assertEqual(404, context.exception.response.status_code)
1010+
1011+
@responses.activate
1012+
def test_update_project_release_relationship(self):
1013+
lib = self.get_logged_in_lib()
1014+
1015+
responses.add(
1016+
responses.PATCH,
1017+
url=self.MYURL + "resource/api/projects/123/release/987",
1018+
body="4",
1019+
status=202,
1020+
match=[
1021+
responses.json_params_matcher({
1022+
"releaseRelation": "STANDALONE",
1023+
"mainlineState": "SPECIFIC",
1024+
"comment": "mycomment"
1025+
})
1026+
]
1027+
)
1028+
1029+
lib.update_project_release_relationship("123", "987", "SPECIFIC", "STANDALONE", "mycomment")
1030+
8251031

8261032
if __name__ == "__main__":
8271033
unittest.main()

0 commit comments

Comments
 (0)